DEV Community

contour
contour

Posted on Edited on

LZ encoder 25 -> 575 MB/s: what broke when I took the lz4 trick into a format with min distance 64

ACEAPEX has a fast profile (levels 11-18). Fixed 32-bit tokens, min match 16, min distance 64, independent 1 MiB blocks. The point of it is decode: no length branches, 64 literal and 96 match bytes copied per token.

Decode was fine. Encode was not. Level 11 did silesia at 25.8 MB/s. zxc -1 does the same file at ~1 GB/s. In lzbench that is the first thing people see.

So I took the obvious thing: lz4 fast. One slot per hash, step grows on misses. Result 65.16 % at 406 MB/s. Fast, but ratio fell apart. Below is where exactly it broke and what was built on top.

where the lz4 trick breaks on this format

  1. min distance 64. On runs and short periods the slot holds a position 1..63 bytes back. Format can not encode it. Match lost.
  2. min match 16. A probe lands in the middle of a match. lz4 starts the match at the probe, so the first part goes out as literals.
  3. insert only probed positions. Table is too sparse, long matches are missed between probes.

what was built

  • 8-byte hash key. With min match 16 a 4-byte key only fills the table with junk candidates.
  • Probe every A bytes, insert every B-th position, A and B coprime (A=3, B=2). Any match long enough is hit at some probe.
  • Backward extension, 8 bytes per step, last <8 bytes in one compare. Recovers the real start of the match.
  • Candidate closer than 64: lift distance to the first legal multiple, d *= ceil(64/d), verify. Runs and short periods compress without any special mode.
  • 8..15 byte hit at the probe: extend backward, accept if it reaches 16.
  • Inside a match insert every 8th position and end-2.
  • Matches 65..128 bytes written as tokens of <=64 bytes (the decoder fast path copies 64).

Rule I got out of it, falsifiable by a grid (K in {4,5,6,8} x strides, it held):

P(find match of length L) ~ (L-K+1)/(A*B) * survival of the table entry
work per literal byte     ~ c_probe/A + c_insert/B
Enter fullscreen mode Exit fullscreen mode

You can not buy more speed with strides after that point. What is left is memory and archive assembly.

numbers

lzbench, Zen 5 (GCP C4D, EPYC 9B45), silesia.tar, one core, median of 5:

codec ratio compress MB/s decompress MB/s
aceapex_fast -1 (2.5.0) 61.38 % 575 15950
zxc 0.15.0 -1 61.67 % 852 11027
memcpy 28285

Decode +44.6 % over zxc -1 at a slightly better ratio. Compress is still behind, 575 vs 852. That is the next wall.

second thing that broke: lzbench builds without -march

First run on Zen 5 in lzbench gave only +24.7 % decode, our own harness +44 %. Reason: lzbench compiles codecs without -march, so the 32-byte copies became two SSE2 moves. Fix:

__attribute__((target_clones("avx2", "default")))
static uint32_t axf_dec_block(...);
Enter fullscreen mode Exit fullscreen mode

x86-64 Linux, glibc, GCC 6+ / Clang 14+. Picked at load time. After it: +44.6 % in lzbench too. If your codec is fast only with -march=native, it is not fast in the benchmark table.

reproduce

aceapex 2.5.0 with aceapex_fast is in lzbench master since 29.10.2026 (PR #355):

git clone https://github.com/inikep/lzbench && cd lzbench && make -j
./lzbench -ezxc,1/aceapex_fast,1 -t8,8 -o1c4 silesia.tar
Enter fullscreen mode Exit fullscreen mode

Codec source: https://github.com/yasha1971-coder/aceapex

Release 2.5.0, DOI 10.5281/zenodo.23748572. Logs of every number above are in the repo (results/).

Next: compress speed. Encoder already does 13.3 instructions per input byte (was 14.2), bytes identical. Will post the Zen 5 number when it is measured, not before.

Top comments (0)