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.