Live data from Hacker News

Performance of Rust Language [pdf]

github.com

21–30 of 146 posts

Re: Performance of Rust Language [pdf]

#21
post #11

Earlier quoted context omitted.

C++ you get templated generic algorithms that in practice no one really does with C because macros suck too much. So in C typically you'd have a runtime generic routine that doesn't inline. A classic example here is qsort() vs std::sort().

I explicitly acknowledged that: > The only candidate is using virtualization and void* pointers instead of monomorphized generics which some C code does for the lack of better options, but that's not a problem in Rust as well. But in fact, if speed is a concern to you, even in C you will use "templated" sorting (via macros or code generation).

Neither codegen nor macros (they are a part of the preprocessor) are really a part of C.

For the latter, the lack of integration becomes more noticeable if you try writing a macro in which the compare param can accept a function identifier. As the preprocessor doesn't have the knowledge of the contents of the referred function, it can't inline it. In C++ and Rust, their compilers do, so they can.

A codegen tool could overcome this, but you could also make a codegen tool to write Zig/D/C#/Swift in C, or any other language for that matter :). By this point, one could say you are programming in a superset of C, not strict C.

Re: Performance of Rust Language [pdf]

#22

Earlier quoted context omitted.

Is C++ more performant than C? I find this hard to believe. C++ does not have any construct that cannot be replicated, or is not common, in C. The only candidate is using virtualization and void* pointers instead of monomorphized generics which some C code does for the lack of better options, but that's not a problem in Rust as well. If anything, Rust has the potential to be more performant than C due to its aliasing…

Modern C++ metaprogramming materially impacts performance in practice. I’ve done performance engineering for decades in both C and modern C++ and I would assert that the difference isn’t arguable. The poor applicability of auto-vectorization is another area where C++ is strong. You can transparently codegen e.g. AVX512 from intrinsics directly in C++ in contexts that would be opaque to auto-vectorization and difficul…

So youre saying the metaprogramming facilities of C++ allow the compiler to better optimise high level human readable code more effectively than C. Thats a fair point and one I'd never even thought of before, I always thought C was faster because of things like v-tables and all that stuff.

Re: Performance of Rust Language [pdf]

#23
post #4

Rust is in an awkward position of being already complicated enough that adding proofs for skipping bounds checks probably will not happen for a long time, even though this kind of low-level operation is where a lot of optimisation is lost. Compounding on this, Rust is also unstable underneath, since there is no public, stable contract for carrying high-level semantics from HIR into MIR. Because these high-level invar…

> Compounding on this, Rust is also unstable underneath, since there is no public, stable contract for carrying high-level semantics from HIR into MIR. Because these high-level invariants are lost during compilation Not sure if I'm just out of the loop, but I'm having a hard time following this line of reasoning. Why is a public and/or stable contract needed to carry high-level semantics from HIR to MIR? Neither seem…

Whoops! Although there is no public contract between HIR and MIR, the public part was not relevant here. What I wanted to highlight is that if they'd want to add proper proof machinery to eliminate low-level safety checks, they'd have to do it at: surface language, which is already complex enough; then HIR->MIR boundary with clean provenance (which I think current MIR would collapse too aggressively) and which may require a much clearer contract; then, even if they do the full front- and mid- ends properly, if you leave it up to LLVM, it ends up in SCEV, which is language neutral and would not be a good fit to support the proof obligations that would be specific to Rust.

I dug up a proposal from 2021 around bounds check hoisting in MIR, and from the discussion, details are pretty thorny [0]. It's narrower than general proofs but the frictions are very similar. The easiest example that shows HIR -> MIR difficulties is the part around `for i in 0..32 { a[i] = 1; }`. At the source level the range fact is super obvious, but after the for-loop/iterator lowering the MIR optimiser has to recover that `i` comes from exactly that range before it can turn 32 checks into the one hot-path check. Then it also would have to check for panic strategy to maintain the correct behaviour after optimisation.

[0] https://github.com/rust-lang/rust/issues/92327

Re: Performance of Rust Language [pdf]

#24

Earlier quoted context omitted.

Modern C++ metaprogramming materially impacts performance in practice. I’ve done performance engineering for decades in both C and modern C++ and I would assert that the difference isn’t arguable. The poor applicability of auto-vectorization is another area where C++ is strong. You can transparently codegen e.g. AVX512 from intrinsics directly in C++ in contexts that would be opaque to auto-vectorization and difficul…

So youre saying the metaprogramming facilities of C++ allow the compiler to better optimise high level human readable code more effectively than C. Thats a fair point and one I'd never even thought of before, I always thought C was faster because of things like v-tables and all that stuff.

In C++, nobody would want or need to use virtual functions in high-performance computational applications, while in the C language structures with virtual function tables that are accessed explicitly by the programmer are in widespread use wherever suitable, for instance in many popular open-source C programs, like the Linux kernel or the debugger gdb.

So the existence of virtual function tables is not a differentiator between C++ and C.

The data types with virtual function tables are just the implementation method for sum types that is dual to tagged unions. Both virtual function tables and tagged unions can be implemented in C and in most other programming languages that do not have intrinsic support for them, but they require explicit boilerplate code for invoking the virtual functions or for testing the union tags.

Which is the better of these 2 variants depends on the application. In high-performance computations, one does not use ambiguous data types, so normally none of these 2 is used. There are a few object-oriented programming languages where "everything is an object", i.e. any kind of data includes a virtual table pointer, but those are just incomplete programming languages, which do not have all the data types needed in practice, like also many early programming languages that had a unique data type, e.g. the original LISP I, which had only linked lists and no arrays, etc. C++ at least is a complete language, in which any kind of data type can be implemented, without overheads.

As you said previously, C has few restrictions in what it can do, so in theory it is almost always possible to write a C program almost exactly equivalent with any program written in another language, matching its speed, even if that may require a significant reorganization of the code, not a line to line translation.

Nevertheless, as the other poster said, the effort needed to write that equivalent program may be so high that it is not a realistic solution.

So in practice it is not unusual that at similar programming efforts a higher-level language like C++ frequently allows writing a faster program than C.

Re: Performance of Rust Language [pdf]

#25

Earlier quoted context omitted.

Modern C++ metaprogramming materially impacts performance in practice. I’ve done performance engineering for decades in both C and modern C++ and I would assert that the difference isn’t arguable. The poor applicability of auto-vectorization is another area where C++ is strong. You can transparently codegen e.g. AVX512 from intrinsics directly in C++ in contexts that would be opaque to auto-vectorization and difficul…

So youre saying the metaprogramming facilities of C++ allow the compiler to better optimise high level human readable code more effectively than C. Thats a fair point and one I'd never even thought of before, I always thought C was faster because of things like v-tables and all that stuff.

For example, you can do loop unrolling using C++ template meta-programming.

https://cpplove.blogspot.com/2012/07/a-generic-loop-unroller...

Of course, nothing beats hand written ffmpeg-style assembly which takes into account optimal register allocation, instruction scheduling, cache alignment, etc. for specific processor architectures.

Re: Performance of Rust Language [pdf]

#26
You end up needing something like refinement types to control the way you statically enforce bounds. That being said, there's stuff like https://flux-rs.github.io/flux/ which uses macros to layer a refinement type system on top of rust's. You can use it to statically eliminate bounds checks.

Re: Performance of Rust Language [pdf]

#27
I worked professionally with C, C++, Zig and Rust (in that order). My experience is that writing performant code is by far the easiest in C++, and by far the most difficult in C. Most of this, in practical experience, is due to ergonomics, in my opinion.

Templates in C++ benefit from being part of the core language, -- stick a `template` above your `class`, and you're in metaprogramming land. Stick a template specialization, and you've done a niche optimization. You didn't need a separate crate or a whole macro DSL. Variadic templates are also really really nice for monomorphizing N-ary generic functions. The duck typing of templates makes

This is precisely where I struggle with Rust the most -- monomorphization is limited within generics, so you end up going to the `proc_macro` hell, which involves a separate crate, a separate Cargo.toml, etc.

Zig seems like it would fit the bill -- and doing micro-optimizations within zig is surprisingly easy. The language's comptime facilities allow for really good niche optimizations -- however, the language also has some strange decisions. The allocator interface is notoriously a vtable, so a lot of the DOD optimizations that andrewrk has spoken numerously of (and to be clear -- I did learn a lot about DOD from his talks back when I was a wee engineer), raise one of my eyebrows.

C seems like it should be fast, but implementing any data structure, any generic algorithm in C is impossible. Either you're copy-pasting, or you're making macro DSLs. None of which is great.

---

To further talk about the C++ situation -- the monomorphic allocator interface was always awesome. Compared to Zig's vtables and Rust's nothing (up until a couple days ago), having a way to pass custom allocators with types was awesome. The new std::pmr::* interfaces and containers are also really exciting -- monomorphization, as beautiful as it is, does cost a lot -- refactoring it is not easy, compilation times are a mess. Sometimes the right tool is a vtable interface, and, C++ gives you those facilities.

And this is C++'s no1 problem when it comes to performance too -- it's a leviathan -- it'll give you the tools to write REALLY fast code, but it will also give you inheritance -- forget about your caches then.

When I was working at Tesla, there were some pretty gnarly vtable jumps in firmware (of all places), and I suspect part of that could've been alleviated if people knew more about CRTP.

So, here's where I land -- C++ really will give you the tools it can to let you write the fastest code possible. But it will also give you the tools to make your code really slow. Committee language means everyone in the committee needs to be happy.

Rust, on the other hand, is really designed to promote safe-but-very-fast practices -- had the firmware that I discussed used Rust, my guess is that we would've gravitated towards generics and monomorphization, rather than the heavy dynamic inheritance. C++, when it comes to performance, as it does to all other things, is a barreled shotgun. Rust's design almost always promotes the best available pattern and that's why I rarely reach out for C++.

Re: Performance of Rust Language [pdf]

#28
I was looking at Zig. It's performant, it's easier to reason about Zig code than Rust code but its api is unstable, there are a lot of breaking changes. Coding agents have a difficult time write proper Zig because of the breaking changes and of the small amount of new Zig code in the wild.

Re: Performance of Rust Language [pdf]

#29
post #11

Earlier quoted context omitted.

C++ you get templated generic algorithms that in practice no one really does with C because macros suck too much. So in C typically you'd have a runtime generic routine that doesn't inline. A classic example here is qsort() vs std::sort().

I explicitly acknowledged that: > The only candidate is using virtualization and void* pointers instead of monomorphized generics which some C code does for the lack of better options, but that's not a problem in Rust as well. But in fact, if speed is a concern to you, even in C you will use "templated" sorting (via macros or code generation).

> in practice no one really does with C because macros [and codegen] suck too much

Re: Performance of Rust Language [pdf]

#30
For a couple of years I have written an advanced software rasterizer (like in old PC games) using Rust. With a little bit of unsafe code it was doable and result performance was great. I only used unsafe in places mentioned in the article above, like in tight loops where the compiler's optimizer struggles to remove bounds checks and in a couple of places where CPU intrinsics were used.
Post reply on HN