Motivation¶
The Problem¶
Haskell programs pay a startup tax that makes them unsuitable for latency-sensitive use cases:
- RTS initialization (~1.6ms with
-V0): GC setup, capability/thread init, block allocator, stats infrastructure, interval timer - Application initialization (variable, often 100ms–1s+): config parsing, data loading, index building, grammar compilation
For CLI tools that should feel instant (like fzf at ~0.5ms), or microservices handling cold starts, this overhead is disqualifying. A fuzzy finder that takes 618ms to build its index on every invocation cannot compete with one that starts in under 1ms.
The fundamental insight: most of this initialization produces immutable data structures that are identical across runs. Computing them fresh each time is pure waste.
Why GHC Needs This More Than Others¶
Haskell has properties that make heap serialization both more necessary and more natural than in other languages:
More necessary¶
-
Lazy evaluation creates complex heap graphs: a lazy
Mapwith 1M entries is a tree of thunks, constructors, and shared subexpressions. In a strict language, the same data would be a flat array of key-value pairs. The heap graph is the natural representation of Haskell data — there's no "simpler" format to fall back to. -
No escape from the GC: unlike Rust (ownership) or C (manual memory), every Haskell value lives on the GC heap. You cannot allocate "outside" the runtime.
ForeignPtrexists but only for opaque byte blobs, not structured Haskell data. -
Binary serialization is painful: Haskell's
Binary/Serializetypeclasses require defining instances for every type, don't handle sharing, and produce formats that must be parsed back into heap objects — O(n) allocation and pointer chasing.
More natural¶
-
Purity: most Haskell data is immutable. A frozen
Map Text Intis referentially transparent — it will be the same next run. No hidden mutation, no stale caches, no invalidation concerns. -
Info tables describe layout: every GHC closure carries a pointer to its info table, which encodes the closure type, number of pointer/non-pointer fields, and entry code. This is the same metadata the GC uses to traverse the heap — ghc-fastboot reuses it for serialization. No schema definition needed.
-
Thunks preserve laziness: unlike compact regions (which require normal form), ghc-fastboot can freeze thunks. A frozen
Mapwhere most entries are unevaluated thunks takes near-zero space per unevaluated entry and evaluates on demand after thaw. This is impossible in any other serialization approach.