A faster, deterministic, maintained fork of Zopfli

June 2026 · QVXLabs · github.com/QVXLabs/zopfli

In October 2025, Google archived zopfli. The repository is now read-only: no more releases, no more fixes, no more maintainer. That's a reasonable end for Google — the algorithm is twelve years old and does exactly what it set out to do — but zopfli is still quietly load-bearing. It's the best general-purpose DEFLATE encoder there is, and it's still in the hot path of PNG optimizers (zopflipng, advpng, parts of oxipng's pipeline) and static-asset gzip precompression on a lot of build systems.

So the canonical implementation just lost its maintainer, and the thing it does is still worth doing. This is a fork that picks it up — and along the way makes it meaningfully faster, leaner, and (newly) deterministic.

What zopfli is, and why it's slow

Zopfli produces standard DEFLATE/zlib/gzip output — anything that reads a .gz or a zlib stream reads zopfli's output unchanged. The trick is purely in the encoder: it runs an iterated optimal-parse search, repeatedly re-costing the LZ77 parse against an entropy model and converging on a smaller encoding than a normal deflater would find. It buys a few percent over gzip -9, and it pays for it in CPU — often 100× slower. That tradeoff is the whole point: you run zopfli once, at build time, on assets you'll serve millions of times.

Because you only pay the cost once, "make zopfli faster" has always been the less interesting half of the problem. The more interesting half turned out to be the cost model.

What this fork changes, and how

Four changes, all preserving the encoder's search behavior (same parse decisions, just computed differently or more cheaply):

An integer fixed-point cost model. Upstream computes symbol costs — the entropy, the -log2 — in double, through function-pointer cost callbacks. This fork replaces that with an integer fixed-point model (IntLog2Fixed, with precision derived from a per-block cost shift). That single change cascades into the other three and into the determinism story below.

A longest-match cache fused into the cost dynamic-program. The optimal-parse search re-walks the same positions every iteration. By carrying match distances forward and fusing the longest-match cache directly into the cost DP, the final path walk never re-searches the hash, and iterations after the first become nearly free — the cost stops scaling roughly linearly with iteration count the way upstream's does.

A variable-length match cache that lets iterations ≥2 skip the per-byte hash rebuild entirely.

Trimmed hot data structures. 16-bit hash tables right-sized to the used bucket count, 32-bit LZ77 cumulative histograms, two precomputed per-symbol arrays dropped in favor of cheap on-the-fly recomputation, and scratch buffers reused instead of re-allocated per call.

The numbers

Measured on an Intel Core i9-8950HK (Coffee Lake), release -O3 -DNDEBUG, at a matched 15 iterations (--i15 for both — upstream's default), min of 2 runs. Text is concatenated C/C++ source extended by repetition; binary is incompressible random data.

InputStock zopfli This forkSpeedup
256 KB text0.90 s0.47 s1.9×
1 MB text2.71 s1.40 s1.9×
3 MB text8.13 s4.22 s1.9×
10 MB text27.16 s13.98 s1.9×
256 KB binary0.36 s0.12 s2.9×
1 MB binary1.58 s0.63 s2.5×
3 MB binary5.69 s1.61 s3.5×
10 MB binary16.46 s5.06 s3.3×

Peak resident memory (/usr/bin/time -l, 3 MB input, --i200):

InputStock zopfli This forkSaved
Text (~3 MB)23.1 MB15.2 MB−34%
Incompressible binary (~3 MB)122.5 MB75.4 MB−38%

The benchmark corpus and method are documented in the repo so you can reproduce these locally.

The determinism angle (the part I'd actually lead with)

Because the cost model is now pure integer arithmetic, the encoder contains no floating point at all. That has two consequences worth more than the speedup:

  1. Bit-identical output on every CPU, compiler, and FP mode. Upstream computes costs with libm's log(), whose last bits vary with the platform, the compiler, x87-vs-SSE, FMA contraction, and -ffast-math. Those variations can change which parse the search picks, and therefore the output bytes. This fork's output is reproducible across all of that — verified by building at -O0, -O3, and -O3 -ffast-math -ffp-contract=fast and checking the compressed md5 is identical. If you care about reproducible builds, this is the only zopfli that gives you a stable artifact.
  2. No libm, no FPU. The core links no -lm and emits no floating-point code, so it's smaller and runs without soft-float emulation on FPU-less microcontrollers.

Honest caveats

Output is not byte-identical to upstream zopfli. It's a valid DEFLATE/zlib/gzip stream any decoder reads, and it's reproducible — but it defines a new canonical encoding. If you diff against golden files made by Google's zopfli, they won't match; regenerate them. Compression ratio is within ~0.02% of upstream (sometimes better).

Getting it

It's a drop-in replacement: same ZopfliCompress API surface, same CLI flags, standard output. Two migration notes — the include path is now <zopfli/zopfli.h>, and don't compare bytes against upstream-produced fixtures.

Zopfli on GitHub

It's actively maintained — issues and PRs welcome.