Hacker News new | ask | show | jobs
by josephg 1100 days ago
Tldr; they’re about even. Which is what I’d expect given they’re both performance oriented languages which compile via llvm.

As I see it, performance wise Rust has one advantage and one disadvantage compared to zig. Rust’s advantage is that it can add the equivalent of C’s noalias all over the place because of the rules imposed by the borrow checker. This can help the optimizer. And the drawback of rust is that all array accesses are bounds checked. (Well, at least in safe rust). But thanks to prediction intrinsics, the slow down from this is much less than I always expect. Bounds checks do bloat the binary size though.

So rust and zig trading blows benchmark to benchmark is about what I would expect to happen. And that’s exactly what I’m seeing here.

11 comments

> it can add the equivalent of C’s noalias all over the place

There's an open proposal to do this in Zig as well, with the ability to opt out at the individual parameter level (and with safety checks in debug builds).

https://github.com/ziglang/zig/issues/1108

Either way we can definitely thank Rust for blazing the trail. noalias in LLVM had never been stress-tested to that degree, and they were finding and fixing noalias-related optimizer bugs for years

People keep being surprised that bounds-checking doesn’t really seem to incur that much cost but frankly it seems pretty straightforward to me.

In the years of Rust code I’ve written, I don’t think I’ve ever actually indexed into an array manually. If I have it’s been an incredibly small number of cases. I’m almost always iterating, which makes bounds checks essentially unnecessary.

As usual, I recall the remarks of C.A.R Hoare on his 1980's Turing Award speech, regarding bounds checking on Algol compilers and customers point of view on how it should be unlawful to do otherwise.

Never, ever, since 1986, have bounds checking been the major source of performance issues on applications I have written.

Rather ill chosen algorithms or data structures.

I suspect it's more that bounds checking actually helps performance in many circumstances in that it can improve branch prediction. Not always, but sometimes.
Inserting branches to your code can only make it slower or equally ~fast but not faster than branchless code so what you say doesn't make a lot of sense.
Not necessarily. If the branch is predictable, it may be better than branchless, particularly if the alternative is a cmov. From Agner:

> As a rule of thumb, we can say that a conditional jump is faster than a conditional move if the code is part of a dependency chain and the prediction rate is better than 75%. A conditional jump is also preferred if we can avoid a lengthy calculation [...] when the other operand is chosen

Bounds check out of the tight loop can give the possibility of the compiler dropping bounds check and/or related branches later.
This sounds purely theoretical. And still even if compiler would be able to coalesce multiple branches into less, such branchy code still cannot be faster than the branchless one.
Sadly, not yet. Even when (Safe) Rust's type system guarantees the noalias annotations will be inserted correctly, the Rust compiler team has been very cautious of turning this on because of numerous LLVM miscompilation issues. It was turned on two years ago in https://github.com/rust-lang/rust/pull/82834 but was subsequently rolled back in https://github.com/rust-lang/rust/pull/86036 because of a miscompliation bug.
The rollback was only for the beta, it didn't land on master. So the next release (1.54) did in fact enable mutable-noalias.

You can see it in the function arguments in llvmir: https://rust.godbolt.org/z/a3c366nG6

Which is amazing, because Rust comes with more safety guarantees.
Why is this amazing? One would expect the language with more restrictions to compile to faster code, no?
Some restrictions aids performance, but checking that pointers have not run wild like elphants in a porcelain shop requires extra checks, which takes time.
> elphants in a porcelain shop

Slander.

Elephants are gentle, careful creatures. Probably would make good proprietors of a porcelain shop.

Unlike bulls and china shops....

Don't you guys watch Mythbusters? https://www.youtube.com/watch?v=Xzw2iBmRsjs (Mythbusters bust 'bull in a china shop stereotype')
> Unlike bulls and china shops....

Steers are probably OK though

https://www.youtube.com/watch?v=iv-HjTkbBwk

Only if the restrictions are static and don't need to be checked dynamically, naturally. For dynamic arrays or dynamic indices it's not always possible to do that in Rust.
Doesn't Zig also check bounds by default?
Yes, in some compilation modes, but those benchmarks were compiled in "ReleaseFast" mode which disables all safety checks.
For slices and arrays, yes.
i see bound checks as advantage. I don't want random stuff from program tbh. And, if someone really wants to disable bound checks for certain stuff, they can always do that.
I'm divided. The majority of the time i know how big the array is with information the compiler doesn't have. However I have to admit 10% of the time i'm off by one.
Bounds checks can sometimes be optimized out, such as when using iterators or a for loop over a fixed range. LLVM is pretty decent at optimizing out redundant checks too.
Most of the ones zig is slightly slower than rust are due to a less than performative bignum implementation. All the rest zig handily beats rust
Zig is writing custom backends in addition to their LLVM backend
So is rust. But I assume these benchmark results all come from llvm.
In some sense, so is Rust -- there's work on a cranelift backend, as well as gccrust.
You can noalias in zig.
You can noalias in C as well, but it is used so infrequently in production C code that llvm didn’t even compile it correctly until a couple years ago. Figuring out when parameters in a C program can safely be declared noalias is very tricky, and I’ve almost never seen anyone bother with it. I assume the same is true in zig.
The point is that rust does it automatically
You probably only need it in a few places
I did view some of the source files though, and the rust ones look to be a lot longer :-)
Nbody looked about the same, zig had really long lines for stuff that rustfmt split. But that aside, I agree the zif metaprogramming really shome here.