TLAB: direct-mapped superblock cache to fix scattered-free thrash - #123
Merged
Merged
Conversation
The free fast path cached exactly one superblock's read-only header fields, so it could normalize a pointer and account for the object without dereferencing the (cold) superblock header. That single entry thrashed whenever consecutive frees hit *different* superblocks: larson with sizes spanning several classes (e.g. 100-200) keeps ~14 superblocks live at once, so every free missed the cache and reloaded ~7 cold header fields plus recomputed the multiplicative-inverse modulo. Replace it with a 16-way direct-mapped set indexed by superblock number ((addr / SuperblockSize) & 15). Each 64-byte entry (padded to a power of two so indexing is a shift, not a multiply) caches the same fields, so the whole bounded working set of a scattered free stream lives in the set at once and those frees hit instead of thrashing. The hit path is unchanged in shape -- one indexed load + predicted-taken compare, then the same combined remote/threshold branch and inline foreign-free -- via a factored, always-inlined applyFree(). Measured (interleaved A/B, macOS/arm64 M1 Max and Linux/x86-64): larson 100-200: +15% (macOS), +8% (Linux) [72% -> 80-83% of mimalloc] larson 7-8: within noise (tied) linux-scalability, threadtest: tied 16 ways is the sweet spot: 8 thrashes against the ~14 live superblocks (no win), 32 is identical to 16 for double the footprint (1KB/thread). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The TLAB free fast path cached exactly one superblock's read-only header fields, so it could normalize a pointer and account for the freed object without touching the (cold) superblock header. That single entry thrashed whenever consecutive frees hit different superblocks:
larsonwith sizes spanning several classes (e.g. 100–200) keeps ~14 superblocks live at once, so every free missed the cache and reloaded ~7 cold header fields + recomputed the multiplicative-inverse modulo. This was the documented larson-100-200 gap (~65% of mimalloc).This replaces the single entry with a 16-way direct-mapped set indexed by superblock number (
(addr / SuperblockSize) & 15). Each 64-byte entry (padded to a power of two so indexing is a shift, not a multiply) caches the same fields, so the whole bounded working set of a scattered free stream lives in the set at once and those frees hit instead of thrashing. Hit-path shape is unchanged — one indexed load + predicted-taken compare, then the same combined remote/threshold branch and inline foreign-free — factored into an always-inlinedapplyFree().Only
tlab.hchanges (+126/−117).Results (interleaved A/B; higher = better unless noted)
Ways sweep (interleaved, Linux): 8-way ties baseline (thrashes against the ~14 live superblocks), 16-way = +8%, 32-way identical to 16 for double the per-thread footprint. 16 is the sweet spot (16×64B = 1 KB/thread).
Caveats / validation notes
larson 7-8effect swings within ±5% run-to-run (interleaved samples ranged 0.94–1.04× vs baseline); the cleanest fully-idle run showed it tied. No robust regression, but the CIqos-larsongate is the arbiter — I could not get a clean absolute hoard-vs-mimalloc gate ratio locally because the benchmark box's mimalloc numbers had 12–38% spread (the gate is report-only on high-variance runners for exactly this reason).Dead-ends ruled out (so they aren't re-attempted)
🤖 Generated with Claude Code