Skip to content

perf(deflate,checksum): faster deflate encoder and checksums - #166

Merged
MagicalTux merged 3 commits into
masterfrom
deflate-encoder-perf
Oct 31, 2026
Merged

MagicalTux merged 3 commits into
masterfrom
deflate-encoder-perf

Conversation

@MagicalTux

Copy link
Copy Markdown
Member

Encoder speed item of #156. The deflate encoder is now 1.1–2.8× zlib's speed at levels 1/6/9 on text, binaries, repetitive and random data (before: 0.6–0.95×). Output size changes by at most +0.1% at any level 1–9, and most levels shrink (level 1 by up to 2.6%).

input level before MB/s after MB/s zlib MB/s
dict/words 1 / 6 / 9 149 / 31 / 14 264 / 43 / 16 190 / 33 / 14
Rust sources 1 / 6 / 9 142 / 56 / 18 304 / 89 / 21 223 / 70 / 18
binaries 1 / 6 / 9 88 / 34 / 13 178 / 59 / 16 123 / 38 / 14
random 6 82 116 88

Measured on Apple M5 Max, raw deflate, against system zlib 1.2.12; full table in BENCH.md.

Changes

  • Match finding:
    • multiplicative 3-byte hash
    • the insert returns the old chain head, so the search needs no rehash
    • quick candidate rejection on the 4 bytes ending at the current best length
    • 8-byte compares
    • zlib's lazy cut-off at levels 6–7
  • Huffman: an in-place length builder (Moffat–Katajainen), with package-merge used only when a code would exceed the length limit.
  • Encoding: symbols packed into a u32 and counted during the match pass; the stored fallback reads the window instead of a per-block copy; a branch-free 64-bit bit writer.
  • Checksums: lane-parallel Adler-32 (about 7.7 GB/s) and slice-by-16 CRC-32 (about 4.3 GB/s). zlib and gzip now encode at raw deflate speed.

No public API changes. All tests pass, including the Python zlib cross-checks for every strategy and flush mode.

🤖 Generated with Claude Code

MagicalTux and others added 3 commits October 31, 2026 15:45
Adler-32 now sums each 16-byte block across sixteen independent lanes
(auto-vectorised) and folds them once per NMAX chunk: ~7.7 GB/s, about
twice the old byte loop, on Apple M5. CRC-32 moves from slice-by-8 to
slice-by-16 (~4.3 GB/s, up from ~2.8). Both are cross-checked against
bytewise references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Multiplicative 3-byte hash; insert returns the old chain head, so the
  match search starts from it without rehashing.
- Chain walk rejects candidates on the four bytes ending at the current
  best length, searches only for matches longer than the pending lazy
  one, and folds NIL/stale/too-far into one distance check.
- zlib's max_lazy cut-off at levels 6-7 (good 8, lazy 32).
- Symbols packed into u32 and histograms tallied during the LZ77 pass;
  the stored fallback reads the window instead of a per-block copy.
- Moffat-Katajainen in-place Huffman lengths, falling back to
  package-merge only when a code would exceed the limit.
- Branch-free 64-bit bit writer over a pre-sized buffer; length and
  distance codes plus extra bits go out in one write.

Sizes move by at most +0.1% at any level on text, binaries, repetitive
and random input; most shrink (level 1 by up to 2.6%). The shared
crate::bits::BitWriter is now only built for deflate64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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