Skip to content

snapshot: experimental warm-start heap-image (env-gated, draft)#23

Draft
littledivy wants to merge 1 commit into
denoland:14.9-test-win-fixfrom
littledivy:feat/heap-image-warm-restore
Draft

snapshot: experimental warm-start heap-image (env-gated, draft)#23
littledivy wants to merge 1 commit into
denoland:14.9-test-win-fixfrom
littledivy:feat/heap-image-warm-restore

Conversation

@littledivy

@littledivy littledivy commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

Experimental warm-start heap-image for the snapshot deserializer: on a warm
run, restore the pre-context isolate heap by adopting whole mmap-able pages and a
verbatim off-heap string-table blob, instead of running the per-object snapshot
deserializer. Skips the object-graph walk and the string-table re-insertion.

All behavior is off by default and env-gated — zero effect on a normal build
unless DENO_HEAP_IMAGE_BAKE / DENO_HEAP_IMAGE_RESTORE are set.

This is posted as a draft for discussion, not for merge as-is (see Caveats).

How it works

  1. Bake (DENO_HEAP_IMAGE_BAKE=<path>): after a normal startup deser, dump
    every adopted heap page (paged + large-object spaces, tagged by
    AllocationSpace, global cage-offset order) plus the roots table, builtin
    table, startup/shared object caches, external-pointer slot table (by id+kind),
    and the off-heap string table as a raw blob.
  2. Restore (DENO_HEAP_IMAGE_RESTORE=<path>): allocate each page via
    MemoryAllocator::AllocatePage/AllocateLargePage at the next sequential cage
    offset (matches the bake since old/trusted space is empty), bulk-memcpy the
    used area, AddPage + Free the tail. Re-resolve external pointers by id.
    Restore the string table by memcpy of the baked Data blob (compressed
    element slots round-trip because pages land at identical cage offsets).
  3. Pinned hash seed (DENO_HEAP_IMAGE_HASH_SEED=<n>, read in
    HashSeed::InitializeRoots): makes the baked cached hashes valid on restore,
    so no rehash pass is needed -- this is most of the speed win.

Soundness relies on the post-deser boot heap being position-independent under
pointer compression (cage-relative 32-bit slots), so a whole-page image adopted
at identical cage offsets is valid without per-object relocation -- the same
property ART boot.art / HotSpot CDS archived heaps depend on.

Measured (arm64, release, deno console.log hello)

Internal phase (DENO_STARTUP_PHASES):

  • create_isolate phase: ~1.84ms -> ~1.23ms
  • JsRuntime::new_inner total: ~4.27ms -> ~3.34ms (~0.9ms)

End-to-end wall-clock (warm, n=100, vs bun 1.3.5 for reference):

runtime min (ms) median (ms)
deno baseline ~9.95 ~10.5
deno heap-image ~9.55 ~10.2
bun ~8.51 ~9.3

~0.4ms faster than baseline end-to-end; closes the deno-vs-bun gap from ~1.4ms
to ~1.0ms. Most of the ~0.9ms internal win is absorbed by fixed process/exec/
teardown cost shared with bun.

Caveats (why draft, not merge)

  • Security tradeoff: the win requires a pinned hash seed, which disables
    per-isolate HashDoS randomization. A shippable version needs a design here
    (e.g. a per-install random-but-fixed seed baked into the image), not a global
    constant.
  • Only the pre-context (isolate) phase is replayed; context deser still runs
    normally.
  • Includes pre-existing snapshot diagnostics (DENO_HEAP_DUMP,
    DENO_SNAPSHOT_HISTO, DENO_RESTORE_BENCH, DENO_HEAP_IMAGE_VERBOSE).
  • Base branch is 14.9-test-win-fix (what rusty_v8 v149.4.0 currently pins);
    retarget as appropriate.

Env knobs

var effect
DENO_HEAP_IMAGE_BAKE=<path> write the image after a normal startup deser
DENO_HEAP_IMAGE_RESTORE=<path> warm-restore from the image
DENO_HEAP_IMAGE_HASH_SEED=<n> pin the hash seed (bake + restore must match)
DENO_HEAP_IMAGE_VERBOSE=1 per-page / restore logging

Restore the pre-context isolate heap on warm runs by adopting whole
mmap-able pages + a verbatim off-heap string-table blob instead of the
per-object deserializer. Off by default; gated on DENO_HEAP_IMAGE_BAKE/
DENO_HEAP_IMAGE_RESTORE/DENO_HEAP_IMAGE_HASH_SEED. ~0.4ms end-to-end.

Requires a pinned hash seed (disables HashDoS randomization) -- draft for
discussion, not merge as-is.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant