Our data files take time to load. And, even though we often load them asynchronously or optimize their reading in other ways, there's only so much we can speed up without changing the format. And, while SQLite or some other opaque format may be nice and performant, impelementation-native FASLs may be even faster and need no foreign interfaces. The only challenge being: how do we put data into FASL and read it back, portably?
Solution
One way I'm thinking about is
- serializing the data into s-expressions, and then writing
(defvar *data* ...s-expressions) into some file,
- calling
compile-file on this newly written file,
- and, when we need our data, loading it to get
*data* variable set to the right value. Which is probably faster and more overflow-resistant than read-ing the whole file.
- Bonus point: we can somehow leverage ASDF to compile and cache all of our data files in some smart way, much like with Nyxt configuration systems generated on the fly.
Alternative solutions
I'm pretty sure there are other ways, like using compiled function bodies or eval-ing the data fetched from FASL, and I'm conscious that my approach is not the most performant, but it skips at least some some roadbumps.
The perfect approach would be to compile the Lisp data itself (and not its cl-prevalence-serialized representation) into FASLs.
Additional context
I've done some of this compilation magic in Sade, and here is the relevant bit:
(let* ((in (uiop:merge-pathnames* (uiop:parse-native-namestring (second args))
(uiop:getcwd)))
(out (uiop:merge-pathnames*
(or (uiop:parse-native-namestring (third args)) (pathname-name in))
(uiop:getcwd))))
#+ecl
(uiop:with-temporary-file
(:stream f :pathname p :type "lisp" :keep t)
(print (with-open-file (i in) (bf i)) f)
(print '(si:quit) f)
:close-stream
(compile-file p :system-p t)
(c:build-program
out :lisp-files (list (uiop:merge-pathnames*
(concatenate 'string (pathname-name p) ".o") p))))
#-ecl
(let ((tmpname (gensym "TMP")))
(bf-compile-from-file tmpname in)
(setf uiop:*image-entry-point* (lambda () (funcall tmpname)))
(uiop:dump-image out :executable t)))
Those, however, are concerned with making executable files, and not FASLs, but they still set the tone for how we might approach the problem.
Our data files take time to load. And, even though we often load them asynchronously or optimize their reading in other ways, there's only so much we can speed up without changing the format. And, while SQLite or some other opaque format may be nice and performant, impelementation-native FASLs may be even faster and need no foreign interfaces. The only challenge being: how do we put data into FASL and read it back, portably?
Solution
One way I'm thinking about is
(defvar *data* ...s-expressions)into some file,compile-fileon this newly written file,*data*variable set to the right value. Which is probably faster and more overflow-resistant thanread-ing the whole file.Alternative solutions
I'm pretty sure there are other ways, like using compiled function bodies or
eval-ing the data fetched from FASL, and I'm conscious that my approach is not the most performant, but it skips at least some some roadbumps.The perfect approach would be to compile the Lisp data itself (and not its
cl-prevalence-serialized representation) into FASLs.Additional context
I've done some of this compilation magic in Sade, and here is the relevant bit:
Those, however, are concerned with making executable files, and not FASLs, but they still set the tone for how we might approach the problem.