Skip to content

perf(decode + encode-greedy): close 3-5× donor gap on negative-level decompress; share SIMD primitives + add dedicated greedy strategy #178

Description

@polaz

Status (re-measured)

The original 3-5× negative-level decode gap is closed to 1.3-1.4×; the missing dedicated greedy strategy has landed. This issue now tracks the residual negative-level decode parity work only.

Current numbers (i9, decodecorpus-z000033, level_-1_fast):

Stream source Rust decode C decode Gap (was)
c_stream 907 µs (1.05 GiB/s) 647 µs (1.47 GiB/s) 1.40× (was 3.69×)
rust_stream 1.18 ms (824 MiB/s) 895 µs (1.06 GiB/s) 1.32× (was 5.35×)

Landed since the original report

  • Greedy strategy: dedicated StrategyTag::Greedy (L5) with lazy_depth = 0 on the Row finder — the reference's own greedy shape (its greedy/lazy share the row-search template with depth 0). The L4 dfast outlier was separately closed by the donor greedy double-fast port.

  • XXH64 frame checksum: gated on the frame's checksum flag (−61% on flag-off frames) and hashed per-block while cache-hot on the direct path; no longer a post-decode cold walk.

  • Per-kernel decoders: four ISA tiers (Scalar/BMI2/AVX2/VBMI2) with monolithic per-tier sequence loops, BMI2 bit-reader specialization, per-tier match-copy chains.

  • SIMD wildcopy / overshooting copies: donor-shape overshoot-tolerant copies with bounded tails per kernel.

  • Per-sequence questions settled once per block (perf(decoding): settle per-sequence questions once per block; fix the overlapping-match copier #517): literal slack checked per block, the nonzero offset asserted, the output cursor carried in locals, one bit-reader refill per sequence, and the overlapping-match copier moved to block moves. −12.5% instructions on z000033 at level -1 (runner1, bench profile), time gap 1.49× → ~1.37×; low-entropy frames decode 2.4× faster than the reference.

Residual (~1.37×), and why lever-pulling is paused

HUF does not apply: negative levels emit raw literals, and neither profile has any Huffman decode in it. The whole gap is the sequence loop, one monolith against one monolith with matching profile shape, and it sits in frames with many short sequences.

After #517 every further lever returned under 2% or measured worse: outlining any arm of the loop, freeing live values, fusing the two output-room checks (fewer instructions, slower), handing the FSE advance its entry. Moving code inside the AVX2 loop is at a local optimum for this compiler, and the residual is spill traffic (about 18 extra memory touches per sequence). The kill-switch below is therefore met: this issue reopens only with a structural idea for the loop, not with another pointwise edit.

Kill-switch criteria

Stop pulling individual levers when both stream sources sit within ~1.1× of the reference on the negative-level corpus, or when a lever returns <2% twice in a row (record the negative result and move on).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1-highHigh priority — core functionalityenhancementNew feature or requestperformancePerformance optimization

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions