Why isn't Rust even faster than C++? Comparing heap vs stack allocation

Rust can set it's own stack size though when compiling the program, so size isn't an issue:
https://stackoverflow.com/questions/18909395/how-do-i-increase-the-stack-size-when-compiling-with-clang-on-os-x

Therefore, I don't see a reason to keep stack sizes small. Increasing stack size does mean smaller heap but if you use the stack more.. no problems.

Even if stack sizes per function are determined per function at compile time, they can still be larger.

Clearly Box, Rc, and Arc need to be on stack, I agree. But String and Vec.. I don't see a reason not to put them on the stack when you know they'll be temporary too. I could see the argument that compared to the functions performed on/with String and Vec the alloc time is tiny and not worth worrying about.. maybe. Would need to benchmark that to know for sure but suspect it's usually true.

The operating system typically has its own limits on stack size. Rust can only increase it up to a point. (For common modern platforms I believe there is often a soft limit around 8 MB and a hard limit around 32ā€“64MB.)

Certainly. There are crates like smallvec and arrayvec that are widely used for keeping vectors on the stack when possible, and this is a big win in some programs. But they also have downsides. They waste more space when empty; they are more expensive to move, and in the case of smallvec, you need to branch on every access to the contents. (Note: I'm the primary maintainer of the smallvec crate, so Iā€™m a big proponent of keeping things on the stack when possible!)

This is what I mean when I say that Rust gives you the choice. You can explicitly use heap-based types like Box and Vec when you want a heap allocation. Or you can use built-in types like structs/enums/arrays/etc. which never allocate on the heap on their own. Or you can combine them in interesting ways like smallvec.

4 Likes

Maybe I'm too noob and am missing a point but none of this correlates with my understanding of Rust so far.

As far as I know the actual data contained in Box, Rc, Arc, String and Vec are all allocated on the stack, always. So "I don't see a reason not to put them on the heap when you know they'll be temporary too." does not add up to me.

Further, whatever you put on the heap you are going to need something on the stack that points to it. Otherwise you have no idea where it is. The compiler knows where it has put those actual Box, Rc, Arc, String and Vec structs on the stack at compile time. They in turn know where their data content has been allocated on the heap at run time.

All of this sounds very much like what goes on with C++, String, Vector,whatever smart pointer, etc.

In short I don't see what all the fuss is about.

If it happens your program is dealing with billions of small objects and all that allocation/deallocation is becoming a significant overhead then it is time to rethink your architecture. Say keeping those objects in an array that is only allocated once and resized rarely.

This is what game devs do in C++ with their "Entity Component System" https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/the-entity-component-system-c-game-design-pattern-part-1-r4803/. Something I only learned of recently whilst following Rust discussions.

Whoops meant don't see a reason not to put them on the stack, not heap.

@mbrubeck didn't know about OS stack limits.. makes sense then

It's true, very C++ like just thought that using stack instead of heap was supposed to be a big Rust selling point enabled by borrowing.

It is- but much of that is around the conceptual stack discipline, not just the mechanics of which addresses map to which data. When people say things like that they are usually trying to make point similar to, for example, this blog post on leveraging Rust's guarantees to make things more efficient.

That is, Rust not only helps you when you are literally putting your actual data in the stack frame itself, but also when your data is on the heap and is merely managed by something on the stack, like a Vec or a String. In C++ you need to be very careful when you start using unchecked vanilla pointers instead of std::unique_ptr/std::shared_ptr/std::vector/std::string, and so people often take the simpler-but-costlier route of copying or ref-counting data to avoid forcing everyone to continually run a borrow checker in their head.

1 Like

You can have a stack-allocated string, just not using String, because String specifically uses the heap. Same for Vec.

The main problem you tend to run into is that you can't grow a stack allocation using a similar approach to the heap. Resizing a stack allocation in-place is generally impossible, and a new allocation can only be created with a shorter lifetime than any existing one, which means there's no way to do a realloc in a deeper stack frame. So you can't implement methods like push_str or extend (unless you fall back to using the heap, as smallvec does).

But you're perfectly allowed to create strings on the stack and pass around &strs that refer to them. It's not always quite as convenient as using String because String provides that convenience by using the heap.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.