On Nov 1, 2022, at 02:33, Alan Ruttenberg alanruttenberg@gmail.com wrote:
Here's an example of fast reading in ABCL. The implementation of certain array types use java.nio buffers, which can be directly read into via the java.nio functions.
(defun test-read (path) (let* ((f (new 'RandomAccessFile (namestring (truename path)) "r")) (channel (#"getChannel" f)) (array (make-array (* 16 1024 1024) :element-type '(unsigned-byte 8))) (buffer (get-java-field array "elements" t))) (time (loop for count = (#"read" channel buffer) until (eql count -1) sum count do (#"position" buffer 0) ))))
On my machine, for a 5G file, the SBCL code in an earlier post takes 2.4 seconds. This code takes 1.4 sec. It's fastest if I use 2M buffers - 1.1 seconds. SBCL is also marginally faster with smaller buffer sizes.
In the ABCL source code the files "SimpleArray_*.java" are the implementations of the nio buffer backed array types. See make_array.java where the specific type of underlying array is chosen. There is a global switch for array allocation choosing either direct allocation or nio-buffers, with the default being nio buffers.
(get-java-field 'java$buffers "active" t) -> #<org.armedbear.lisp.Java$Buffers$AllocationPolicy NIO {432E958E}>
Hmmm. That isn’t quite the official interface that I implemented for abcl-1.7. and am rather unsure if setting that really works at the moment. I would need to look closer at the Java implementation to be sure.
The “official” way is to use the additional keywords to CL:MAKE-ARRAY
From the fine manual § 4.10 "Extension to CL:MAKE-ARRARY"
With the NIO feature is present and indicated by the presence of :nio in CL:*FEATURES*, the implementation adds two keyword arguments to CL:MAKE-ARRAY :nio-buffer and :nio-direct.
With the :nio-buffer keyword, the user is able to pass instances of of java.nio.ByteBuffer and its subclasses for the storage of vectors and arrays specialized on the byte-vector types satisfying
(or (unsigned-byte 8) (unsigned-byte 16) (unsigned-byte 32))
As an example, the following would use the :nio-buffer as follows to create a 16 byte vector using the created byte-buffer for storage:
(let* ((length 16) (byte-buffer (java:jstatic "allocate" "java.nio.ByteBuffer" length))) (make-array length :element-type '(unsigned-byte 8) :nio-buffer byte-buffer))
:nio-buffer NIO-BUFFER
Initializes the contents of the new vector or array with the contents of NIO-BUFFER which needs to be a reference to a JAVA-OBJECT of class java.nio.ByteBuffer.
:nio-direct NIO-DIRECT-P
NIO-DIRECT-P is not NIL, constructs a java.nio.Buffer as a ``direct'' buffer. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measurable gain in program performance.
I haven't looked into the :element-type 'character case.
Maybe someone who is familiar with the ABCL stream implementation is interested in writing a fast path for read-sequence that uses the nio calls? If so, shout. Otherwise I'll keep it on my procrastinate-by-hacking-abcl list.
I have some experiments with replacing i/o with asynchronous thread pools, for which using specials isn’t going to work, hence it is preferrable to use the CL:MAKE-ARRAY implementation.
After I finish stablizing with abcl-1.9.1 (any day now…). we can start look at optimizing for various read/write patterns. Other than “make it as fast as SBCL” it would be helpful if potential users could give me some usage patterns to optimize for.
Oh, and with suitable elbow grease the ultimate plan is to add a flag to mmap(2) files as well, which should speed up fasl loads immensely.