Lean beat a billion-download Rust crate but fell well short of libdeflate. A pre-0.1.0 Roc package then got within single digits of it. Here is what those benchmarks measured and where they stop.
libdeflate is the hardest DEFLATE implementation to beat. It is written in C, tuned per architecture, and uses SIMD paths that most higher-level languages cannot express at all. So the useful question for functional programming is not whether it can beat a mainstream library. It already has. The question is how much of the remaining distance to hand-tuned C a pure functional language can close, and what that distance looks like when you measure it file by file.
Richard Feldman's write-up How close can pure FP get to C? gives a concrete answer for one workload. roc-deflate, a DEFLATE package written in Roc, ran 144 benchmarks against libdeflate on the Silesia corpus and produced identical output. It was slower on average. The median gap was 8.9% on an AMD Ryzen 9 3900X and 2.6% on a 2021 M1 MacBook Pro.
Those two numbers are the result. The rest of this article looks at how they were produced, what they support, and what they leave open, because a single-digit gap to C means different things depending on what sits behind it.
The starting point: functional code had already beaten Rust
The Roc experiment responds to an earlier result. In 'Why Lean is Faster Than Rust', a DEFLATE library called lean-zip, written in Lean 4, was benchmarked against miniz_oxide. Lean 4 is a functional programming language and a proof assistant. miniz_oxide is a Rust crate that crates.io reports at over a billion downloads. The Lean article claims that lean-zip 'beats every single compression level miniz_oxide provides'. It also reports that at its fastest setting lean-zip is 15% quicker and its output is 8.8% smaller.
That is a strong result against a widely deployed systems-language implementation. The same article is direct about where it stops. Against libdeflate, it says, lean-zip 'is certainly not the best here: libdeflate unsurprisingly blows it out of the water'. The reason it gives is that libdeflate is 'a very carefully tuned implementation using architecture-specific SIMD, that we can't touch in Lean'.
So the baseline facts are these. Pure functional code can beat a popular Rust crate on this workload. It did not get close to the best C, and the authors put that down to a capability gap, not a lack of effort.
Why libdeflate is the right opponent
Beating miniz_oxide shows that functional programming is not inherently slow at compression. It does not show how near the ceiling a functional language can get. libdeflate is a better test because it represents what a determined C author does with full control over memory layout and instruction selection. When the Lean authors say they 'can't touch' architecture-specific SIMD, they are describing a limit of the language, not of their implementation.
This changes how you read any comparison against it. A small gap to libdeflate means the functional implementation does nearly the same amount of work per byte as code that was tuned for specific CPUs. A large gap could have many causes, such as allocation, bounds checks, poor code generation, or simply missing vector instructions. You need the per-file data, not just the headline, to tell which.
It also explains why a near-tie with libdeflate is a stronger claim than a win over miniz_oxide. The Rust comparison measured functional code against a good implementation. The C comparison measures it against one of the best available.
Roc and roc-deflate: what was actually tested
Roc is a functional language that is not a theorem prover and that treats runtime performance as a primary goal. Its tagline is 'Fast, Friendly, Functional'. According to the author, 'Fast' comes first because fast execution and fast compilation have both been central goals from the start.
The author is open about Roc's maturity, and you should keep that in mind. At the time of writing, Roc was 'still weeks away from its first numbered release, 0.1.0'. Its package ecosystem had seen 'almost no optimization work'. The author's reason for expecting a competitive result was a mix of language design, longstanding optimizations, and a brand-new compiler implementation. The author does not say which of those mattered most for this workload.
The package itself was not written as a benchmark toy. Niclas Ahden wrote roc-deflate and uses it in paid production work. Feldman then spent a long stretch optimizing it. The comparison is therefore between a production package after a focused optimization effort and libdeflate.
Scope: what the comparison covers
The benchmarks have narrow boundaries, and they should be stated before reading any numbers.
- Non-streaming APIs only. roc-deflate had not implemented streaming yet, so only whole-buffer operations were compared. Streaming adds buffer management and state handling at chunk boundaries, and none of that was measured.
- Silesia corpus. This is the standard compression benchmark set. The files in the results are dickens, mozilla, mr, nci, ooffice, osdb, reymont, samba, sao, webster, x-ray and xml, a mix of text, binaries, medical imaging, database and XML data.
- Identical output. Outputs were checked to be identical to libdeflate's. This rules out the most common way compression benchmarks mislead, which is going faster by compressing less.
- Repeated runs. Each reported throughput figure is the median of 15 runs.
- Two machines. A 2021 M1 MacBook Pro, where all the optimization work was done, and an AMD Ryzen 9 3900X running Ubuntu for an x64 comparison.
The identical-output check matters most. Without it, a throughput gap between two DEFLATE implementations says very little, because each one picks its own trade-off between match-finding effort and ratio. With it, the comparison is close to like-for-like: same input bytes, same output bytes, different time spent producing them.
Reading the throughput numbers
The benchmark reports raw compression throughput in MB/s for each Silesia file, in repeated groups of twelve. The results do not label which group belongs to which machine, implementation or compression setting. So the numbers below show the spread within one group and should not be read as a configuration-by-configuration comparison.
In the first group, the highest throughput is nci at 601.03 MB/s, followed by xml at 476.83 MB/s. The lowest are x-ray at 145.93 MB/s and sao at 160.21 MB/s. Most files fall between roughly 230 and 345 MB/s: dickens 230.57, mozilla 248.31, webster 268.58, reymont 281.14, osdb 284.01, mr 312.26, samba 344.00.
The same pattern holds in every group. nci is always the fastest file and sao or x-ray is always the slowest. Absolute throughput falls from group to group. By the fifth group, nci is at 415.29 MB/s and sao at 100.60 MB/s. Across all groups, the fastest file runs at about three to four times the speed of the slowest. That is a property of the data. Highly redundant inputs compress quickly, and dense binary or image data does not.
For your own evaluation, the takeaway is that a single averaged MB/s figure hides a 3x to 4x spread that depends on the input. Benchmark on data that looks like yours.
What the gap between machines suggests
The median gap differs between platforms: 2.6% on the M1 and 8.9% on the Ryzen 3900X. The author reports both numbers and notes that all the optimization work was done on the M1. No explanation for the difference is given, so any explanation here is an inference, not a finding.
A reasonable inference is that profile-guided hand tuning tends to fit the machine it was done on. The developer profiles, changes code, and keeps whatever helps on that hardware. libdeflate, by contrast, contains architecture-specific code paths for each target. A gap that is smaller on the tuning machine and larger elsewhere fits that picture. It is still a hypothesis, and testing it would mean repeating the tuning work on x64.
In practice, 'within 10% of C' was a cross-platform result for this workload, and only one platform got the full benefit of the optimization effort. If you are deciding whether a functional implementation is fast enough, benchmark it on your deployment hardware, not on your development machine.
What the result supports
Taken together, the claims the evidence supports are fairly specific. A pure functional language, before its first numbered release, with an almost unoptimized ecosystem, ran a production DEFLATE package within a median 2.6% to 8.9% of libdeflate across 144 non-streaming benchmarks, with byte-identical output. This is a different kind of result from the Lean one. Lean beat a popular Rust crate but was clearly outrun by libdeflate. Roc stayed behind libdeflate on average but got within single digits.
What it does not support is a general ranking of languages. This is one algorithm and one corpus, on two machines, after one developer's optimization pass. Feldman did not expect to win across the board and says roc-deflate was slower on average. No claim of parity is being made.
Where this does not fit, and what to watch for
If you are deciding whether to write performance-critical code in a functional language on the strength of this result, keep these limits in view.
- Streaming is untested. Many real compression workloads are streams, such as HTTP bodies, log shipping and archive tools. Nothing here measures that path.
- SIMD remains the hard limit. The Lean authors named architecture-specific SIMD as the reason they could not compete with libdeflate. Workloads that depend heavily on hand-written vector code are where this gap is most likely to persist.
- Maturity costs. Roc was pre-0.1.0. Expect language changes, a thin ecosystem and tooling gaps. That is a real cost for production systems, whatever the benchmark says.
- Tuning does not transfer by default. The gap more than tripled moving from the tuning machine to x64. Budget for per-platform profiling.
- Correctness checks are what make the comparison valid. Copy the identical-output requirement into your own benchmarks. A faster compressor that produces different bytes is not being compared on equal terms.
The honest summary is that functional programming has moved from 'competitive with typical systems code' to 'single digits behind the best C' on at least one demanding workload. Whether that holds for yours depends on streaming, SIMD and the hardware you ship on, and you will only know by measuring it.
Share this article
Comments