Roc vs C: roc-deflate Benchmarks Within 2.6% of libdeflate on Apple Silicon
A DEFLATE compression package written entirely in the pure functional language Roc ran a median 2.6% slower than the SIMD-optimized C library libdeflate on a 2021 M1 MacBook Pro, and 8.9% slower on an AMD Ryzen 9 3900X running Ubuntu. Roc creator Richard Feldman published the benchmark on October 22, 2026, and described the result as something he "didn't expect" going in: a functional language with automatic memory management, bounds checks, and no unsafe escape hatch, staying within single-digit percent of a hand-tuned C implementation that no other language has matched.
The benchmark in one paragraph
roc-deflate, a DEFLATE package written by Niclas Ahden and used in paid production work, was benchmarked against libdeflate on the Silesia compression corpus. The two implementations were verified to produce identical outputs, and 144 benchmark cases were run across compression levels L1 through L12. Only non-streaming APIs were compared, because roc-deflate has not implemented streaming yet. Reported figures are medians across 15 runs on the M1 MacBook Pro and 7 runs on the Ryzen 3900X. The headline numbers: roc-deflate was 2.6% slower on the M1 in the median case and 8.9% slower on the Ryzen 3900X.
Is Roc faster or slower than C's libdeflate?
Roc is slower on average, but the gap is small, and roc-deflate wins on some inputs. Across the full 144-case suite, the median slowdown was 2.6% on Apple Silicon and 8.9% on x64. The article reports that on the worst-case inputs roc-deflate was 18% slower than libdeflate, while on the best-case inputs it was slightly over 18% faster. Those wins are not the product of an optimization C cannot access; the two implementations simply differ, so specific inputs land differently. Switching from libdeflate to roc-deflate could produce a speedup on some inputs, but not on average.
Representative numbers from the M1 machine at compression level L1, median across 15 runs:
- Compressing the
dickensfile: libdeflate 230.57 MB/s, roc-deflate 202.92 MB/s. - Compressing the
ncifile: libdeflate 601.03 MB/s, roc-deflate 529.49 MB/s. - Compressing the
saofile: libdeflate 160.21 MB/s, roc-deflate 145.28 MB/s.
Representative numbers from the Ryzen 3900X at L1, median across 7 runs:
- Compressing
dickens: libdeflate 191.10 MB/s, roc-deflate 182.30 MB/s. - Compressing
nci: libdeflate 387.30 MB/s, roc-deflate 428.60 MB/s. - Compressing
sao: libdeflate 157.40 MB/s, roc-deflate 138.50 MB/s.
Does a pure functional language pay a performance penalty?
Roc pays a lower performance ceiling than C, but the penalty is smaller than expected because the compiler removes the checks it can prove are unnecessary. Roc code is fully memory-safe, uses automatic reference counting, performs bounds checks on dynamically sized collections, and offers no Rust-style unsafe keyword to opt out of any of that. The article states plainly that C's performance ceiling is higher than Roc's: for any input where roc-deflate currently beats libdeflate, C code could close that gap, and the reverse is not true. roc-deflate approaches C performance only because Roc's optimization pipeline determined that many bounds checks and reference counting operations were provably safe to eliminate.
This is the first time Roc has been measured this close to a tuned C library. The article is explicit that one result does not generalize: as far as its author knows, this is the most optimization effort ever put into any Roc package, so how well the approach transfers to other codebases is a complete unknown.
What the remaining single-digit gap buys
The article argues the tradeoff is worth it by listing four guarantees that pure Roc code provides and that C, Zig, and Rust code cannot. None of these can be opted out of in the language.
Memory safety
roc-deflate has no access to the kind of unchecked memory operation that caused a Brotli remote code execution vulnerability disclosed in Node.js's October 2026 security releases (CVE-2026-111497). A package written in pure Roc cannot read or write out of bounds, at any level of the implementation, because the language provides no primitive that permits it.
No side effects
Roc's type system infers which functions are pure and which perform side effects. Pure functions are annotated with a thin arrow, as in Bool -> Bool, while effectful functions use a thick arrow, as in Bool => Bool. Since roc-deflate's public annotations use only thin arrows, the compiler verifies that the entire package is made of pure functions.
Supply-chain security
Roc has no arbitrary foreign function interface, so a pure Roc package provably cannot attempt I/O or touch arbitrary memory. The article argues that the worst a malicious actor can accomplish through a compromised pure Roc package is returning a wrong value, crashing, or wasting system resources. Roc packages also do not support build scripts, which was the mechanism used in the August 2026 compromise of the Rust arrayref package.
Portability and determinism
Roc pure functions are designed to return the same values for the same arguments on every target the code is compiled to, not just on the same machine. They may crash or fail to terminate, but that behavior is also expected to be deterministic across targets. The only expected cross-system difference is one system running out of memory mid-execution.
The article frames the choice in reverse: would you give up guaranteed memory safety, guaranteed absence of side effects, guaranteed cross-target determinism, and the security benefit of having no exploitable build scripts, in exchange for an average speedup in the single digits? It notes the answer legitimately varies by use case, since Roc's own compiler wants maximum hardware performance.
Roc-specific optimizations that closed the gap
Most of the gains came from general optimization techniques, and the article recommends Casey Muratori's Performance-Aware Programming Series as the source the author paid for and learned from. Four techniques are Roc-specific.
Avoid unnecessary sharing
Roc's most effective optimization is opportunistic mutation, which Koka calls "Functional but In-Place" (FBIP). From a performance standpoint, the critical rule is to avoid unnecessary sharing so that values stay unique and therefore eligible for in-place mutation.
Hoist bounds checks out of loops
Roc does not let programmers skip bounds checks, but the compiler can delete them when it can prove they would pass. Bounds checks are most expensive inside loops, so adding an explicit check before the loop, such as returning early when the collection length is less than or equal to the highest index used, can let the optimizer safely remove costlier checks inside the loop.
Build lookup tables at compile time
Roc's compile-time evaluation can assemble lookup tables at compile time when nothing depends on runtime inputs. Because their lengths are then known at compile time, code such as Ok(entry) = lookup_table.get(5) becomes an exhaustive pattern match the compiler knows is valid, avoiding the runtime bounds checks a non-constant table would need.
Consider wrapping arithmetic
In Roc, a + b desugars to a.plus(b). For U64, U64.plus includes an overflow check while U64.plus_wrap does not. When the compiler can prove no overflow occurs, both compile to the same instructions; when it cannot, choosing plus_wrap can be faster, at the cost of the safer default.
Background: Lean 4's lean-zip beat miniz_oxide, not libdeflate
The benchmark was motivated by an earlier write-up titled "Why Lean is Faster Than Rust," which reported that lean-zip, a DEFLATE library written in the Lean 4 functional programming language and proof assistant, outperformed the Rust package miniz_oxide. The article quoted that result directly:
lean-zipbeats every single compression levelminiz_oxideprovides, i.e. we're capable of providing the same compression at higher throughput. At its fastest setting we're 15% quicker and the output is 8.8% smaller.
The same write-up conceded that lean-zip could not match libdeflate:
lean-zipis certainly not the best here:libdeflateunsurprisingly blows it out of the water (ununsurprisingly because this is a very carefully tuned implementation using architecture-specific SIMD, that we can't touch in Lean).
Roc entered the picture as a functional language that is not a theorem prover and that places runtime performance first: its tagline is "Fast, Friendly, Functional," and the official performance page argues that fast compilation matters as much as fast execution. Roc is not mature; it was weeks away from its first numbered release, 0.1.0, at the time of writing, and its package ecosystem has had almost no optimization work done on it.
What Hacker News commenters disputed
The most substantive challenge concerned compression ratios rather than throughput. NohatCoder wrote:
Uh, so the program is almost as fast as libdeflate at compressing at a given level. But the post makes absolutely no attempt at establishing that a given level compresses as well as the corresponding level in libdeflate. In the linked post there is a graph detailing that libdeflate does indeed compress quite a lot better than the original lean-zip at the same level. Without data on compression levels it is hard to make any useful conclusions.
Another commenter argued the Lean comparison baseline was too weak. VorpalWay wrote:
Miniz_oxide is an intentionally simple implementation. It would be more fair to compare it to the newer zlib-rs, which even beats zlib-ng (fastest implementation in C).
VorpalWay linked the zlib-rs crate and the Trifecta Tech Foundation write-up "zlib-rs is faster than C" as evidence that Rust already matches or beats the fastest C implementation, which would change the significance of the Roc result.
jeffbee questioned the premise that libdeflate is a meaningful performance reference at all:
This seems to raise the question: is libdeflate fast? My overall impression of libdeflate is that it produces a similar output size to zstd but takes 40 times longer to do it. I realize these are different algorithms of course, but they aren't that different, both being in the Huffman line of the LZ family. So why is libdeflate considered a performance benchmark?
jandrewrogers offered a structural explanation for why functional languages tend to lag on performance-critical workloads:
Much of the difference in optimizability is that C/C++ allows you to do fairly extreme copy elision. This is important because memory bandwidth tends to be a major performance bottleneck. C/C++ code can trivially reach into some remote bit of state and mutate it directly. The compiler rarely objects, no matter how ugly the access method or pattern. It is up to the programmer to make sure the invariants hold and the compiler can't always see through it. These elided copies add up, though it usually has a maintainability and safety cost. It is related to why functional languages tend to be mediocre for supercomputing in practice despite having nice properties for massive parallelism in theory.
andai raised an inconsistency between the article's framing of Roc as immature and the claim that a consultant has used it in production for years:
It then links to a video where a consultant mentions using in production (for several years already?), which confused me. How can you justify using immature technologies for client work? Or is it an extra benefit when it breaks and you have to fix it?
johnny22 objected to the framing that a library's download count is evidence of its performance:
saying it "beats a crate that has been downloaded X times" is not saying anything other than that is popular. It'd be nice to see slightly more detail there.
ReDress argued that single-digit gaps are not acceptable in performance-critical systems:
I'd also like to note that performance is the ULTIMATE concern in a lot of cases. If you have a server that is making 10million requests to a certain method in a minute, then you cannot afford any performance compromise. Any, at all. Rust will never replace C/C++ with regard to high performance computing.
gobdovan asked whether the Roc-specific optimizations could be exposed as a library of compiler-checked passes, reporting a similar experience in it is possible to get within roughly 10% of optimized C code in V8 with enough effort, except for SIMD, where gobdovan said V8 appears not to optimize even obvious cases.
Other comments raised adjacent points. WalterBright noted that D has a pure function annotation the compiler checks. cubefox noted that Haskell requires careful tail recursion for efficient stack usage, unlike languages with loops. giovannibonetti speculated that Roc might eventually get high-level SIMD abstractions, pointing to the data-parallel functional language Futhark. amelius suggested the long-term ideal is writing FP code while an AI translates it to faster code using correctness-preserving transforms. deadbabe asked whether powerful LLMs remove the reason to use anything above C. michaelteter liked Roc's combination of functional compilation and Ruby-like ergonomics. jdw64 asked which well-known programs are built with Roc and how hard its syntax is.
Where the work goes next
Feldman said he believes the low-hanging fruit in roc-deflate has been picked, but sees remaining opportunities in both roc-deflate (especially on x64 CPUs) and Roc's compiler optimization pipeline. He published the script used to set up and run the benchmark suite on Linux, noting that it pins both Roc compiler revisions and roc-deflate versions, and warned that the script was generated by Claude Opus 5.5 and has not been vetted for security, so it should be run outside a sandbox at the reader's own risk.
For his part, Feldman said he will focus on the upcoming Roc 0.1.0 release, which he described as eight years in the making.
Sources
Related
- Dispatch
- Dispatch
- Dispatch
- Dispatch
- Dispatch