Skip to content

Decompression: faster dictionary decoding - #500

Open
jesup wants to merge 11 commits into
trifectatechfoundation:mainfrom
jesup:decompress-dictionaries
Open

jesup wants to merge 11 commits into
trifectatechfoundation:mainfrom
jesup:decompress-dictionaries

Conversation

@jesup

@jesup jesup commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Builds on PR #499 (decompress-pgo-robustness); only the last 3 commits are new.

  • Copy short matches into the dictionary (extDict) with ZSTD_copy16 instead of calling memmove; the over-write is covered by the same oMatchEnd <= oend_w guarantee wildcopy relies on. C still calls memmove here.
  • Use the BMI2 table builders when loading dictionary entropy.
  • Long sequence decoder (used for freshly loaded dictionaries): peel the last sequence so is_last_sequence is a constant in the loop.

Measured on Zen 3 with a harness that decodes the way Firefox does (fresh DStream per response, 16 KB input chunks, 128 KB output buffer), cycles per decompressed byte. "Cold PGO" is instrumented with a profile that never decodes zstd, which is what Firefox ships, since its PGO training has no zstd content; "trained" includes zstd decoding in the training. Dictionary cases: web resources under 60 KB, with a 110 KB trained dictionary or a 232 KB raw-content dictionary, loaded per response with ZSTD_DCtx_loadDictionary.

#PR1 this PR
trained dictionary, cold PGO 8.71 8.07 (-7%)
raw dictionary, cold PGO 8.50 7.72 (-9%)
trained dictionary, trained PGO 8.58 7.86 (-8%)
raw dictionary, trained PGO 8.14 7.64 (-6%)

Non-dictionary decoding is unchanged within noise.

On an Apple M-series Mac (wall-clock, same harness), this PR and its parent decode about 4% faster than main without PGO, 10% faster with cold PGO and 4% faster with trained PGO; dictionary inputs are 5% faster without PGO, 12% faster with cold PGO and 6% faster with trained PGO.

Each commit builds, is rustfmt-clean and passes cargo test.

@codecov

codecov Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.66197% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/decompress/zstd_decompress_block.rs 85.24% 9 Missing ⚠️
Flag Coverage Δ
test-aarch64-apple-darwin 51.28% <93.66%> (+0.54%) ⬆️
test-aarch64-unknown-linux-gnu 49.34% <93.66%> (+0.03%) ⬆️
test-i686-unknown-linux-gnu 49.15% <93.66%> (+0.02%) ⬆️
test-x86_64-unknown-linux-gnu 51.01% <93.66%> (+0.52%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
lib/common/bitstream.rs 98.35% <ø> (-0.42%) ⬇️
lib/common/fse_decompress.rs 95.17% <100.00%> (+0.53%) ⬆️
lib/common/zstd_internal.rs 86.66% <ø> (ø)
lib/decompress/huf_decompress.rs 84.03% <100.00%> (+0.64%) ⬆️
lib/decompress/zstd_decompress.rs 67.14% <100.00%> (+11.53%) ⬆️
lib/decompress/zstd_decompress_block.rs 76.85% <85.24%> (-0.23%) ⬇️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jesup
jesup force-pushed the decompress-dictionaries branch from e9deb75 to b4d0028 Compare November 6, 2026 16:52
jesup added 11 commits November 10, 2026 17:01
LLVM lowers inline thresholds for code that is cold in a PGO profile, which
leaves calls like `Writer::subslice` in the sequence loop. Firefox PGO
training does not exercise zstd, so this is the configuration it ships.
PGO builds turned the element-wise `fill` into two byte stores per entry.
LLVM vectorized the per-byte loop on x86-64 only without PGO, and not at all
on aarch64.
`ZSTD_execSequence` already guarantees the sequence fits before `oend`, so the
per-sequence `Writer` bounds check is redundant. Keep `new_seq_state` out of
line: inlining it makes the x86-64 loop allocate registers noticeably worse.

On targets other than x86_64, force it inline with the new `pgo` feature:
out of line the sequence state stays in memory, which made trained and cold
PGO builds up to 10% slower on Apple silicon. Without PGO forcing it inline
is slower, so the compiler decides. The long sequence decoder always calls
the out of line version.
The 16-byte over-copy relies on the same output slack as wildcopy.
Makes `is_last_sequence` a constant in the loop.
@jesup
jesup force-pushed the decompress-dictionaries branch from b4d0028 to a78fd94 Compare November 10, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant