12 ms·
I have done a bit of work with large arrays on the stack and in general this can be a problem area for Rust atm (or was a year ago when I looked into it). The r
by nu11ptr 3y ago
I have done a bit of work with large arrays on the stack and in general this can be a problem area for Rust atm (or was a year ago when I looked into it). The reason is Rust uses a lot of move semantics, but under the covers I discovered many of these are not being optimized away and were actually mem copies. If you can keep your stack buffer in one spot and only pass refs to it I suspect you might be fine, but "moving" it is likely to incur a large performance penalty in some cases if not optimized away into a no-op.
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.
- pkulak 3y agoIsn't this the entire reason you shouldn't use the stack for large amounts of data? Stacks unwind, so you can't pass around pointers to it. 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. This is what the heap is for, it has nothing to do with Rust itself. (everything I said could be wrong, I'm not a compiler guy, I just didn't feel like qualifying every single statement)
- jvanderbot 3y agoI agree to a point. There was a "stack is faster" sentiment taught in school which is obviously an oversimplification for actual working programs.
- nu11ptr 3y ago> Isn't this the entire reason you shouldn't use the stack for large amounts of data 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).
- gpderetta 3y agoSo generally you have to copy arrays, you can't meaningfully move them (you can move each element of course). For vectors with heap allocated backing store of course you only need to copy the pointer to heap. 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.
- lalaithion 3y agoNRVO is a completely transparent optimization that Rust tries to do whenever possible. I don’t know what sort of case you can get into with object identity but I suspect it’s obviated by making moved-out-of values untouchable at compile time.
- gpderetta 3y agoC++ example: 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.