fast-io
2022-11-07
Alternative I/O mechanism to a stream or vector
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]])))
Multi-byte and Endianness
Fast-io provides a host of read and write functions for big- and little-endian reads. See the Dictionary below.
Static Vectors
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.
Streams
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.
Dictionary
Octets
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 LEN
Make an octet-vector of lengthLEN
.octets-from SEQUENCE
Make an octet-vector from the contents ofSEQUENCE
.
Buffers
-
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 intoVECTOR
If both:vector
and:stream
is provided, the input buffer reads from the vector first, followed by the stream. -
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 forBUFFER
. -
with-fast-input (BUFFER VECTOR &optional STREAM (OFFSET 0)) &body body
Create an input buffer calledBUFFER
, optionally reading fromVECTOR
, followed by reading fromSTREAM
. IfOFFSET
is specified, start reading from this position inVECTOR
. -
with-fast-output (BUFFER &optional OUTPUT) &body BODY
Create an output buffer namedBUFFER
, optionally writing to the streamOUTPUT
. This will automaticallyFINISH-OUTPUT-BUFFER
onBUFFER
. Thus thewith-fast-output
form evaluates to the completed octet-vector.
Reading and Writing
fast-read-byte INPUT-BUFFER &optional (EOF-ERROR-P t) EOF-VALUE
Read a byte fromINPUT-BUFFER
. IfEOF-ERROR-P
ist
, reading past the end-of-file will signalCL:END-OF-FILE
. Otherwise, it will returnEOF-VALUE
instead.fast-write-byte BYTE OUTPUT-BUFFER
Write a byte toOUTPUT-BUFFER
.fast-read-sequence SEQUENCE INPUT-BUFFER &optional (START 0) END
Read fromINPUT-BUFFER
intoSEQUENCE
. Values will be written starting at positionSTART
and, ifEND
is specified, ending atEND
. 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) END
WriteSEQUENCE
toOUTPUT-BUFFER
, starting at positionSTART
inSEQUENCE
. IfEND
is specified, values will be written untilEND
; 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.