https://github.com/rpav/fast-io.git
git clone 'https://github.com/rpav/fast-io.git'
(ql:quickload :fast-io)
Now with static-vectors support!
(deftype octet '(unsigned-byte 8))
(deftype octet-vector '(simple-array octet (*)))
Fast-io is about improving performance to octet-vectors and octet streams (though primarily the former, while wrapping the latter). Imagine we're creating messages for the network. If we try and fill an octet-vector with 50 bytes, 50000 times, here are the results (SBCL 1.0.57):
| vector-push-extend: | flexi-streams: | fast-io: | |
|---|---|---|---|
| Time: | 0.767s | 2.545s | 0.090s |
| Bytes consed: | 104,778,352 | 274,452,768 | 18,373,904 |
(See t/benchmarks.lisp for the exact code used.)
It should be surprising that it takes a nontrivial effort to achieve relatively decent performance to octet-vectors, but probably isn't. However, fast-io provides a relatively straightforward interface for reading and writing either a stream or a vector:
;;; Write a byte or sequence, optionally to a stream:
(with-fast-output (buffer [STREAM | :vector | :static])
(fast-write-byte BYTE buffer))
(with-fast-output (buffer [STREAM | :vector | :static])
(fast-write-sequence OCTET-VECTOR buffer [START [END]]))
;;; Read from a vector or stream:
(with-fast-input (buffer VECTOR [STREAM])
(fast-read-byte buffer))
(with-fast-input (buffer VECTOR [STREAM])
(let ((vec (make-octet-vector N)))
(fast-read-sequence vec buffer [START [END]])))
Fast-io provides a host of read and write functions for big- and little-endian reads. See the Dictionary below.
You may now specify :static instead of a stream to
WITH-OUTPUT-BUFFER. This returns an octet-vector created with
static-vectors,
which means that passing the buffered data directly to a foreign
function is now that much more efficient:
(let ((data (with-fast-output (buffer :static)
(buffer-some-data buffer))))
(foreign-send (static-vectors:static-vector-pointer data))
(static-vectors:free-static-vector data))
Note that the restriction for manually freeing the result remains. This avoids multiple inefficient (i.e., byte-by-byte) copies to foreign memory.
Obviously, the above API isn't built around Lisp streams, or even
gray-streams. However, fast-io provides a small wrapper using
trivial-gray-streams, and supports {WRITE,READ}-SEQUENCE:
(let ((stream (make-instance 'fast-io:fast-output-stream)))
(write-sequence (fast-io:octets-from '(1 2 3 4)) stream))
Both fast-input-stream and fast-output-stream support backing a
stream, much like using the plain fast-io buffers. However, using the
gray-streams interface is a 3-4x as slow as using the buffers alone.
Simple benchmarks show the gray-streams interface writing 1M 50-byte
vectors in about 1.7s, whereas simply using buffers is about 0.8s.
Consing remains similar between the two.
Most functions operate on or require octet-vectors, i.e.,
(deftype octet () '(unsigned-byte 8))
(deftype octet-vector '(simple-array octet (*)))
Which is exactly what is defined and exported from fast-io. Also:
make-octet-vector LENLEN.octets-from SEQUENCESEQUENCE.make-input-buffer &key VECTOR STREAM POS
Create an input buffer for use with input functions. :vector specifies the vector to be read from. :stream specifies the stream to read from. :pos specifies the offset to start reading into VECTOR.
make-output-buffer &key OUTPUT
Create an output buffer for use with output functions. :output specifies an output stream. If :output :static is specified, and static-vectors is supported, output will be to a static-vector.
finish-output-buffer BUFFER
Finish the output and return the complete octet-vector.
buffer-position BUFFER
Return the current read/write position for BUFFER.
with-fast-input (BUFFER VECTOR &optional STREAM (OFFSET 0)) &body body
Create an input buffer called BUFFER, optionally reading from VECTOR, followed by reading from STREAM. If OFFSET is specified, start reading from this position in VECTOR.
with-fast-output (BUFFER &optional OUTPUT) &body BODY
Create an output buffer named BUFFER, optionally writing to the stream OUTPUT. This will automatically FINISH-OUTPUT-BUFFER on BUFFER. Thus the with-fast-output form evaluates to the completed octet-vector.
fast-read-byte INPUT-BUFFER &optional (EOF-ERROR-P t) EOF-VALUEINPUT-BUFFER. If EOF-ERROR-P is t, reading past the end-of-file will signal CL:END-OF-FILE. Otherwise, it will return EOF-VALUE instead.fast-write-byte BYTE OUTPUT-BUFFEROUTPUT-BUFFER.fast-read-sequence SEQUENCE INPUT-BUFFER &optional (START 0) ENDINPUT-BUFFER into SEQUENCE. Values will be written starting at position START and, if END is specified, ending at END. Otherwise values will be written until the length of the sequence, or until the input is exhausted.fast-write-sequence SEQUENCE OUTPUT-BUFFER &optional (START 0) ENDSEQUENCE to OUTPUT-BUFFER, starting at position START in SEQUENCE. If END is specified, values will be written until END; otherwise, values will be written for the length of the sequence.For multi-byte reads and writes requiring endianness, fast-io provides functions in the following forms:
write[u]{8,16,32,64,128}{-be,-le}: E.g., (write32-be VALUE BUFFER) will write the specified 32-bit value to the specified buffer with a big-endian layout. Likewise, (writeu16-le VALUE BUFFER) will write an unsigned 16-bit value in little-endian layout.read[u]{8,16,32,64,128}{-be,-le}: Similarly, (read64-le BUFFER) will read a 64-bit value from the buffer with little-endian layout.