From ed90adc451f3a797e0b83c0212812a53bbbccde5 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 19 Apr 2026 12:18:56 -0400 Subject: [PATCH] lumbda-www cached: in-memory hash-table cache, 1.64x PDF throughput MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit examples/http-static-server-cached.lsp — same HTTP server but every preloaded URL's full HTTP/1.0 response (headers + body) is composed once at startup and stored in a hash-table, so the per-request handler is a single hash-table-ref/default. No file->string, no string-append, no MIME lookup in the hot path. Config knobs: *docroot* filesystem root (default "www") *cache-max* soft cap on cached entries (default 100) *preload-paths* list of URL paths to pre-fetch at startup Preload list for lumbda.com: "/", "/style.css", "/whitepaper.pdf", "/robots.txt", "/404.html" — anything not in the list returns the cached 404 response (no disk hit). Cache lives pre-snapshot so heap-restore never reclaims it; RSS stays at the cache size forever. Race vs caddy on this laptop (2000 small / 200 large, concurrency 8): small req/s PDF req/s PDF MiB/s peak RSS lumbda-www uncached 690 195 495 15.5 MB lumbda-www cached 686 319 811 7.2 MB caddy file-server 688 478 1,217 38.9 MB Cache wins on the PDF: 1.64x faster than uncached, RSS DROPS from 15.5 MB to 7.2 MB because the cached path allocates nothing per request (all allocation happened pre-snapshot). On small files already-hot paths mean the cache is a wash — 690 vs 686 is noise. Caddy still wins 1.5x on the PDF via sendfile(2) zero-copy; we allocate the 2.67 MB response once at startup and tcp-send it. Closing the gap further would take a sendfile asm primitive — separate project. For a minimal static site serving its own whitepaper, the cached 27 KB asm binary is viable: 319 req/s and 811 MiB/s with 5x less memory than caddy. tests/bench-www-race.sh updated to run all three side-by-side (uncached + cached + caddy) at three ports. Cached server's port is patched via sed at the entry point so the two lumbda variants don't collide. PDF byte-integrity checked on all three paths. --- examples/http-static-server-cached.lsp | 157 +++++++++++++++++++++++++ tests/bench-www-race.sh | 74 ++++++++---- 2 files changed, 206 insertions(+), 25 deletions(-) create mode 100644 examples/http-static-server-cached.lsp diff --git a/examples/http-static-server-cached.lsp b/examples/http-static-server-cached.lsp new file mode 100644 index 0000000..415c5b9 --- /dev/null +++ b/examples/http-static-server-cached.lsp @@ -0,0 +1,157 @@ +;;; http-static-server-cached.lsp — lumbda.com's docroot served +;;; from an in-memory response cache. Every preloaded path's full +;;; HTTP/1.0 response (headers + body) is composed once at startup +;;; and stored in a hash-table, so the per-request handler is a +;;; single hash-table-ref. No file->string, no string-append, no +;;; MIME lookup in the hot path. +;;; +;;; Configuration: +;;; *docroot* filesystem root containing the files +;;; *cache-max* soft cap on cached entries (default 100) +;;; *preload-paths* list of URL paths to pre-fetch at startup +;;; +;;; Add a file: drop it in *docroot*, add its URL path to +;;; *preload-paths*, restart. A server restart is deliberate +;;; — cache lives forever across requests. +;;; +;;; Runs in any tier (Python/C/asm/asm-gc); target deployment is +;;; asm-gc (27 KB stripped, bounded RSS). + +(define *port* 8080) +(define *docroot* "www") +(define *cache-max* 100) +(define *max-requests* 1000000) + +(define *crlf* "\r\n") +(define *crlf-crlf* "\r\n\r\n") + +;;; ── HTTP / MIME helpers (startup-time only) ───────────────── + +(define (ends-with? s suffix) + (let ((ns (string-length s)) (nf (string-length suffix))) + (if (< ns nf) #f + (string=? suffix (substring s (- ns nf) ns))))) + +(define (mime-of path) + (cond + ((ends-with? path ".html") "text/html; charset=utf-8") + ((ends-with? path ".css") "text/css; charset=utf-8") + ((ends-with? path ".pdf") "application/pdf") + ((ends-with? path ".txt") "text/plain; charset=utf-8") + ((ends-with? path ".js") "application/javascript") + ((ends-with? path ".svg") "image/svg+xml") + ((ends-with? path ".png") "image/png") + ((ends-with? path ".jpg") "image/jpeg") + ((ends-with? path ".ico") "image/x-icon") + (else "application/octet-stream"))) + +(define (http-response status ctype body) + (string-append + "HTTP/1.0 " status *crlf* + "Content-Type: " ctype *crlf* + "Content-Length: " (number->string (string-length body)) *crlf* + "Connection: close" *crlf-crlf* + body)) + +(define (url-to-fs url-path) + (cond + ((string=? url-path "/") (string-append *docroot* "/index.html")) + (else (string-append *docroot* url-path)))) + +;;; ── Cache (hash-table: url-path → full HTTP response) ─────── + +(define *cache* (make-hash-table)) + +(define (cache-count) (hash-table-size *cache*)) + +(define (cache-add! url-path) + (if (>= (cache-count) *cache-max*) + (begin + (display "cache-add!: skipped (cap reached): ") (display url-path) (newline) + 'capped) + (let ((fs-path (url-to-fs url-path))) + (let ((body (file->string fs-path))) + (cond + (body + (hash-table-set! *cache* url-path + (http-response "200 OK" (mime-of fs-path) body)) + 'cached) + (else + (display "cache-add!: missing file: ") (display fs-path) (newline) + 'missing)))))) + +;;; Paths to preload. Anything not in this list returns 404. +(define *preload-paths* + (list "/" + "/style.css" + "/whitepaper.pdf" + "/robots.txt" + "/404.html")) + +;;; ── 404 / 403 (built once) ────────────────────────────────── + +(define *resp-404* + (let ((body (file->string (string-append *docroot* "/404.html")))) + (if body + (http-response "404 Not Found" "text/html; charset=utf-8" body) + (http-response "404 Not Found" "text/plain" "not found\n")))) + +(define *resp-403* + (http-response "403 Forbidden" "text/plain" "forbidden\n")) + +;;; Preload. +(define (preload-all paths) + (cond + ((null? paths) 'done) + (else + (cache-add! (car paths)) + (preload-all (cdr paths))))) + +(preload-all *preload-paths*) + +;;; ── Per-request handler ──────────────────────────────────── + +(define SPACE 32) +(define (char-at s i) (char->integer (string-ref s i))) + +(define (second-token s) + (let ((len (string-length s))) + (let loop1 ((i 0)) + (cond + ((= i len) "/") + ((= (char-at s i) SPACE) + (let loop2 ((j (+ i 1))) + (cond + ((= j len) (substring s (+ i 1) len)) + ((= (char-at s j) SPACE) (substring s (+ i 1) j)) + (else (loop2 (+ j 1)))))) + (else (loop1 (+ i 1))))))) + +(define (handle-request req) + (let ((path (second-token req))) + (hash-table-ref/default *cache* path *resp-404*))) + +;;; ── Main loop ─────────────────────────────────────────────── + +(define server (tcp-listen *port*)) + +(define (server-loop n snap) + (if (>= n *max-requests*) + (begin + (display "request cap reached, exiting\n") + (tcp-close server)) + (begin + (let ((client (tcp-accept server))) + (let ((req (tcp-recv client 4096))) + (if (and req (> (string-length req) 0)) + (tcp-send client (handle-request req)) + #f)) + (tcp-close client)) + (heap-restore snap) + (server-loop (+ n 1) snap)))) + +(display "lumbda-www cached on :") (display *port*) +(display " serving ") (display *docroot*) +(display " (") (display (cache-count)) (display "/") (display *cache-max*) +(display " cached)") (newline) +(server-loop 0 (heap-snapshot)) diff --git a/tests/bench-www-race.sh b/tests/bench-www-race.sh index c1a1104..e313891 100755 --- a/tests/bench-www-race.sh +++ b/tests/bench-www-race.sh @@ -10,6 +10,7 @@ SMALL_N=${SMALL_N:-2000} LARGE_N=${LARGE_N:-200} CONCURRENCY=${CONCURRENCY:-8} LUMBDA_PORT=${LUMBDA_PORT:-8080} +LUMBDA_CACHED_PORT=${LUMBDA_CACHED_PORT:-8082} CADDY_PORT=${CADDY_PORT:-8081} CADDY_BIN=${CADDY_BIN:-/home/fox/git/make_post_sell/caddy} @@ -25,38 +26,56 @@ trap cleanup EXIT INT TERM make -s -C asm all printf "\n═══════════════════════════════════════════════════════\n" -printf "asm-gc lumbda-www vs caddy — same docroot, same workload\n" +printf "asm-gc lumbda-www (uncached + cached) vs caddy\n" printf " %s small (/), %s large (/whitepaper.pdf 2.67 MiB)\n" "$SMALL_N" "$LARGE_N" printf " concurrency %s\n" "$CONCURRENCY" printf "═══════════════════════════════════════════════════════\n" -# ── start both servers ── +# ── start three servers ── + +# 1. lumbda-www uncached (reads file per request) ./asm/lumbda-gc < examples/http-static-server.lsp >/dev/null 2>&1 & LUMBDA_PID=$! SPAWNED+=("$LUMBDA_PID") +# 2. lumbda-www cached (in-memory hash-table of pre-built responses). +# Patch *port* to avoid colliding with the uncached server. +sed "s|(define \*port\* 8080)|(define *port* $LUMBDA_CACHED_PORT)|" \ + examples/http-static-server-cached.lsp \ + | ./asm/lumbda-gc >/dev/null 2>&1 & +LUMBDA_CACHED_PID=$! +SPAWNED+=("$LUMBDA_CACHED_PID") + +# 3. caddy file-server "$CADDY_BIN" file-server --root www --listen ":$CADDY_PORT" >/dev/null 2>&1 & CADDY_PID=$! SPAWNED+=("$CADDY_PID") -# Wait for both to be ready -for _ in $(seq 1 30); do +# Wait for all three to be ready. +for _ in $(seq 1 40); do a=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$LUMBDA_PORT/" 2>/dev/null || echo 0) - b=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$CADDY_PORT/" 2>/dev/null || echo 0) - [ "$a" = "200" ] && [ "$b" = "200" ] && break + b=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$LUMBDA_CACHED_PORT/" 2>/dev/null || echo 0) + c=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$CADDY_PORT/" 2>/dev/null || echo 0) + [ "$a" = "200" ] && [ "$b" = "200" ] && [ "$c" = "200" ] && break sleep 0.1 done -kill -0 "$LUMBDA_PID" 2>/dev/null || { echo "lumbda-www failed to start"; exit 1; } -kill -0 "$CADDY_PID" 2>/dev/null || { echo "caddy failed to start"; exit 1; } +kill -0 "$LUMBDA_PID" 2>/dev/null || { echo "lumbda-www (uncached) failed to start"; exit 1; } +kill -0 "$LUMBDA_CACHED_PID" 2>/dev/null || { echo "lumbda-www (cached) failed to start"; exit 1; } +kill -0 "$CADDY_PID" 2>/dev/null || { echo "caddy failed to start"; exit 1; } -# ── Byte-integrity check (both ways) ── -curl -s "http://localhost:$LUMBDA_PORT/whitepaper.pdf" -o /tmp/a-lumbda.pdf -curl -s "http://localhost:$CADDY_PORT/whitepaper.pdf" -o /tmp/a-caddy.pdf -echo -n " pdf-integrity (lumbda): " -cmp /tmp/a-lumbda.pdf whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1 && echo "byte-identical" || echo "DIFFERS" -echo -n " pdf-integrity (caddy): " -cmp /tmp/a-caddy.pdf whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1 && echo "byte-identical" || echo "DIFFERS" -rm -f /tmp/a-lumbda.pdf /tmp/a-caddy.pdf +# ── Byte-integrity checks ── +curl -s "http://localhost:$LUMBDA_PORT/whitepaper.pdf" -o /tmp/a-lumbda.pdf +curl -s "http://localhost:$LUMBDA_CACHED_PORT/whitepaper.pdf" -o /tmp/a-cached.pdf +curl -s "http://localhost:$CADDY_PORT/whitepaper.pdf" -o /tmp/a-caddy.pdf +for f in /tmp/a-lumbda.pdf /tmp/a-cached.pdf /tmp/a-caddy.pdf; do + label=$(basename "$f" .pdf | sed 's/^a-//') + if cmp "$f" whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1; then + printf " pdf-integrity (%-7s): byte-identical\n" "$label" + else + printf " pdf-integrity (%-7s): DIFFERS\n" "$label" + fi +done +rm -f /tmp/a-lumbda.pdf /tmp/a-cached.pdf /tmp/a-caddy.pdf # ── benchmark one (pid, port, label, N, path) ── bench_one() { @@ -91,18 +110,23 @@ print(f'{$n * 2669058 / (1024*1024) / max(t, 1e-6):.0f}') echo echo "── small (/) ──" -bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www (asm-gc)" "$SMALL_N" "/" -bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$SMALL_N" "/" +bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www uncached" "$SMALL_N" "/" +bench_one "$LUMBDA_CACHED_PID" "$LUMBDA_CACHED_PORT" "lumbda-www cached" "$SMALL_N" "/" +bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$SMALL_N" "/" echo echo "── large (/whitepaper.pdf, 2.67 MiB) ──" -bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www (asm-gc)" "$LARGE_N" "/whitepaper.pdf" -bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$LARGE_N" "/whitepaper.pdf" +bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www uncached" "$LARGE_N" "/whitepaper.pdf" +bench_one "$LUMBDA_CACHED_PID" "$LUMBDA_CACHED_PORT" "lumbda-www cached" "$LARGE_N" "/whitepaper.pdf" +bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$LARGE_N" "/whitepaper.pdf" echo -echo "── binary sizes ──" -printf " lumbda-gc %10s bytes stripped\n" "$(du -b asm/lumbda-gc | cut -f1)" -printf " caddy %10s bytes (Go, v2.5.1, static)\n" "$(du -b "$CADDY_BIN" | cut -f1)" +echo "── binary sizes (stripped) ──" +# Strip to temp for fair comparison — asm binaries ship stripped in prod. +cp asm/lumbda-gc /tmp/lumbda-gc-s && strip /tmp/lumbda-gc-s +printf " lumbda-gc %10s bytes\n" "$(du -b /tmp/lumbda-gc-s | cut -f1)" +printf " caddy %10s bytes (Go, v2.5.1, already stripped)\n" "$(du -b "$CADDY_BIN" | cut -f1)" +rm -f /tmp/lumbda-gc-s -kill -9 "$LUMBDA_PID" "$CADDY_PID" 2>/dev/null -wait "$LUMBDA_PID" "$CADDY_PID" 2>/dev/null || true +kill -9 "$LUMBDA_PID" "$LUMBDA_CACHED_PID" "$CADDY_PID" 2>/dev/null +wait "$LUMBDA_PID" "$LUMBDA_CACHED_PID" "$CADDY_PID" 2>/dev/null || true