Live data from Hacker News

Allocating on the Stack

go.dev

51–59 of 59 posts

Re: Allocating on the Stack

#51

It's kind of like the small string optimization you see in C++[1] where all the string metadata to account for heap pointer, size and capacity is union'ed with char*. Getting the stack allocation doesn't costs extra memory, but does cost a bit check. Not sure if slices in go use the same method. 32 bytes is a lot so maybe they fattened slice representations a bit to get a bit more bang for your buck? [1] https://gith…

> It's kind of like the small string optimization you see in C++ ...

Agreed. These types of optimizations can yield significant benefits and are often employed in language standard libraries. For example, the Scala standard library employs an analogous optimization in their Set[0] collection type.

0 - https://github.com/scala/scala3/blob/88438e2c6e6204e12666067...

Re: Allocating on the Stack

#52
post #36

Optimizations like these are so cool. I love seeing higher level languages take advantage of their high level-ness

Agreed. There's quite a bit of room for optimization if your language design allows for it. Plus you have flexibility to make different tradeoffs as computer architectures and the cost of various operations change over time.

Re: Allocating on the Stack

#53

If I had a nickel for every article about avoiding implicit boxing in gc-heap languages...

...you would have the same balance as before, because this is not an article about implicit boxing. ;-)

"The reason is that the compiler decided to allocate the backing store on the stack. Because it knows what size it needs to be (10 times the size of a task) it can allocate storage for it in the stack frame of process2 instead of on the heap1. Note that this depends on the fact that the backing store does not escape to the heap inside of processAll."

This is definitionally small-object boxing optimizations.

Re: Allocating on the Stack

#54

Earlier quoted context omitted.

...you would have the same balance as before, because this is not an article about implicit boxing. ;-)

"The reason is that the compiler decided to allocate the backing store on the stack. Because it knows what size it needs to be (10 times the size of a task) it can allocate storage for it in the stack frame of process2 instead of on the heap1. Note that this depends on the fact that the backing store does not escape to the heap inside of processAll." This is definitionally small-object boxing optimizations.

... no, that's not boxing. Boxing is storing value types on the heap.

Re: Allocating on the Stack

#56

> ... > On the third loop iteration, the backing store of size 2 is full. append again has to allocate a new backing store, this time of size 4. The old backing store of size 2 is now garbage. Correct me if I'm wrong, but isn't this a worst-case scenario? realloc can, iirc, extend in place. Your original pointer is still invalid then, but no copy is needed then. Unless I'm missing something? Equally, what happens to…

[]task is a pointer to a range of elements. TFA says if you initialize it to point to a new array of 10 elements, that array of 10 elements may be stack–allocated. If you allocate another array dynamically, that one won't be.

Re: Allocating on the Stack

#57
I want to like this, and it's directionally good work...

But it's hard to see this as very useful unless we also start to see some increases in legibility, and ways to make sure these optimizations are being used (and that textually minor changes don't cause non-obvious performance regressions).

I've written a lot of golang code that was benchmarked to shreds, and in which we absolutely cared about stack-vs-heap allocations because they were crucial to overall performance. I've spent a lot of time pouring over assembler dumps, because grepping those for indications of new object creation was sometimes clearer (and certainly more definitive) than trying to infer it from the source code level. The one thing I've learned from this?

It's very, very easy for all those efforts to come to naught if the rules change slightly.

And it's very, very, VERY easy for a co-maintainer on a project to stroll in and make seemingly textually trivial changes that have outsized impacts. (I'm looking at inliner thresholds, specifically. Hoo boy.)

The best balm we have for this right now is writing benchmarks and making sure they report zero allocs. (Or unit tests using the runtime memstats harness; potato potato.) But that is a very fragile balm, and relatively complex to maintain, and (if DX is considered) is not textually local to the code in question -- which means someone changing the code can easily miss the criticality of a section (until the tests yell at them, at least).

I really yearn for some markup that can say "I expect this code to contain zero heap allocations; please flunk the compile if that is not the case".

Re: Allocating on the Stack

#59

This article is about Go, but I wonder how many C/C++ developers realize that you've always had the ability to allocate on the stack using alloca() rather than malloc(). Of course use cases are limited (variable length buffers/strings, etc) since the lifetime of anything on the stack has to match the lifetime of the stack frame (i.e the calling function), but it's super fast since it's just bumping up the stack point…

alloca()'s availability and correctness/bugginess is platform dependent, so it probably sees only niche usage since it's not portable. Furthermore, even its man page discourages its use in the general case: >The alloca() function is machine- and compiler-dependent. Because it allocates from the stack, it's faster than malloc(3) and free(3). In certain cases, it can also simplify memory deallocation in applications th…

Yeah, all stack overflow behavior is undefined in C/C++, although both on Linux and Windows you'll get a page fault (SEGV) on stack overflow since memory beyond the stack is deliberately unmapped.
Post reply on HN