Skip to content(if available)orjump to list(if available)

Zlib-rs is faster than C

Zlib-rs is faster than C

491 comments

·March 16, 2025

brianpane

I contributed a number of performance patches to this release of zlib-rs. This was my first time doing perf work on a Rust project, so here are some things I learned: Even in a project that uses `unsafe` for SIMD and internal buffers, Rust still provided guardrails that made it easier to iterate on optimizations. Abstraction boundaries helped here: a common idiom in the codebase is to cast a raw buffer to a Rust slice for processing, to enable more compile-time checking of lifetimes and array bounds. The compiler pleasantly surprised me by doing optimizations I thought I’d have to do myself, such as optimizing away bounds checks for array accesses that could be proven correct at compile time. It also inlined functions aggressively, which enabled it to do common subexpression elimination across functions. Many times, I had an idea for a micro-optimization, but when I looked at the generated assembly I found the compiler had already done it. Some of the performance improvements came from better cache locality. I had to use C-style structure declarations in one place to force fields that were commonly used together to inhabit the same cache line. For the rare cases where this is needed, it was helpful that Rust enabled it. SIMD code is arch-specific and requires unsafe APIs. Hopefully this will get better in the future. Memory-safety in the language was a piece of the project’s overall solution for shipping correct code. Test coverage and auditing were two other critical pieces.

Boereck

Interesting! I wonder if you have used PGO in the project? Forcing fields to be located next to each other kind of feels like something that PGO could do for you.

brianpane

I basically did manual PGO because I was also reducing the size of several integer fields at the same time to pack more into each cache line. I’m excited to try out the rustc+LLVM PGO for future optimizations.

ofek

A long-standing issue with that was just recently fixed: https://github.com/rust-lang/rust/pull/133250

YZF

I found out I already know Rust:

        unsafe {
            let x_tmp0 = _mm_clmulepi64_si128(xmm_crc0, crc_fold, 0x10);
            xmm_crc0 = _mm_clmulepi64_si128(xmm_crc0, crc_fold, 0x01);
            xmm_crc1 = _mm_xor_si128(xmm_crc1, x_tmp0);
            xmm_crc1 = _mm_xor_si128(xmm_crc1, xmm_crc0);
Kidding aside, I thought the purpose of Rust was for safety but the keyword unsafe is sprinkled liberally throughout this library. At what point does it really stop mattering if this is C or Rust?

Presumably with inline assembly both languages can emit what is effectively the same machine code. Is the Rust compiler a better optimizing compiler than C compilers?

Aurornis

Using unsafe blocks in Rust is confusing when you first see it. The idea is that you have to opt-out of compiler safety guarantees for specific sections of code, but they’re clearly marked by the unsafe block.

In good practice it’s used judiciously in a codebase where it makes sense. Those sections receive extra attention and analysis by the developers.

Of course you can find sloppy codebases where people reach for unsafe as a way to get around Rust instead of writing code the Rust way, but that’s not the intent.

You can also find die-hard Rust users who think unsafe should never be used and make a point to avoid libraries that use it, but that’s excessive.

timschmidt

Unsafe is a very distinct code smell. Like the hydrogen sulfide added to natural gas to allow folks to smell a gas leak.

If you smell it when you're not working on the gas lines, that's a signal.

mrob

There's no standard recipe for natural gas odorant, but it's typically a mixture of various organosulfur compounds, not hydrogen sulfide. See:

https://en.wikipedia.org/wiki/Odorizer#Natural_gas_odorizers

gigatexal

Someone mentioned to me that for something as simple as a Linked list you have to use unsafe in rust

Update its how the std lib does it: https://doc.rust-lang.org/src/alloc/collections/linked_list....

RossBencina

Hydrogen Sulfide is highly corrosive (big problem in sewers and associated infrastructure) I highly doubt you would choose to introduce it to gas pipelines on purpose.

branko_d

Hydrogen sulfide is highly toxic (it's comparable to carbon monoxide). I doubt anyone in their right mind would put it intentionally in a place where it could leak around humans.

But it can occur naturally in natural gas.

cmrdporcupine

Look, no. Just go read the unsafe block in question. It's just SIMD intrinsics. No memory access. No pointers. It's unsafe in name only.

No need to get all moral about it.

throwaway150

> Like the hydrogen sulfide added to natural gas to allow folks to smell a gas leak.

I am 100% sure that the smell they add to natural gas does not smell like rotten eggs.

api

The idea is that you can trivially search the code base for "unsafe" and closely examine all unsafe code, and unless you are doing really low-level stuff there should not be much of it. Higher level code bases should ideally have none.

It tends to be found in drivers, kernels, vector code, and low-level implementations of data structures and allocators and similar things. Not typical application code.

As a general rule it should be avoided unless there's a good reason to do it. But it's there for a reason. It's almost impossible to create a systems language that imposes any kind of rules (like ownership etc.) that covers all possible cases and all possible optimization patterns on all hardware.

timschmidt

To the extent that it's even possible to write bare metal microcontroller firmware in Rust without unsafe, as the embedded hal ecosystem wraps unsafe hardware interfaces in a modular fairly universal safe API.

formerly_proven

My understanding from Aria Beingessner's and some other writings is that unsafe{} rust is significantly harder to get right in "non-trivial cases" than C, because the semantics are more complex and less specified.

j-krieger

This is not really true. You have to uphold those guarantees yourself. With unsafe preconditions, if you don't, the code will still crash loudly (which is better than undefined behaviour).

littlestymaar

With unsafe you get exactly the same kind of semantics as C, if you don't uphold the invariant the unsafe functions expect, you end up with UB exactly like in C.

If you want a clean crash instead on indeterministic behavior, you need to use assert like in C, but it won't save you from compiler optimization removing checks that are deemed useless (again, exactly like in C).

chongli

Isn't it the case that once you use unsafe even a single time, you lose all of Rust's nice guarantees? As far as I'm aware, inside the unsafe block you can do whatever you want which means all of the nice memory-safety properties of the language go away.

It's like letting a wet dog (who'd just been swimming in a nearby swamp) run loose inside your hermetically sealed cleanroom.

timschmidt

It seems like you've got it backwards. Even unsafe rust is still more strict than C. Here's what the book has to say (https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html)

"You can take five actions in unsafe Rust that you can’t in safe Rust, which we call unsafe superpowers. Those superpowers include the ability to:

    Dereference a raw pointer
    Call an unsafe function or method
    Access or modify a mutable static variable
    Implement an unsafe trait
    Access fields of a union
It’s important to understand that unsafe doesn’t turn off the borrow checker or disable any other of Rust’s safety checks: if you use a reference in unsafe code, it will still be checked. The unsafe keyword only gives you access to these five features that are then not checked by the compiler for memory safety. You’ll still get some degree of safety inside of an unsafe block.

In addition, unsafe does not mean the code inside the block is necessarily dangerous or that it will definitely have memory safety problems: the intent is that as the programmer, you’ll ensure the code inside an unsafe block will access memory in a valid way.

People are fallible, and mistakes will happen, but by requiring these five unsafe operations to be inside blocks annotated with unsafe you’ll know that any errors related to memory safety must be within an unsafe block. Keep unsafe blocks small; you’ll be thankful later when you investigate memory bugs."

janice1999

Claiming unsafe invalidates "all of the nice memory-safety properties" is like saying having windows in your house does away with all the structural integrity of your walls.

There's even unsafe usage in the standard library and it's used a lot in embedded libraries.

sunshowers

What language is the JVM written in?

All safe code in existence running on von Neumann architectures is built on a foundation of unsafe code. The goal of all memory-safe languages is to provide safe abstractions on top of an unsafe core.

LoganDark

> Isn't it the case that once you use unsafe even a single time, you lose all of Rust's nice guarantees?

No, not even close. You only lose Rust's safety guarantees when your unsafe code causes Undefined Behavior. Unsafe code that can be made to cause UB from Safe Rust is typically called unsound, and unsafe code that cannot be made to cause UB from Safe Rust is called sound. As long as your unsafe code is sound, then it does not break any of Rust's guarantees.

For example, unsafe code can still use slices or references provided by Safe Rust, because those are always guaranteed to be valid, even in an unsafe block. However, if from inside that unsafe block you then go on to manufacture an invalid slice or reference using unsafe functions, that is UB and you lose Rust's safety guarantees because of the UB.

wongarsu

If your unsafe code violates invariants it was supposed to uphold, that can wreck safety properties the compiler was trying to uphold elsewhere. If you can achieve something without unsafe you definitely should (safe, portable simd is available in rust nightly, but it isn't stable yet).

At the same time, unsafe doesn't just turn off all compiler checks, it just gives you tools to go around them, as well as tools that happen to go around them because of the way they work. Rust unsafe is this weird mix of being safer than pure C, but harder to grasp; with lots of nuanced invariants you have to uphold. If you want to ensure your code still has all the nice properties the compiler guarantees (which go way beyond memory safety) you would have to carefully examine every unsafe block. Which few people do, but you generally still end up with a better status quo than C/C++ where any code can in principle break properties other code was trying to uphold.

xboxnolifes

If you have 1 unsafe block, and you have a memory related crash/issue, where in your Rust code do you think the root cause is located?

This isn't a wet dog in a cleanroom. This is cleanroom complex that has a very small outhouse that is labeled as dangerous.

EnnEmmEss

Jason Ordendorff's talk [1] was probably the first time I truly grokked the concept of unsafe in Rust. The core idea behind unsafe in Rust is not to provide an escape from the guarantees provided by rust. It's to isolate the places where you have no choice but to break the guarantees and rigorously code/test the boundaries there so that anything wrapping the unsafe code can still provide the guarantees.

[1]: https://www.youtube.com/watch?v=rTo2u13lVcQ

andyferris

Rust isn't the only memory-safe language.

As soon as you start playing with FFI and raw pointers in Python, NodeJS, Julia, R, C#, etc you can easily loose the nice memory-safety properties of those languages - create undefined behavior, segfaults, etc. I'd say Rust is a lot nicer for checking unsafe correctness than other memory-safe languages, and also makes it easier to dip down to systems-level programming, yet it seems to get a lot of hate for these features.

colonwqbang

Can’t rust do safe simd? This is just vectorised multiplication and xor, but it gets labelled as unsafe. I imagine most code that wants to be fast would use simd to some extent.

steveklabnik

It's still nightly-only.

ricardobeat

Is this a sloppy codebase? I browsed through a few random files, and easily 90% of functions are marked unsafe.

immibis

Clearly marking unsafe code is no good for safety, if you have many marked areas.

Some codebases, you can grep for "unsafe", find no results, and conclude the codebase is safe... if you trust its dependencies.

This is not one of those codebases. This one uses unsafe liberally, which tells you it's about as safe as C.

"unsafe behaviour is clearly marked" seems to be a thought-stopping cliche in the Rust world. What's the point of marking them, if you still have them? If every pointer dereference in C code had to be marked unsafe (or "please" like in Intercal), that wouldn't make C any better.

kazinator

> clearly marked by the unsafe block.

Rust has macros; are macros prohibited from generating unsafe blocks, so that macro invocations don't have to be suspected of harboring unsafe code?

steveklabnik

No. Just like function bodies can contain unsafe blocks.

pcwalton

> Presumably with inline assembly both languages can emit what is effectively the same machine code. Is the Rust compiler a better optimizing compiler than C compilers?

rustc uses LLVM just as clang does, so to a first approximation they're the same. For any given LLVM IR you can mostly write equivalent Rust and C++ that causes the respective compiler to emit it (the switch fallthrough thing mentioned in the article is interesting though!) So if you're talking about what's possible (as opposed to what's idiomatic), the question of "which language is faster" isn't very interesting.

gf000

Rust's borrow checker still checks within unsafe blocks, so unless you are only operating with raw pointers (and not accessing certain references as raw pointers in some small, well-defined blocks) across the whole program it will be significantly more safe than C. Especially given all the other language benefits, like a proper type system that can encode a bunch of invariants, no footguns at every line/initialization/cast, etc.

acdha

Yes. I think it’s easy to underestimate how much the richer language and library ecosystem chip away at the attack surface area. So many past vulnerabilities have been in code which isn’t dealing with low-level interfaces or weird performance optimizations and wouldn’t need to use unsafe. There’ve been so many vulnerabilities in crypto code which weren’t the encryption or hashing algorithms but things like x509/ASN parsing, logging, or the kind of option/error handling logic a Rust programmer would use the type system to validate.

dietr1ch

> I thought the purpose of Rust was for safety but the keyword unsafe is sprinkled liberally throughout this library.

Which is exactly the point, other languages have unsafe implicitly sprinkled in every single line.

Rust tries to bound and explicitly delimit where unsafe code is to makes review and verification efforts precise.

cbarrick

Others have already addressed the "unsafe" smell.

I think the bigger point here is that doing SIMD in Rust is still painful.

There are efforts like portable-simd [1] to make this better, but in practice, many people are dropping down to low-level SIMD intrinsics and/or inline assembly, which are no better than their C equivalents.

[1]: https://github.com/rust-lang/portable-simd

koito17

The purpose of `unsafe` is for the compiler to assume a block of code is correct. SIMD intrinsics are marked as unsafe because they take raw pointers as arguments.

In safe Rust (the default), memory access is validated by the borrow checker and type system. Rust’s goal of soundness means safe Rust should never cause out-of-bounds access, use-after-free, etc; if it does, then there's a bug in the Rust compiler.

no_wizard

How do we know if Rust is safe unless Rust is written purely in safe Rust?

Is that not true? Even validators have bugs or miss things no?

TheDong

And while we're in the hypothetical extreme world somewhat separated from reality, a series of solar flares could flip a memory bit and all the error-correction bits in my ECC ram at once to change a pointer in memory, causing my safe rust to do an out of bounds write.

Until we design perfectly correct computer hardware, processors, and a sun which doesn't produce solar radiation, we can't rely on totally uniform correct execution of our code, so we should give up.

The reality is that while we can't prove the rust compiler is safe, we can keep using it and diligently fix any counter-examples, and that's good enough in practice. Over in the real world, where we can acknowledge "yes, it is impossible to prove the absence of all bugs" and simultaneously say "but things sure seem to be working great, so we can get on with life and fix em if/when they pop up".

steveklabnik

> Even validators have bugs

Yep! For example, https://github.com/Speykious/cve-rs is an example of a bug in the Rust compiler, which allows something that it shouldn't. It's on its way to being fixed.

> or miss things no?

This is the trickier part! Yes, even proofs have axioms, that is, things that are accepted without proof, that the rest of the proof is built on top of. If an axiom is incorrect, so is the proof, even though we've proven it.

int_19h

Out of curiosity, why do they take raw pointers as arguments, rather than references?

steveklabnik

From the RFC: https://rust-lang.github.io/rfcs/2325-stable-simd.html

> The standard library will not deviate in naming or type signature of any intrinsic defined by an architecture.

I think this makes sense, just like any other intrinsic: unsafe to use directly, but with safe wrappers.

I believe that there are also some SIMD things that would have to inherently take raw pointers, as they work on pointers that aren't aligned, and/or otherwise not valid for references. In theory you could make only those take raw pointers, but I think the blanket policy of "follow upstream" is more important.

datadeft

I thought that the point of Rust is to have safe {} blocks (implicit) as a default and unsafe {} when you need the absolute maximum performance available. You can audit those few lines of unsafe code very easily. With C everything is unsafe and you can just forget to call free() or call it twice and you are done.

steveklabnik

> unsafe {} when you need the absolute maximum performance available.

Unsafe code is not inherently faster than safe code, though sometimes, it is. Unsafe is for when you want to do something that is legal, but the compiler cannot understand that it is legal.

datadeft

True, however I only saw this happens to achieve max perf. I have very limited experience so this is confirmation bias from my end.

WD-42

It’s not about performance, it’s about undefined behavior.

fxtentacle

Yeah, this article about a rust "win" perfectly illustrates why I distrust all good news about it.

Rust zlib is faster than zlib-ng, but the latter isn't a particularly fast C contender. Chrome ships a faster C zlib library which Rust could not beat.

Rust beat C by using pre-optimized code paths and then C function pointers inside unsafe. Plus C SIMD inside unsafe.

I'd summarize the article as: generous chunks of C embedded into unsafe blocks help Rust to be almost as fast as Chrome's C Zlib.

Yay! Rust sure showed it's superiority here!!!!1!1111

FreshOldMage

Did you even read the article? They compare specifically against the Chrome zlib library and beat it at 10 out of 13 chunk sizes considered.

johnisgood

"faster than C" almost always boils down to different designs, implementations, algorithms, etc.

Perhaps it is faster than already-existing implementations, sure, but not "faster than C", and it is odd to make such claims.

atoav

The thing is, Rust allows you to casually code things that are fast. A few years back I took part in an "all programming languages allowed" competition on a popular hacker blog in my country. The topic was who writes the fastest tokenizer (a thing splitting sentences into words).

I took 15 minutes to write one in Rust (a language I had just learned by that point) using a "that should work" approach and became second place, with some high effort C-implementations being slower and a highly optimized assembler variant taking first place.

Since then I programmed a lot more in C and C++ as well (for other reasons) and got more experience. Rust is not automatically faster, but the defaults and std library of Rust is so well put together that a common-sense approach will outperform most C code without even trying – and it does so while having typesafety and memory safety. This is not nothing in my book and still extremely impressive.

The best thing about learning Rust however was how much I learned for all the other languages. Because what you learn there is not just how to use Rust, but how to program well. Understanding the way the Rust borrow checker works 1000% helped me avoiding nasty bugs in C/C++ by realizing that I violatr ownership rules (e.g. by having multiple writers)

johnisgood

Well, yeah, same with books on Erlang, Common Lisp, even Odin. They teach you how to become a "better" (debatable, perhaps) programmer.

xxs

zlib-ng is pretty much assembly - with a bit of C. There is this quote: but was not entirely fair because our rust implementation could assume that certain SIMD capabilities would be available, while zlib-ng had to check for them at runtime

zlib-ng can be compiled to whatever target arch is necessary, and the original post doesn't mention how it was compiled and what architecture and so on.

It's another case not to trust micro benchmarks

tdiff

Nevertheless Russinovich actually says something in the lines of "simple rewriting in rust made some our code 5-15% faster (without deliberate optimizations)": https://www.youtube.com/watch?v=1VgptLwP588&t=351s

Someone

Without analysis as to what caused that, that statement is meaningless.

For example, he says they didn’t set out to improve the code, but they were porting decennia-old C code to rust. Given the subject (truetype font parsing and rendering), my guess would be that the original code had more memory copies copying data out of the font data because rust makes it easier to safely avoid that (in which case the conclusion would be “C could be as fast, but with a lot more effort”), but it could also be that they spent a day figuring out some code did to realize that it wasn’t necessary on anything after Windows 95, and stripped it out, rather than porting it.

serial_dev

I understand their improvement figures exactly as you wrote, "C could be as fast, but with a lot more effort".

Yes, if your code in Lang-X is faster than C, it's almost certainly a skill issue somewhere in the C implementation.

However, in the day-to-day, if I can make my code run faster in Lang-X than C, especially if I'm using Lang-X for only a couple of months and C potentially for decades, that is absolutely meaningful. Sure, we can make the C code just as fast, but it's not viable to spend that much time and expertise on every small issue.

Outside of "which lang is better" discussions on online forums, it doesn't matter how fast you can theoretically make your program, it matters how fast you actually make it with the constraints your business have (time usually).

pinkmuffinere

I’m sure I’m missing context, and presumably there are other benefits, but 5-15% improvement is such a small step to justify rewriting codebases.

I also wonder how much of an improvement you’d get by just asking for a “simple rewrite” in the existing language. I suspect there are often performance improvements to be had with simple changes in the existing language

turtletontine

Far better justification for a rewrite like this is if it eases maintenance, or simplifies building/testing/distribution. Taking an experienced and committed team of C developers with a mature code base, and retraining them to rewrite their project in Rust for its own sake is pretty absurd. But if you have a team that’s more comfortable in Rust, then doing so could make a lot of sense - and, yes, make it easier to ensure the product is secure and memory-safe.

tdiff

I agree that simple rewriting could have given some if not all perf benefits, but can it be the case that rust forces us to structure code in a way that is for some reason more performant in some cases?

5-15% is a big deal for a low-level foundational code, especially if you get it along with some other guarantees, which may be of greater importance.

sedatk

> 5-15% improvement is such a small step to justify rewriting codebases

They hadn't expected any perf improvements at all. Quite the opposite, in fact. They were surprised that they saw perf improvements right away.

maccard

There are hopefully very few things that can be done to low level building blocks. A 15% improvement is absolutely worth it for a library as widely used as a compression library.

pdimitar

Even 5% on a hot path are quite the big gains, actually.

Furthermore, they said that they did not expect any performance gains. They did the rewrite for other reasons and got the unexpected bonus of extra performance.

pveierland

One big part I've noticed when working in rust is that, because the compilation and analysis checks you're given are so much stronger than in C or C++, and because the ecosystem of crates is so easy to make use of, I'll generally be able to make use of more advanced algorithms and methods.

I'm currently working with ~150 dependencies in my current project which I know would be a major hurdle in previous C or C++ projects.

ForTheKidz

Everything you said is correct of course, but the idea of auditing 150 dependencies makes me feel ill. It's essentially impossible for a single person.

steveklabnik

This is why sharing code is so important; it doesn't fall on one person, but instead, on the overall community.

For example, cargo-vet and cargo-crev allow you to rely on others you trust to help audit dependencies.

pveierland

Oh, absolutely. Software cannot scale without trust. No single person is capable of auditing their browser or operating system either.

maccard

The effort is _roughly_ proportional - if you need to parse JSON in either language you can write it yourself or use an existing library. Both of those are the same amount of work in c++ and rust.

layer8

If anything, this should be “zlib-rs is faster than zlib-ng”, but not “$library is faster than $programming_language”.

chjj

[flagged]

pdimitar

I am hopping on Rust threads on HN very regularly and I have to tell you my anecdotal experience.

Which is: people complaining about Rust zealots are much more than actual Rust zealots. Thinking of it, I haven't seen a proper Rust zealot on HN for at least a year at this point.

So I don't know, maybe do less cheap digs. Tearing down straw men is pretty boring to watch.

brooke2k

the point of saying "faster than C" isn't to make it a competition, it's to demonstrate that Rust is capable of hitting performance goals that previously one needed to use C/C++ for.

kgeist

I heard that aliasing in C prevents the compiler from optimizing aggressively. I can believe Rust's compiler can optimize more aggressively if there's no aliasing problem.

layer8

C has the restrict type qualifier to express non-aliasing, hence it shouldn’t be a fundamental impediment.

gf000

Which is so underused that the whole compiler feature was buggy as hell, and was only recently fixed because compiling Rust where it is the norm exposed it.

anon-3988

> fundamental impediment

This is an interesting word. I wonder why no one has written high performance library code in assembly yet at this point?

qweqwe14

The fact that it's faster than the C implementation that surely had more time and effort put into it doesn't look good for C here.

jandrewrogers

C++ surpassed C performance decades ago. While C still has some lingering cachet from its history of being “fast”, most software engineers have not worked at a time when it was actually true. C has never been that amenable to scalable optimization, due mostly to very limited abstractions and compile-time codegen.

vkou

I think you'll find that if you re-write an application, feature-for-feature, without changing its language, the re-written version will be faster.

renewiltord

This is known as the Second System Effect: where Great Rewrites always succeed in making a more performant thing.

johnisgood

It says absolutely nothing about the programming language though.

acdha

Doesn’t it say something if Rust programmers routinely feel more comfortable making aggressive optimizations and have more time to do so? We maintain code for longer than the time taken to write the first version and not having to pay as much ongoing overhead cost is worth something.

jason-johnson

How can it not? Experts in C taking longer to make a slower and less safe implementation than experts in Rust? It's not conclusive but it most certainly says something about the language.

jason-johnson

This has generally been the case, but a system language like Rust has access to optimisations that C simply won't have due to the compiler having so much more information (e.g. being able to skip run time array size checks because the compiler was able to prove out of bounds access cannot occur).

cb321

I think this may not be a very high bar. zippy in Nim claims to be about 1.5x to 2.0x faster than zlib: https://github.com/guzba/zippy I think there are also faster zlib's around in C than the standard install one, such as https://github.com/ebiggers/libdeflate (EDIT: also mentioned elsethread https://news.ycombinator.com/item?id=43381768 by mananaysiempre)

zlib itself seems pretty antiquated/outdated these days, but it does remain popular, even as a basis for newer parallel-friendly formats such as https://www.htslib.org/doc/bgzip.html

JoshTriplett

The bar here is not zlib, it's zlib-ng, which aims primarily for performance.

libdeflate is an impressive library, but it doesn't help if you need to stream data rather than having it all in memory at once.

lern_too_spel

They're comparing against zlib-ng, not zlib. zlib-ng is more than twice as fast as zlib for decompression. https://github.com/zlib-ng/zlib-ng/discussions/871

libdeflate is not zlib compatible. It doesn't support streaming decompression.

cb321

Thanks (to all correctors). FWIW, that zlib-ng discussion page you link to has way more information about what machine the benchmarks were run on than TFA. It's also a safe bet that Google timed their chromium lib (which seems really close) on a much larger diversity of core architectures than these 3..4 guys have with zlib-rs. So, you know, very early days in terms of perf claims, IMO.

Also, FWIW, that zippy Nim library has essentially zero CPU-specific optimizations that I could find. Maybe one tiny one in some checksumming bit. Optimization is specialization. So, I'd guess it's probably a little slower than zlib-ng now that this is pointed out, but as @hinkley observed, portability can also be a meaningful goal/axis.

mastax

The benchmarks in the parent post are comparing to zlib-ng, which is substantially faster than zlib. The zippy claims are against "zlib found on a fresh Linux install" which at least for Debian is classic zlib.

hinkley

Zlib is unapologetically written to be portable rather than fast. It is absolutely no wonder that a Rust implementation would be faster. It runs on a pathetically small number of systems by contrast. This is not a dig at Rust, it’s an acknowledgement of how many systems exist out there, once you include embedded, automotive, aerospace, telecom, industrial control systems, and mainframes.

Richard Hipp denounces claims that SQLite is the widest-used piece of code in the world and offers zlib as a candidate for that title, which I believe he is entirely correct about. I’ve been consciously using it for almost thirty years, and for a few years before that without knowing I was.

maccard

Except this comparison isn’t against zlib, it’s against zlib-ng [0]. The readme states:

> The result is a better performing and easier to maintain zlib-ng.

So they’re comparing a first pass rewrite against a variation of zlib designed for performance

[0] https://github.com/zlib-ng/zlib-ng

jrockway

Chromium is kind of stuck with zlib because it's the algorithm that's in the standards, but if you're making your own protocol, you can do even better than this by picking a better algorithm. Zstandard is faster and compresses better. LZ4 is much faster, but not quite as small.

Some reading: https://jolynch.github.io/posts/use_fast_data_algorithms/

(As an aside, at my last job container pushes / pulls were in the development critical path for a lot of workflows. It turns out that sha256 and gzip are responsible for a lot of the time spent during container startup. Fortunately, Zstandard is allowed, and blake3 digests will be allowed soon.)

jeroenhd

`Content-Encoding: zstd` was added to Chromium a while ago: https://chromestatus.com/feature/6186023867908096

You can still use deflate for compression, but Brotli and Zstd have been available in all modern browsers for quite some time.

amaranth

Safari doesn't support zstd, that means if you want to use it you have to support multiple formats.

cesarb

> Zstandard is faster and compresses better.

However, keep in mind that zstd also needs much more memory. IIRC, it uses by default 8 megabytes as its buffer size (and can be configured to use many times more than that), while zlib uses at most 32 kilobytes, allowing it to run even on small 16-bit processors.

jeffbee

Yeah I just discovered this a few days ago. All the docker-era tools default to gzip but if using, say, bazel rules_oci instead of rules_docker you can turn on zstd for large speedups in push/pull time.

j16sdiz

Chromium supports brotli and zstd

IshKebab

It's barely faster. I would say it's more accurate to say it's as fast as C, which is still a great achievement.

ajross

It's... basically written in C. I'm no expert on zlib/deflate or related algorithms, but digging around https://github.com/trifectatechfoundation/zlib-rs/ almost every block with meaningful logic is marked unsafe. There's raw allocation management, raw slicing of arrays, etc... This code looks and smells like C, and very much not like rust. I don't know that this is a direct transcription of the C code, but if you were to try something like that this is sort of what it would look like.

I think there's lots of value in wrapping a raw/unsafe implementation with a rust API, but that's not quite what most people think of when writing code "in rust".

hermanradtke

> basically written in C

Unsafe Rust still has to conform to many of Rust’s rules. It is meaningfully different than C.

est31

It has also way less tooling available than C to analyze its safety.

ajross

Are there examples you're thinking about? The only good ones I can think of are bits about undefined behavior semantics, which frankly are very well covered in modern C code via tools like ubsan, etc...

gf000

C is not assembly, nor is it portable assembly at all in this century, so your phrasing is very off.

C code will go through a huge amounts of transformations by the compiler, and unless you are a compiler expert you will have no idea how the resulting code looks. It's not targeting the PDP-11 anymore.

xxs

I mentioned in under another comment - and while I consider myself versed enough in deflate - comparing the library to zlib-ng is quite weird as the latter is generally hand written assembly. In order to beat it'd take some oddity in the test itself

solidsnack9000

I'm not sure why people say this about certain languages (it is sometimes said about Haskell, as well).

The code has a C style to it, but that doesn't mean it wasn't actually written in Rust -- Rust deliberately has features to support writing this kind of code, in concert with safer, stricter code.

Imagine if we applied this standard to C code. "Zlib-NG is basically written in assembler, not C..." https://github.com/zlib-ng/zlib-ng/blob/50e9ca06e29867a9014e...

ajross

> Imagine if we applied this standard to C code. "Zlib-NG is basically written in assembler, not C..."

We absolutely should, if someone claimed/implied-via-headline that naive C was natively as fast as hand-tuned assembly! This kind of context matters.

FWIW: I'm not talking about the assembly in zlib-rs, I was specifically limiting my analysis to the rust layers doing memory organization, etc... Discussing Rust is just exhausting. It's one digression after another, like the community can't just take a reasonable point ("zlib-rs isn't a good example of idiomatic rust performance") on its face.

johnisgood

It does actually seem like what a C -> Rust transpiler would spit out.

oneshtein

Cannot understand your complain. It written in Rust, but for you it looks like C. So what?

Alifatisk

So, it is basically like it was written in C.

ajross

It doesn't exploit (and in fact deliberately evades) Rust's signature memory safety features. The impression from the headline is "Rust is as fast as C now!", but in fact the subset of the language that has been shown to be as fast as C is the subset that is basically isomorphic to C.

The impression a naive reader might take is that idiomatic/safe/best-practices Rust has now closed the performance gap. But clearly that's not happening here.

throwaway48476

But it is faster. The closer to theoretical maximum the smaller the gains become.

mananaysiempre

Zlib-ng is between a couple and multiple times away from the state of the art[1], it’s just that nobody has yet done the (hard) work of adjusting libdeflate[2] to a richer API than “complete buffer in, complete buffer out”.

[1] https://github.com/zlib-ng/zlib-ng/issues/1486

[2] https://github.com/ebiggers/libdeflate

qweqwe14

"Barely" or not is completely irrelevant. The fact is that it's measurably faster than the C implementation with the more common parameters. So the point that you're trying to make isn't clear tbh.

Also I'm pretty sure that the C implementation had more man hours put into it than the Rust one.

bee_rider

I think that would be really hard to measure. In particular, for this sort of very optimized code, we’d want to separate out the time spent designing the algorithms (which the Rust version benefits from as well). Actually I don’t think that is possible at all (how will we separate out time spent coding experiments in C, then learning from them).

Fortunately these “which language is best” SLOC measuring contests are just frivolous little things that only silly people take seriously.

1vuio0pswjnm7

Which library compiles faster.

Which library has fewer dependencies.

Is each library the same size. Which one is smaller.

rnijveld

I would argue compile time changes don't matter much, as the amount of data going through zlib all across the world is so large, that any performance gain should more than compensate any additional compilation time (and zlib-rs compiles in a couple of seconds anyway on my laptop).

As for dependencies: zlib, zlib-ng and zlib-rs all obviously need some access to OS APIs for filesystem access if compiled with that functionality. At least for zlib-rs: if you provide an allocator and don't need any of the file IO you can compile it without any dependencies (not even standard library or libc, just a couple of core types are needed). zlib-rs does have some testing dependencies though, but I think that is fair. All in: all of them use almost exactly the same external dependencies (i.e.: nothing aside from libc-like functionality).

zlib-rs is a bit bigger by default (around 400KB), with some of the Rust machinery. But if you change some of that (i.e. panic=abort), use a nightly compiler (unfortunately still needed for the right flags) and add the right flags both libraries are virtually the same size, with zlib at about 119KB and zlib-rs at about 118KB.

1vuio0pswjnm7

One of the things I like about C is I can download a statically-compiled native GCC for use on a computer with modest amounts of memory, storage and a relatively old, slow CPU. Total size uncompressed is 242.3MB.

Using this I can statically compile a cross-compiler. Total size uncompressed 169.4MB.

I use GCC to compille zlib and a wide variety of other software. I can build an operating system from the ground up.

Perhaps someday during my lifetime it will be possible to compile programs written in Rust using inexpensive computers with modest amounts of memory, storage and relatively slow CPUs. Meanwhille, there is C.

WalterGillman

> Which library has fewer dependencies.

This is not insignificant.

Remember xz? That could have been a disaster.

That the language includes a package manager that fetches an assortment of libraries from who knows whom on demand doesn't exactly inspire confidence in the process to me. Alice's secure AES implementation might bring Eve's string padding function along for the ride.

Rust(TM) the language might be (memory) safe in theory but I have serious issues (t)rusting (t)rust and anything built with it.

miki123211

I think performance is an underappreciated benefit of safe languages that compile to machine code.

If you're writing your program in C, you're afraid of shooting yourself in the foot and introducing security vulnerabilities, so you'll naturally tend to avoid significant refactorings or complicated multithreading unless necessary. If you have Rust's memory safety guarantees, Go's channels and lightweight goroutines, or the access to a test runner from either of those languages, that's suddenly a lot less of a problem.

The compiler guarantees you get won't hurt either. Just to give a simple example, if your Rust function receives an immutable reference to a struct, it can rely on the fact that a member of that struct won't magically be mutated by a call to some random function through spooky action at a distance. It can just keep it on the stack / in a callee-saved register instead of fetching it from memory at every loop iteration, if that's more optimal.

Then there's the easy access to package ecosystems and extensive standard libraries. If there's a super popular do_foo package, you can almost guarantee that it was a bottleneck for somebody at some point, so it's probably optimized to hell and back. It's certainly more optimized than your simple 10-line do_foo function that you would have written in C, because that's easier than dealing with yet another third-party library and whatever build system it uses.

throwaway2037

Does this performance have anything to do with Rust itself, or is it just more optimized than the other C-language versions (more SIMD instructions / raw assembly code)? I ask because there is a canonical use case where C++ can consistently outperform C -- sorting, because the comparison operator in C++ allows for more compiler optimization compared to the C version: qsort(). I am wondering if there is something similar here for Rust vs C.

anonymoushn

these are facts about the C and C++ stdlib sort functions which nobody should really use.

up2isomorphism

Rust folks love compare rust to C but C folks seldom compare C to rust.

Narishma

Not that surprising, Rust folks are more likely to be familiar with C than the reverse.

Georgelemental

> The C code is able to use switch implicit fallthroughs to generate very efficient code. Rust does not have an equivalent of this mechanism

Rust very much can emulate this, with `break` + nested blocks. But not if you also add in `goto` to previous branches

CyberDildonics

If you're dealing with a compiled system language the language is going to make almost no difference in speed, especially if they are all being optimized by LLVM.

An optimized version that controls allocations, has good memory access patterns, uses SIMD and uses multi-threading can easily be 100x faster or more. Better memory access alone can speed a program up 20x or more.