From: cutting@skye.parc.xerox.com (Doug Cutting)
Newsgroups: comp.lang.lisp
Subject: Re: CL I/O performance (Was: Playing sparcaudio directly from lisp)
Date: 11 Jan 93 18:56:59 GMT
Organization: Xerox PARC, Palo Alto, CA

In article <9301081511.AA19973@maya.cs.nyu.edu> marcoxa@maya.CS.nyu.EDU (Marco Antoniotti) writes:
> In article <C0IEG8.G5n.1@cs.cmu.edu> mkant+@cs.cmu.edu (Mark Kantrowitz) writes:
>    I wrote the following code for Allegro CL. As you can see by
>    experimentation with the code and variations on it, playing the sound
>    files from Lisp is much too slow. Lisp file IO just isn't fast enough.
>    If you're going to play sound files, you'll have to do much of the
>    work in C, and then use a foreign function interface to run the code. 
> 
> This is an interesting remark. I ran through the code and I sincerely
> do not understand why a Lisp application should be "slower" doing
> IO than a C/C++ application.
> 
> As far as my experience is concerned the only "slow" call is the 'elt'
> in read-ascii-string. The functions do not seem to cons a lot.

There are several things in Common Lisp which make it difficult to
implement I/O as quickly as does C.  These include:

1. CL I/O functions can't specialize on the stream argument.

CL allows for many different types of streams (string, file,
concatenated, etc).  This suggests a CLOS implementation of the stream
functions, with a one method dispatch overhead for each call.
Unfortunately, the stream argument to most calls is optional, and
hence cannot be specialized.  Hence a CLOS implementation of CL I/O
must have a DEFUN'ed veneer to do the argument defaulting.  A cleanup
proposal (STREAM-DEFINITION-BY-USER) was made to standardize the
generic function level interface, but it has not been adopted.

2. CL stream types can't be adequately declared.

The compiler might be able optimize calls to (READ-BYTE S) if it knew
that S was the result of (OPEN P :element-type '(UNSIGNED-BYTE 8)),
but one cannot declare the ELEMENT-TYPEs of FILE-STREAMs.

3. 32-bit quantities, natural for file I/O, are usually bignums in CL.

When performing binary I/O, care must be taken to structure things so
that 32-bit quantities are not encountered (16- and 24-bit quantities
are fixnums in most implementations).  Some CL implementations may
provide unboxed 32-bit quantities which substantially ameliorates this
problem.  A rather ugly workaround we've used, when reading 32-bit
quantities which are usually fixnums, is the following:

(defun write-byte32 (byte32 stream)
  (declare (type (unsigned-byte 32) byte32))
  (if (typep byte32 'fixnum)
      (progn
        (byte16-write (ash (the fixnum byte32) -16) stream)
        (byte16-write (logand (the fixnum byte32) #xffff) stream))
    (progn (byte16-write (ash byte32 -16) stream)
           (byte16-write (logand byte32 #xffff) stream))))

(defun read-byte32 (stream)
  (let ((hi16 (byte16-read stream))
        (lo16 (byte16-read stream)))
    (declare (type (unsigned-byte 16) hi16 lo16))
    (if (< hi16 (ash most-positive-fixnum -16))
        (the (or (unsigned-byte 16) fixnum)
          (logior (the (or (unsigned-byte 16) fixnum) (ash hi16 16)) lo16))
      (the (unsigned-byte 32) 
        (+ (the (unsigned-byte 32) (ash hi16 16)) lo16)))))

> Here is my question. Are there any benchmarks around for I/O done in
> CL vs. the usual foes (i.e. C/C++)? I believe this would be another
> valuable piece of information regarding CL implementations.

Not that I know of, though I agree that this is a gap which needs filling.

	Doug