Asm gains the file I/O surface Python and C already had, unlocking 9/9 cells of the portal producer×consumer matrix (previously 6/9). asm: - (load "path") — mmaps file, swaps input source, loops scheme_read+eval, restores on exit. Nestable. Uses SYS_LSEEK + SYS_MUNMAP. - Output ports: (open-output-file), (close-port), (port?). Encoded as SPECIAL values ≥ 1000 (fd = (val>>3) − PORT_SPECIAL_BASE), no tag-bit expansion needed. - (display), (write), (newline) accept optional port arg; printer writes via output_fd global, swapped by port-aware builtins. - (write-file path content) / (file->string path) — bytes in/out. c, py: (write-file) / (file->string) added for parity. tests: 131 asm (up 23), 189 functional (up 8, shared py+c), tests/portal-cross-test.sh exercises 3×3 save×load matrix.
139 lines
6.4 KiB
Text
139 lines
6.4 KiB
Text
;;; portal-formats.lsp — Benchmark three portal formats
|
|
;;;
|
|
;;; 1. S-expression (portable — any implementation can read)
|
|
;;; 2. JSON (Python + C)
|
|
;;; 3. Binary heap dump (assembly only, fastest)
|
|
;;;
|
|
;;; Run in Python: python3 uncommonlisp.py --fast tests/portal-formats.lsp
|
|
;;; Run in C: ./c/uncommonlisp tests/portal-formats.lsp
|
|
;;; Asm: asm/uncommonlisp < tests/portal-cross-load.lsp (no `load` builtin)
|
|
;;;
|
|
;;; ── Observed results (2026-04-16, same laptop) ─────────────────
|
|
;;;
|
|
;;; | save time | load time | file size
|
|
;;; Python S-exp | ~0.55 ms | ~0.38 ms | 310 B
|
|
;;; Python JSON | ~3.07 ms | (n/a) | 25,855 B
|
|
;;; C S-exp | ~0.17 ms | ~0.03 ms | 310 B
|
|
;;; Asm binary | ~0.08 ms | ~0.08 ms | ~10 KB
|
|
;;;
|
|
;;; Insight: S-expression is ~80x smaller than JSON for the same
|
|
;;; state, and every implementation already has a parser for it —
|
|
;;; because the language IS the interchange format.
|
|
;;;
|
|
;;; Cross-impl verified: Python→C, C→Python, Python→asm, C→asm
|
|
;;; (see tests/portal-cross-save.lsp + portal-cross-load.lsp)
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Setup: create some state to serialize
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
(define my-int 42)
|
|
(define my-list (list 1 2 3 4 5 6 7 8 9 10))
|
|
(define my-string "hello world")
|
|
(define my-nested (list (list 1 2) (list 3 4) (list 5 6)))
|
|
(define my-alist (list (cons 'a 1) (cons 'b 2) (cons 'c 3)))
|
|
|
|
(define (my-fib n)
|
|
(let loop ((a 0) (b 1) (i 0))
|
|
(if (= i n) a (loop b (+ a b) (+ i 1)))))
|
|
|
|
(define my-result (my-fib 30))
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Format 1: S-expression (portable)
|
|
;;; Write values as Scheme (define ...) forms. Any impl can (load) it.
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
(define (write-sexp-portal filename)
|
|
(let ((port (open-output-file filename)))
|
|
;; Header
|
|
(display ";; uncommonlisp portable state (S-expression format)" port)
|
|
(newline port)
|
|
(display ";; resume: (load \"" port)
|
|
(display filename port)
|
|
(display "\")" port)
|
|
(newline port)
|
|
(newline port)
|
|
;; Values
|
|
(display "(define my-int " port) (write my-int port) (display ")" port) (newline port)
|
|
(display "(define my-list '" port) (write my-list port) (display ")" port) (newline port)
|
|
(display "(define my-string " port) (write my-string port) (display ")" port) (newline port)
|
|
(display "(define my-nested '" port) (write my-nested port) (display ")" port) (newline port)
|
|
(display "(define my-alist (list" port)
|
|
(for-each (lambda (p)
|
|
(display " (cons '" port) (write (car p) port)
|
|
(display " " port) (write (cdr p) port) (display ")" port))
|
|
my-alist)
|
|
(display "))" port) (newline port)
|
|
(display "(define my-result " port) (write my-result port) (display ")" port) (newline port)
|
|
(close-port port)))
|
|
|
|
(define t0 (current-time))
|
|
(write-sexp-portal "/tmp/state.sexp")
|
|
(define t1 (current-time))
|
|
|
|
(display "S-expression save: ")
|
|
(display (* (- t1 t0) 1000))
|
|
(display "ms") (newline)
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Verify: load it back
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
;; Clear the values
|
|
(set! my-int 0)
|
|
(set! my-list '())
|
|
(set! my-string "")
|
|
(set! my-result 0)
|
|
|
|
(define t2 (current-time))
|
|
(load "/tmp/state.sexp")
|
|
(define t3 (current-time))
|
|
|
|
(display "S-expression load: ")
|
|
(display (* (- t3 t2) 1000))
|
|
(display "ms") (newline)
|
|
|
|
;; Verify
|
|
(display "Verify: my-int=") (display my-int)
|
|
(display " my-result=") (display my-result)
|
|
(display " my-list=") (display my-list)
|
|
(newline)
|
|
|
|
(if (and (= my-int 42) (= my-result 832040) (= (length my-list) 10))
|
|
(display "PASS: S-expression portal round-trip")
|
|
(display "FAIL: S-expression portal round-trip"))
|
|
(newline)
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Format 2: JSON (via portal-save builtin if available)
|
|
;;; Python binds `portal-save` directly. C binds `portal-save!` as a
|
|
;;; VM-level checkpoint (deferred save). We probe by procedure? so this
|
|
;;; stays portable and never invokes an unbound callable.
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
(define json-saver
|
|
(guard (e (#t #f))
|
|
(if (procedure? portal-save) portal-save #f)))
|
|
|
|
(if json-saver
|
|
(let ((t4 (current-time)))
|
|
(json-saver "/tmp/state.json")
|
|
(let ((t5 (current-time)))
|
|
(display "JSON save: ")
|
|
(display (* (- t5 t4) 1000))
|
|
(display "ms") (newline)))
|
|
(display "JSON save: skipped (no direct portal-save in this impl)\n"))
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Summary
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
(newline)
|
|
(display "Portal format comparison:") (newline)
|
|
(display " S-expression: portable — every impl parses it") (newline)
|
|
(display " JSON: Python + C, graph-aware, preserves sharing") (newline)
|
|
(display " Binary: asm only — raw heap dump, same-arch restore") (newline)
|
|
(newline)
|
|
(display "Files left at /tmp/state.sexp and /tmp/state.json for size comparison.") (newline)
|
|
(display "Run `wc -c /tmp/state.*` in shell to compare sizes.") (newline)
|