Live data from Hacker News

Go's escape analysis and why my function return worked

bonniesimon.in

41–50 of 76 posts

Re: Go's escape analysis and why my function return worked

#41
post #32

Earlier quoted context omitted.

The point the GP was making was that the following Go snippet: func foo() { x := []int { 1 } //SNIP } Could translate to C either as: void foo() { int* x = malloc(1 * sizeof(int)); x[0] = 1; //... } Or as void foo() { int data[1] = {1}; int *x = data; //... } Depending on the content of //SNIP. However, some people think that the semantics can also match the semantics of the second version in C - when in fact the sem…

The semantics are clearly defined as being the same as the C code I posted earlier. Why would one try to complicate the situation by thinking that it would somehow magically change sometimes?

Because people hear that Go supports value types and so is more efficient than Java because it can allocate on the stack*, and so they start thinking that they need to manage the stack.

* Of course, in reality, Java also does escape analysis to allocate on the stack, though it's less likely to happen because of the lack of value types.

Re: Go's escape analysis and why my function return worked

#43
post #37

Earlier quoted context omitted.

Go has been my primary language for a few years now, and I’ve had to do extra work to make sure I’m avoiding the heap maybe five times. Stack and heap aren’t on my mind most of the time when designing and writing Go, even though I have a pretty good understanding of how it works. The same applies to the garbage collector. It just doesn’t matter most of the time. That said, when it matters it matters a lot. In those t…

Why have you had to avoid the heap? Performance concerns?

For me, avoiding heap, or rather avoiding gc came when I was working (at work) on backend and web server using Java, and there was default rule for our code that if gc takes more than 1% (I don't remember the exact value) then the server gets restarted.

Coming (back then) from C/C++ gamedev - I was puzzled, then I understood the mantra - it's better for the process to die fast, instead of being pegged by GC and not answering to the client.

Then we started looking what made it use GC so much.

I guess it might be similar to Go - in the past I've seen some projects using a "baloon" - to circumvent Go's GC heuristic - e.g. if you blow this dummy baloon that takes half of your memory GC might not kick so much... Something like this... Then again obviously bad solution long term

Re: Go's escape analysis and why my function return worked

#44
post #32

Earlier quoted context omitted.

The semantics are clearly defined as being the same as the C code I posted earlier. Why would one try to complicate the situation by thinking that it would somehow magically change sometimes?

Because people hear that Go supports value types and so is more efficient than Java because it can allocate on the stack*, and so they start thinking that they need to manage the stack. * Of course, in reality, Java also does escape analysis to allocate on the stack, though it's less likely to happen because of the lack of value types.

I don't see the difficulty here. The slice is to be thought of as value type, as demonstrated in the C version. Just like in C, you can return it from a function without the heap because it is copied.

Re: Go's escape analysis and why my function return worked

#45
post #21

Earlier quoted context omitted.

I am currently learning go and your comment made me sort some things out, but probably in a counterintuitive way. Assuming to everything allocates on the heap, will solve this specific confusion. My understanding is that C will let you crash quite fast if the stack becomes too large, go will dynamically grow the stack as needed. So it's possible to think you're working on the heap, but you are actually threshing the…

Go won’t put large allocations on the stack even if escape analysis would permit it, so generally speaking this should only be a concern if you have very deep recursion (in which case you might have to worry about stack overflows anyway).

Escape analysis accounts for size, so it wouldn't even permit it.

The initial stack size seems to be 2kb, a more on a few systems. So far I understand you can allocate a large local i.e. 8kb, that doesn't escape and grow the stack immediately. (Of course that adds up if you have a chain of calls with smaller allocs). So recursion is certainly not the only concern.

Re: Go's escape analysis and why my function return worked

#46

Earlier quoted context omitted.

I like Go a lot, but I often wish we could be more explicit about where allocations are. It’s often important for writing performant code, but instead of having semantics we have to check against the stack analyzer which has poor ergonomics and may break at any time. But yeah, to your point, returning a slice in a GC language is not some exotic thing.

I think I would like a “stackvar” declaration that works the same as “var” except my code won’t compile if escape analysis shows it would wind up on the heap. I say that knowing I’m not a language designer and have never written a compiler. This may be an obviously bad idea to somebody experienced in either of those. I commented elsewhere on this post that I rarely have to think about stacks and heaps when writing Go…

Escape analysis sends large allocation to the stack. The information is there.

Re: Go's escape analysis and why my function return worked

#47
post #26
post #3

This seems to be a persistent source of confusion. Escape analysis is just an optimization. You don't need to think about it to understand why your Go code behaves the way it does. Just imagine that everything is allocated on the heap and you won't have any surprises.

> This seems to be a persistent source of confusion. Why? It is the same as in C. #include #include struct slice { int *data; size_t len; size_t cap; }; struct slice readLogsFromPartition() { int *data = malloc(2); data[0] = 1; data[1] = 2; return (struct slice){ data, 2, 2 }; } int main() { struct slice s = readLogsFromPartition(); for (int i = 0; i

What confuses people is

    int *foo(void) {
        int x = 99;
        return &x; // bad idea
    }
vs.

    func foo() *int {
        x := 99
        return &x // fine
    }
They think that Go, like C, will allocate x on the stack, and that returning a pointer to the value will therefore be invalid.

(Pedants: I'm aware that the official distinction in C is between automatic and non-automatic storage.)

Re: Go's escape analysis and why my function return worked

#48
post #21

Earlier quoted context omitted.

Go won’t put large allocations on the stack even if escape analysis would permit it, so generally speaking this should only be a concern if you have very deep recursion (in which case you might have to worry about stack overflows anyway).

Escape analysis accounts for size, so it wouldn't even permit it. The initial stack size seems to be 2kb, a more on a few systems. So far I understand you can allocate a large local i.e. 8kb, that doesn't escape and grow the stack immediately. (Of course that adds up if you have a chain of calls with smaller allocs). So recursion is certainly not the only concern.

For that to be a problem you either have to have one function that allocates an enormous number of non-escaping objects below the size limit (if the Go compiler doesn't take the total size of all a function's non-escaping allocations into account – I don't know), or a very long series of nested function calls, which in practice is only likely to arise if there are recursive calls.

Re: Go's escape analysis and why my function return worked

#49

Earlier quoted context omitted.

I like Go a lot, but I often wish we could be more explicit about where allocations are. It’s often important for writing performant code, but instead of having semantics we have to check against the stack analyzer which has poor ergonomics and may break at any time. But yeah, to your point, returning a slice in a GC language is not some exotic thing.

Can you elaborate on the stack analyzer? All I could figure out was to see runtime.morestack calls that affected the runtime, but as far I remember the caller timings did exclude the cost. Having a clearer view of stack grow rates would be really great.

I’m not sure what you mean? Are you asking for information about what it is or how to use it?

Re: Go's escape analysis and why my function return worked

#50
post #37

Earlier quoted context omitted.

Go has been my primary language for a few years now, and I’ve had to do extra work to make sure I’m avoiding the heap maybe five times. Stack and heap aren’t on my mind most of the time when designing and writing Go, even though I have a pretty good understanding of how it works. The same applies to the garbage collector. It just doesn’t matter most of the time. That said, when it matters it matters a lot. In those t…

Why have you had to avoid the heap? Performance concerns?

Garbage Collection.

The content of the stack is (always?) known at compile time; it can also be thrown away wholesale when the function is done, making allocations on the stack relatively cheaper. These FOSDEM talks by Bryan Boreham & Sümer Cip talk about it a bit:

- Optimising performance through reducing memory allocations (2018), https://archive.fosdem.org/2018/schedule/event/faster/

- Writing GC-Friendly [Go] code (2025), https://archive.fosdem.org/2025/schedule/event/fosdem-2025-5...

Speaking of GC, Go 1.26 will default to a newer one viz. Green Tea: https://go.dev/blog/greenteagc

Post reply on HN