Skip to content

Binary format for data files: FASLs! #5

Description

@aartaka

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions