Replace the nightly simd feature with runtime-dispatched SIMD, and vectorize more of the encoder - #262
Open
Mnwa wants to merge 6 commits into
Open
Replace the nightly simd feature with runtime-dispatched SIMD, and vectorize more of the encoder#262Mnwa wants to merge 6 commits into
Mnwa wants to merge 6 commits into
Conversation
danielrh
reviewed
Aug 18, 2026
| edition = "2015" | ||
| rust-version = "1.59.0" | ||
| # Bounded by `fearless_simd`, which the encoder's vectorized paths use unconditionally. | ||
| rust-version = "1.89.0" |
Collaborator
There was a problem hiding this comment.
is there an older version of fearless_simd we could use with an older rust, or could we only have it select this rust version if the fearless_simd feature is selected?
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.
Summary
The
simdfeature requires nightly (#![feature(portable_simd)]), so the vectorized pathsship to nobody on stable, and
src/enc/compat.rsexists only to hand-write a scalar twin forevery vector op — two implementations to keep in sync.
This replaces that with
fearless_simd: runtimeinstruction-set detection and dispatch, on stable Rust, with no
unsafe. Oneimplementation covers every target — AVX2 hardware takes AVX2, aarch64 takes NEON, everything
else falls back to scalar, from the same binary. With that in place it vectorizes the encoder
stages that profiling shows actually dominate, and drops a redundant histogram copy in
clustering.
Compressed output does not change. Every optimization is bit-identical to the scalar code
it replaces.
What changed
SIMD backend
simdfeature and the nightly gate; vectorization is now unconditional on stable.src/enc/compat.rs(392 lines of scalar shim).s16/v8/s8becomeMem16x16/Mem256f/Mem256iinenc::vectorization— plainCopy + Defaultarrays, so they still live in the allocator-backed slices; arithmetic happenson
fearless_simdregisters.detect_level()caches the detected level in aOnceLock, anddispatch!regions arehoisted to the outermost loop that can own them (a whole Zopfli block, a whole match-finder
walk) so detection isn't paid per comparison.
no_stdstill builds, viafearless_simd'slibmfeature.Newly vectorized paths
hash_to_binary_tree.rs).hq.rs):UpdateNodes,FindAllMatchesH10,ZopfliIterateand theper-position sweep.
static_dict.rs): short matches stay scalar and never touchthe CPU feature set; only long matches pay for detection and switch to 32-byte compares.
block_splitter.rs):FindBlockskeeps a per-lane running winner and reducesonce per row. Ties still resolve to the lowest histogram id.
bit_cost.rs): eight buckets per compare, per-bucket work only for populatedones — histograms are mostly empty, which is what the wide compare is for.
Histogram clustering no longer materializes sums
BrotliHistogramBitCostDistanceand the clustering pair queue used to clone a histogram, runHistogramAddHistogram, then cost the result — and between them they are nearly everypopulation-cost call in the encoder.
BrotliPopulationCostOfSumcosts the histogram that sumwould produce, letting the cost walk do the adding. Same values in the same order, so the
float accumulation is unchanged; it just skips a copy and an add pass.
Optional profiling — an optional
hotpathlayer behindhotpath/hotpath-cpu/hotpath-alloc. Every call site is acfg_attr, so a default buildneither links it nor pays for it. Happy to drop this or split it into its own PR.
Verification
Encoder output diffed against the merge-base over 60 (corpus, quality) pairs —
alice29.txt,asyoulik.txt,random_org_10k.bin,monkey× qualities 0–11 including 9.5,9.5x, 9.5y, all
-w22. Every pair byte-identical. Existing 100-test suite passes unchanged.Repeated on a second instruction set: the same 60 pairs cross-compiled to
x86_64-apple-darwinand run under Rosetta (SSE4.2). Also byte-identical, and identical to the aarch64 output — so
the NEON path, the SSE4.2 path and the original scalar code all agree byte for byte.
AVX2 and AVX-512 are not verified. I have no x86 hardware and Rosetta caps at SSE4.2. Same
dispatch!, same source, so I expect them to agree — but I haven't checked. If CI can run thediff on an AVX2 runner before merging, that closes the gap; I'm glad to add that job.
Performance
3.1 MB varied corpus, Apple M5 Pro, release + LTO, best of three:
NEON numbers, one machine, one corpus; no x86 measurements at all. Indication of scale, not a
promise.
Breaking changes — your call
fearless_simd(edition 2024 needs 1.85 independently).This is the big one. If it's a blocker, the alternative is a default-on feature with a scalar
fallback — which brings back the two-implementations problem
compat.rshad. I didn't wantto reintroduce that without asking.
unsafeblocks insideunsafe fnmust now be explicit. Isolated in its own commit.simdfeature removed, not deprecated —--features simdbecomes an error. I can leaveit as an accepted no-op instead.
fearless_simd ~0.6.no_std-capable, nounsafeinthe surface this crate uses, but it's a 0.x crate and that's a real call to make.
sha2~0.10→~0.11(only reachable via the existingvalidationfeature).