The Rust Performance Book (2020)
nnethercote.github.io
nnethercote.github.io
The Rust Performance Book (2020) - https://news.ycombinator.com/item?id=32147999 - July 2022 (9 comments)
Also:
The Rust Performance Book - https://news.ycombinator.com/item?id=34877528 - Feb 2023 (1 comment)
The Rust Performance Book - https://news.ycombinator.com/item?id=25122178 - Nov 2020 (19 comments)
* This is in the FAQ: https://news.ycombinator.com/newsfaq.html.
C compilers do a pretty good job of optimizing these into something pretty efficient, and I imagine it helps that the size of the case values is constant, and it can be turned by the compiler into either a jump table lookup or an unrolled jmp depending on size, etc.
Rust enums, in contrast, can be variable sized at match time. And the elements can be nested a few levels deep. The obvious way to write an opcode execution loop in Rust -- a pattern match over an enum of opcodes -- I'm curious if this can be made to perform as well as in C/C++.
(I've been writing a virtual machine interpreter loop and so have a modest interest in seeing how this kind of thing, though I'm not optimizing for performance yet, so)
Don't see any explicit mention of this in this document.
I resorted to similar techniques in optimizing Befunge: https://github.com/serprex/Befunge (See bejit.c & marsh.c/marsh.h)
2. Rust enums are not variable sized, think of them as tagged C unions, where the Rust compiler can sometimes apply tricks, for example making Option<Vec<T>> the same size as Vec<T>
3. match can specialize for straight forward cases, when in doubt use https://godbolt.org
https://godbolt.org/z/nW79G1o71
Also my old 6502 then 68000 brain never learned to read x86 assembly. :-) I find ARM/Aarch64 easier to grock. I might do this same test on the M1 Mac I have handy here, but fiddle with compiler until I can get an absolutely tiny binary and then compare to C. Some day.
UPDATE: I should probably add that just about everything that is a move could turn into a copy, and Rust uses the move paradigm so often that is is easy to not even see what might turn into a copy. For small vars, it is no big deal, but for huge stack arrays it could add up. For example, if you init your buffer in a block and then return it as a binding, it will often be copied which can be painful.
let my_buffer = {
let buffer: [u8; 1_000_000] = [0; 1_000_000];
// Do stuff to init buffer here
buffer // This might end up mem copying the buffer out of the block
};
UPDATE 2: I had a very atypical use case for doing this. In general, I would agree large buffers should be put on the heap, but for my use case it made sense. To this day, I use a "large" (128K) buffer on the stack in my crate for performance reasons. I solved the above issues by using macros. My benchmarks are now solidly faster than heap allocation without the moves.(everything I said could be wrong, I'm not a compiler guy, I just didn't feel like qualifying every single statement)
No, the risk of stack overflow would be the typical reason
> Stacks unwind, so you can't pass around pointers to it
Sure you can, you just can't _return_ a reference to something on the stack, but within the data's lifetime you can pass refs to it to other functions
> And the stack is fast because it's often mostly in CPU cache, but that won't be true if you have 100 megs sitting there
The stack is also fast because allocation is free (pointer bump) vs. calling a free list allocator (aka malloc). Cache hits for a large buffer would likely be about the same. If the buffer were small, I would agree on the likelihood of it being in CPU cache.
> This is what the heap is for, it has nothing to do with Rust itself.
In many scenarios moves can and are optimized away, so yes, this has something to do with Rust. I will admit my use case was very contrived and is not a typical use case, but it was very valid (even if not typical).
For this particular example it seems that the rust compiler is failing to implement the equivalent of NRVO though, so it is not necessarily related to mives. Do rust semantics allow NRVO? There are no copy/move constructors with side effects, so the only issue is object identity.
Disclaimer: I'm just a c++ programmer, I know very little about rust.
using array = std::array<int, 10>;
array make(array* y) {
array result = {...};
assert(&result != y);
return result;
}
array x = make(&x);
Normally in C++ distinct objects (result and x) have distinct addresses, so the assert would be guaranteed not to fire. But NRVO is explicitly allowed in the standard to violate this guarantee, so result can overlap with x. In rust you likely cannot get the address of the not-yet-constructed x, but it might be possible to construct equivalent examples.As you hint at, guard probes without stack probes don't provide guaranteed segfaults -- sparse access to stack allocations can skip the guard and smash another stack (or whatever) easily enough. And stack probes for large sparse allocations firmly make stack allocations more expensive than heap allocations (since pages must be allocated for the whole allocation, even if it's unused), which is a pretty huge downside.