Umbrella / tracking issue: re-architect the native DEFLATE decoder along libdeflate's lines. Records what libdeflate actually does (correcting #2653's premise) and links the per-phase work, split by the lean4#14053 dependency.
Background: why #2653 didn't pan out, and what libdeflate actually does
#2653 assumed libdeflate decodes "1-2 symbols per table lookup". Both prototypes built on that premise were net-negative (closed on #2653): a lazy runtime second-lookup was −1.7%/−2.9% (canterbury/dickens), and an eager precomputed wide 2-symbol table was −31% to −53%, dominated by per-block table-build cost.
Reading libdeflate's source (lib/deflate_decompress.c, lib/decompress_template.h) shows the premise was wrong. libdeflate decodes one symbol per table lookup; its speed (multiple GB/s vs our ~143 MB/s) comes from a different, composable set of techniques:
- Bulk word refill — a single unaligned 8-byte load OR'd into the bit buffer (
REFILL_BITS_BRANCHLESS). We load one byte at a time in a ~7-iteration loop.
- Multiple symbols per refill — the fastloop refills once (~56 bits) then decodes up to 3 literals back-to-back, no refill between them. We refill after every symbol.
- Packed single-word entry — one
u32 per entry (literal/length-base + codeword length + extra-bit count + type flags); literal fast path is a single high-bit test. We use two arrays plus separate length-base/extra lookups.
- Canonical O(num_syms + 2^bits) build, no Huffman tree — counting sort + table-doubling fill, with subtables (array lookups) for long codes. We build a
HuffTree per block, walk it per slot to build the table, and tree-walk long codes.
- Wider root table —
LITLEN_TABLEBITS = 11 vs our fastBits = 9.
What our bottleneck actually is (corrected after the #2669 attempt)
libdeflate's per-symbol cost is a few register ops, so amortizing the refill frequency (technique 2) is a big deal for them. Ours is not: an implementation attempt at technique 2 (#2669) regressed on every variant, because our per-symbol cost is dominated by table reads + ByteArray.push/RC + well-founded-recursion argument threading, which batching doesn't reduce — and without packed entries the per-literal "continue the batch?" test costs the same multi-read+compare that sank #2653's lazy prototype. So technique 2 is not the safe first win; it depends on technique 3 and is marginal even then.
The levers that attack our cost structure are the ones that cut per-symbol reads, per-refill cost, and per-block build: techniques 1, 3, 4.
Phases — split by the lean4#14053 dependency, measure-first at each gate
Priority, available now (no lean4#14053):
Deferred / conditional:
Blocked on lean4#14053 (wide ByteArray load accessors; open):
Recommended order: #2670 (packed entry) first — it's a standalone per-symbol read reduction and unblocks everything else — then measure; #2630 (bulk refill) whenever lean4#14053 is available; then decide whether the remaining gap justifies #2671's heavy build/proof. #2669 is conditional on #2670 and low priority.
References: libdeflate lib/deflate_decompress.c (build_decode_table; entry format ~L425-500) and lib/decompress_template.h (fastloop ~L337-435). Foundations landed: #2666.
Umbrella / tracking issue: re-architect the native DEFLATE decoder along libdeflate's lines. Records what libdeflate actually does (correcting #2653's premise) and links the per-phase work, split by the lean4#14053 dependency.
Background: why #2653 didn't pan out, and what libdeflate actually does
#2653 assumed libdeflate decodes "1-2 symbols per table lookup". Both prototypes built on that premise were net-negative (closed on #2653): a lazy runtime second-lookup was −1.7%/−2.9% (canterbury/dickens), and an eager precomputed wide 2-symbol table was −31% to −53%, dominated by per-block table-build cost.
Reading libdeflate's source (
lib/deflate_decompress.c,lib/decompress_template.h) shows the premise was wrong. libdeflate decodes one symbol per table lookup; its speed (multiple GB/s vs our ~143 MB/s) comes from a different, composable set of techniques:REFILL_BITS_BRANCHLESS). We load one byte at a time in a ~7-iteration loop.u32per entry (literal/length-base + codeword length + extra-bit count + type flags); literal fast path is a single high-bit test. We use two arrays plus separate length-base/extra lookups.HuffTreeper block, walk it per slot to build the table, and tree-walk long codes.LITLEN_TABLEBITS = 11vs ourfastBits = 9.What our bottleneck actually is (corrected after the #2669 attempt)
libdeflate's per-symbol cost is a few register ops, so amortizing the refill frequency (technique 2) is a big deal for them. Ours is not: an implementation attempt at technique 2 (#2669) regressed on every variant, because our per-symbol cost is dominated by table reads +
ByteArray.push/RC + well-founded-recursion argument threading, which batching doesn't reduce — and without packed entries the per-literal "continue the batch?" test costs the same multi-read+compare that sank #2653's lazy prototype. So technique 2 is not the safe first win; it depends on technique 3 and is marginal even then.The levers that attack our cost structure are the ones that cut per-symbol reads, per-refill cost, and per-block build: techniques 1, 3, 4.
Phases — split by the lean4#14053 dependency, measure-first at each gate
Priority, available now (no lean4#14053):
Deferred / conditional:
Blocked on lean4#14053 (wide ByteArray load accessors; open):
needs lean4#14053family with perf(explore, needs lean4#14053): compress hash-load — ugetUInt32LE in hash3 #2629/perf(explore): BitWriter wide-store — local usetUInt64LE + pre-sized buffer (unblocked by #2707) #2631; refill is ~13.9% of decode, wide refill measured ~1.63× → est. ~5% decode). Lands when #14053 merges and the toolchain bumps.Recommended order: #2670 (packed entry) first — it's a standalone per-symbol read reduction and unblocks everything else — then measure; #2630 (bulk refill) whenever lean4#14053 is available; then decide whether the remaining gap justifies #2671's heavy build/proof. #2669 is conditional on #2670 and low priority.
References: libdeflate
lib/deflate_decompress.c(build_decode_table; entry format ~L425-500) andlib/decompress_template.h(fastloop ~L337-435). Foundations landed: #2666.