Earlier quoted context omitted.
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?
Go's escape analysis and why my function return worked
51–60 of 76 posts
Re: Go's escape analysis and why my function return worked
#52I don’t think this is confusing to the vast majority of people writing Go. In my experience, the average programmer isn’t even aware of the stack vs heap distinction these days. If you learned to write code in something like Python then coming at Go from “above” this will just work the way you expect. If you come at Go from “below” then yeah it’s a bit weird.
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…
I also came "from above".
Re: Go's escape analysis and why my function return worked
#53Earlier quoted context omitted.
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.
I am pretty sure the escape analysis doesn't affect the initial stack size. Escape analysis does determine where an allocation lives. So if your allocation is lower then what escape analysis considers heap and bigger then the initial stack size, the stack needs to grow.
What I am certain about, is that I have runtime.newstack calls accounting for +20% of my benchmark times (go testing). My code is quite shallow (3-4 calls deep) and anything of size should be on the heap (global/preallocated) and the code has zero allocations. I don't use goroutines either, it might me I still make a mistake or it's the overhead from the testing benchmark. But this obviously doesn't seem to be anything super unusual.
Re: Go's escape analysis and why my function return worked
#54Earlier quoted context omitted.
I don't see how this is coming at go "from below". even in C, the concept of returning a pointer to a stack allocated variable is explicitly considered undefined behavior (not illegal, explicitly undefined by the standard, and yes that means unsafe to use). It be one thing if the the standard disallowed it. but that's only because the memory location pointed to by the pointer will be unknown (even perhaps immediately…
The confusion begins the moment you think Go variables get allocated on the stack, in the C sense. They don't, semantically. Stack allocation is an optimization that the Go compiler can sometimes do for you, with no semantics associated with it. The following Go code also works perfectly well, where it would obviously be UB in C: func foo() *int { i := 7 return &i } func main() { x := foo() fmt.Printf("The int was: %…
Of course the compiler could inline it or do something else but semantically its a copy.
Re: Go's escape analysis and why my function return worked
#55Earlier quoted context omitted.
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.
I think we mix things up here. But be aware of my newbie knowledge. I am pretty sure the escape analysis doesn't affect the initial stack size. Escape analysis does determine where an allocation lives. So if your allocation is lower then what escape analysis considers heap and bigger then the initial stack size, the stack needs to grow. What I am certain about, is that I have runtime.newstack calls accounting for +20…
Re: Go's escape analysis and why my function return worked
#56Are you sure this is what's happening? Looks to me like the slice object is returned by value, and the array was always on the heap. See https://go.dev/play/p/Bez0BgRny7G (the address of the slice object changed, so it's not the same object on the heap) Sure, Go has escape analysis, but is that really what's happening here? Isn't this a better example of escape analysis: https://go.dev/play/p/qX4aWnnwQV2 (the object…
Depending on escape analysis, the array underlying the slice can get allocated on the stack as well, if it doesn't escape the function context. Of course, in this case, because we are returning a pointer to it via the slice, that optimization isn't applicable.
Both arrays in this example seem to be on the heap.
Re: Go's escape analysis and why my function return worked
#57Earlier quoted context omitted.
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…
This could probably be implemented as an expensive comment-driven lint during compilation.
Re: Go's escape analysis and why my function return worked
#58Earlier quoted context omitted.
> 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.)
What you wrote is not the same in C and Go, because GC and escape analysis. But 9rx is also correct that what OP wrote is the same in C and Go.
So OP almost learned about escape analysis, but their example didn't actually do it. So double confusion on their side.
Re: Go's escape analysis and why my function return worked
#59Earlier quoted context omitted.
I’m not sure what you mean? Are you asking for information about what it is or how to use it?
I never heard of "stack analyzer" and didn't get meaningful results for it, do you mean escape analysis?