Avoid transient table allocation during deserialization

perfloop/fsst · ALLOCATION HOT LOOP

https://perfloop.ai/t/oss/case_896g0ph2r0

Verdict

VERIFIED · settled 2026-09-16 · merged as axiomhq/fsst#7

What happened: The paired measurements met the required improvement.

Hypothesis

The incoming-reference check for `fsst.newTable` found exactly `Table.ReadFrom` and `Train`, so the training initializer can remain intact while the deserialization call site is specialized. Source inspection shows `newTable` fills the 65,536-entry `shortCodes` array plus byte/hash defaults, while ReadFrom subsequently loads active symbols and calls `rebuildIndices`, which resets byte codes and hash slots and rewrites every short-code fallback before `buildDecoderTables` consumes only active symbols. The compiler check `go test -run '^$' -gcflags='-m=2' .` reported `table.go:48: &Table{} escapes to heap` because it is too large for the stack; that confirms a transient heap object exists, but no runtime profile has measured its share of end-to-end latency. The removed delta is that one heap Table and its constructor writes/copy on every ReadFrom; the remaining encBuf allocation and index rebuild are not claimed removed. A case session should benchmark repeated ReadFrom of serialized small and representative/max-symbol tables with `b.ReportAllocs`, CPU profiling, and alloc-space profiling. The confirming signal is one fewer Table-shaped allocation per call, lower B/op and allocs/op, and disappearance or reduction of `newTable` allocation/initialization in profiles, while round-trip and malformed-input tests preserve semantics.

Change to test: Give ReadFrom a deserialization-specific in-place reset instead of assigning `*newTable()`, then let its existing symbol load, `rebuildIndices`, and decoder build establish the runtime tables. Keep newTable's fully initialized literal baseline for Train, clear stale receiver state before parsing, and retain the current reset-before-read/error behavior.

Where it lives

perfloop/fsst · table.go

Evidence

ReadFrom of a small serialized trained table · 10 sample pairs

metric baseline candidate paired median change confidence range required result
ns/op 131283 43940 −65.5% (−86007) −93917 to −79385 < −6564 PASSED
B/op 180881 656 −99.6% (−180225) −180226 to −180225 < −9044 PASSED
allocs/op 12 11 −8.3% (−1) −1 to −1 < −0.6 PASSED

ReadFrom of a serialized table trained from Apache log data · 10 sample pairs

metric baseline candidate paired median change confidence range required result
ns/op 132444 43737 −67.7% (−89716) −91957 to −87114 < −6622 PASSED
B/op 182434 2208 −98.8% (−180226) −180226 to −180225 < −9122 PASSED
allocs/op 183 182 −0.5% (−1) −1 to −1 ≤ 0 PASSED

Checks: 4 of 4 passed. Verification: no defect found.

Timeline