Skip to content

Decompression: unsafe-performance-experimental: assume FSE states are within their table - #502

Open
jesup wants to merge 15 commits into
trifectatechfoundation:mainfrom
jesup:decompress-fse-state-hint
Open

jesup wants to merge 15 commits into
trifectatechfoundation:mainfrom
jesup:decompress-fse-state-hint

Conversation

@jesup

@jesup jesup commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Builds on PR #501 (decompress-codegen-parity); only the last commit is new.

Under the existing unsafe-performance-experimental feature, tell LLVM that an FSE state is always below its table size, which removes the bounds check from the state lookup. This holds for every table this crate builds (decoded, RLE or default); unreachable_unchecked is checked in debug builds, and decompression was fuzzed with the feature on.

The hint is applied on x86_64 only. On Apple silicon, removing the bounds check made sequence decoding 4-8% slower (mostly the offset table lookup), so other targets keep the checked lookup.

The Long decoder (used for freshly loaded dictionaries) keeps the checked lookup: with the hint there, register allocation in its loop got worse (about a third more stack loads and stores executed) and dictionary decoding was 3-5% slower without a profile or with a cold one. ZSTD_decodeSequence takes a const parameter for this.

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. Feature enabled, against #PR2:

#PR2 this PR
web resources, no PGO 2.93 2.86 (-2.6%)
web resources, cold PGO 2.99 2.91 (-2.9%)
web resources, trained PGO 2.90 2.82 (-2.9%)
silesia level 19, no PGO 3.31 3.18 (-4%)
silesia level 19, cold PGO 3.35 3.26 (-2.7%)
small responses, cold PGO 6.06 5.99 (-1.2%)
dictionary, cold PGO 7.68 7.69 (0%)
dictionary, trained PGO 7.63 7.53 (-1.3%)

Upstream benchmark (non-PGO, rs/C): silesia-small 0.992 -> 0.980, re2-exhaustive 1.012 -> 1.006, zip64support 0.983 -> 0.971.

On an Apple M-series Mac (wall-clock, same harness), the whole stack through this PR decodes about 9% faster than main without PGO, 13% faster with cold PGO and 8% faster with trained PGO (about 10%, 15% and 9% for streaming non-dictionary inputs). Against C zstd 1.5.8 built with clang -O3 and LTO, that is about 8% slower without PGO, level with cold PGO and 11% slower with trained PGO. With the pgo feature from the first PR enabled, the stack is 21% faster than main with cold PGO and 14% faster with trained PGO, which is about 9% faster than C and 4% slower than C. This PR itself changes nothing on aarch64, since the hint is x86_64 only.

Tests pass with and without the feature, in release and debug.

@codecov

codecov Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
lib/decompress/zstd_decompress_block.rs 87.50% 9 Missing ⚠️
Flag Coverage Δ
test-aarch64-apple-darwin 51.29% <94.15%> (+0.56%) ⬆️
test-aarch64-unknown-linux-gnu 49.35% <94.15%> (+0.04%) ⬆️
test-i686-unknown-linux-gnu 49.15% <93.63%> (+0.03%) ⬆️
test-x86_64-unknown-linux-gnu 51.01% <93.63%> (+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/entropy_common.rs 94.52% <ø> (+0.28%) ⬆️
lib/common/fse_decompress.rs 95.17% <100.00%> (+0.53%) ⬆️
lib/common/zstd_internal.rs 87.01% <100.00%> (+0.34%) ⬆️
lib/compress/huf_compress.rs 90.94% <ø> (ø)
lib/compress/zstd_compress_sequences.rs 91.01% <ø> (ø)
lib/decompress/huf_decompress.rs 84.03% <100.00%> (+0.64%) ⬆️
lib/decompress/zstd_decompress.rs 67.22% <100.00%> (+11.61%) ⬆️
lib/decompress/zstd_decompress_block.rs 76.85% <87.50%> (-0.23%) ⬇️

... and 1 file 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-fse-state-hint branch from d2b7d1e to 305b7a6 Compare November 6, 2026 16:52
jesup added 15 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.
Matches C's BMI2_TARGET_ATTRIBUTE; without lzcnt, `ilog2` in the table
builders compiles to bsr plus a fixup.
The mask sits on the state dependency chain; the bounds check does not, and
never fails because every table is built by this crate.
…able

Not in the Long decoder, where it made register allocation in the loop worse.
@jesup
jesup force-pushed the decompress-fse-state-hint branch from 305b7a6 to 5c794a5 Compare November 10, 2026 17:08
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