Fastest programming language: C++ vs Rust vs Zig | Dave Plummer and Lex Fridman

C++ vs Rust vs Zig

4 Likes

I know these types of benchmarks are mostly just fun exhibition and should be taken with a huge grain of salt when comparing real-world performance of a language, but I am actually surprised that Rust managed to take second place, and C is sitting in 7th place. If I was forced to make a prediction without seeing the results, I would have ordered them something like this:

  • Zig and C in the top two slots
  • C++
  • Rust
  • Haskell, Nim, Odin in the next few slots

(should I be embarrassed for having never even heard of chapel?).

1 Like

Without having watched the video (I find Fridman quite insufferable) I would take those results (or any ‘language X produces faster code than language Y’ benchmarks) with a massive grain of salt, since the devil is usually in the details.

At least all compiled languages that sit on top of LLVM should have identical performance and in fact output identical code (with optimizations enabled and any ‘safety overhead’ like range checks disabled), because all the performance optimization heavy lifting happens down in the LLVM optimizer passes - and as long as those optimizer passes have all the information they need they’ll produce the same output for the same input code, no matter what language the input code was written in.

If there are measurable differences I would rather go looking for differences in the build process (like Zig picking -march=native by default or Clang not automatically using LTO).

There is one theoretical advantage in Rust in that it guarantees that pointers can’t alias, in C this requires careful use of the restrict keyword, but AFAIK in practice this hardly shows up in profiling outside artificial benchmarks.

8 Likes

The design/structure of a the language can often prohibit it, and make it impossible for LLVM to be able to make certain optimizations simply because it can’t safely make certain assumptions. Andrew’s recent “Don’t Forget to Flush” presentation had a very good example illustrating this, and IIRC Rust was one the example languages used there for comparison. In theory it could be true, but in practice there can still be significant differences between two similar languages, both using LLVM, even for basic constructs.

IME those are more often stdlib design warts/philosophies which might make the work for the compiler harder or easier. But any compiled language should be able to drop down to simple expressions and memory load/stores.

For the record, I consider language and stdlib separate things, even though some languages (like C++ or Rust) blur the line.

(e.g. we should separate between benchmarks that measure language performance from benchmarks that measure performance of specific stdlib features, those can drastically differ of course)

1 Like

I do generally agree, but there are minor differences that can have a significant impact, even between two analogous features. I won’t claim to be an expert on the internals of either Rust of Zig, but even constructs like unreachable differ in their meaning to the compiler. From what I understand, the unreachable macro in Rust is essentially the same as Zig’s keyword in Debug and ReleaseSafe modes, but in ReleaseFast, it can further take advantage to not even generate a panic and do undefined behavior instead, which allows even more aggressive optimizations to be made that are not possible in Rust. To the compiler, this semantic difference of “should not be possible, but panic if it does” and “logically impossible and no need to even handle it” can result in different machine code.

How often this is actually impactful in the real-world is obviously open for debate, but even at the language level or trying to milk the CPU for every cycle you can get for a competitive benchmark, it can make a difference, even though both are using the same backend to generate the machine code.

1 Like

For it to become UB you need unreachable_unchecked but iirc there are no buildin which works exactly as zig’s version with regards to optimization mode

EDIT: i was wrong it behaves exactly as zig’s version Rust Playground and panics in debug mode

Zig SHOWTIME talk on the Zig implementation at the time:

5 Likes

Rust’s equivalent is this: unreachable in std::intrinsics - Rust

…and in Clang it is __builtin_unreachable(), and I bet they all simply compile down to the LLVM unreachable instruction: LLVM Language Reference Manual — LLVM 22.0.0git documentation (in MSVC the same thing is called __assume(false)).

I have used this in one place so far in my home computer emulators: if you put an unreachable into a switch default prong, the compiler will remove an initial jump table range check. But this also means that if the default prong is actually called that the code will access a slot outside the jump table and jump to some random location.

I agree with you, deep down every languages that depends on LLVM can and will output the same machine code, given the same code/informations, but in practice we ought to remember that we are humans, and that some languages makes it easier to get performant code, whilst other at times can feel like a battle, and this is due to a lot of parameters, syntax, type system, compiler, tooling, stdlib, ecosystem etc. My point being that for example in Zig I always use a fitting allocator, I always try to think about memory, optimization, and good error handling, and that’s because the language makes it very convenient to do all of those things at the same time. whereas in C for example, making your own allocator is more painful, and you are essentially on your own. In C++ you have to be very carefull, and not make even the tiniest mistake, and again like C it doesn’t really play nice with the ecosystem. Even Rust in this case makes it trivial to write performant code because you can most often than not grab a high quality package or library for your needs. So in practice a language and the choices that were made to design it have IMO a tremendous impact on practical performance.

2 Likes

Rust is pretty big on no_std tho