Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Go uses escape analysis at compile time to determine where to allocate each variable. If the variable is shown to not 'escape' the current goroutine's stack, then it is can be allocated on the stack. Otherwise it is said that the value 'escapes' to the heap.

You can see what the escape analysis has determined for each variable by building like so: go build -gcflags "-m"

Unfortunately it can be hard to know by just looking at code if something will be stack or heap allocated. You can allocate a struct and take its pointer, and have it still end up on the stack, for instance (which is good). You can also have seemingly obvious flows for a variable that the analyzer can't reason about and it escapes when you didn't think it would. You really just have to run the tool to see if you want to be sure.



> You can also have seemingly obvious flows for a variable that the analyzer can't reason about and it escapes when you didn't think it would. You really just have to run the tool to see if you want to be sure.

And this is why "get programmers to manually optimize their code to please escape analysis" is not a reasonable memory management strategy.


Nobody is suggesting that. Go has very good profiling support, and that's how performance optimization starts.

If profiling finds that there are too many allocations on a hot path, you might want to see why they escape to heap. But that's a last resort strategy.


This sounds very much like "write code to please escape analysis", but with a profiling step in front of it.


No, there's a difference. Profiling will often show you where you're doing allocations or calling code that does allocations in the most often used code. Profiling shows where to reduce those allocations to get the biggest improvements. When making the changes, you don't try to reduce escapes, you reduce allocations.

Have a look at [1] and go down to where it says " To find out why the garbage collector is running so much".

[1] https://blog.golang.org/profiling-go-programs


I am not disagreeing with you but am confused on the issue. Java also has escape analysis, so I wonder what makes the difference here.


That is a good point. I don't know, so I've been googling around and came across this awesome article:

https://shipilev.net/jvm-anatomy-park/18-scalar-replacement/

It essentially says that java doesn't (ever?) allocate Objects on the stack, but it sometimes approximates it by just storing the scalar values (that would have been part of the object) on the stack, and never allocating the object (with its headers) at all. The end result is similar.

It also has a mention of an easy way to confuse the escape analysis (though Go's escape analysis has many pitfalls as well).

I'm speculating, but it could be partly that the escape analysis and resulting scalar replacement is a hotspot optimization, and therefore the Java GC has to be well tuned to handle non-jitted items. It may have something to do with what types of failure cases each of the languages have with their analysis.


The HotSpot JVM does indeed do escape analysis. The primary difference is that, in Go, you can put structs and arrays inside other structs.

I tend to be skeptical of the claims that are often made that Go data structures have different GC-relevant characteristics from those of Java, or especially C#.


Can you elaborate why being able to embed objects within another void this hypothesis?


Java used to default to heap allocation for pretty much everything, but added escape analysis as an optimization. For Go escape analysis as been part of it since day 1. Basically Java is heap by default that uses the stack when it can. Go uses the stack unless it determines it must use the heap.


Most go data is value types that can be assumed to be on the stack, unless you introduce a pointer explicitly or use one of the referenceish types like interface, string, array or map. Its very easy for both the programmer and the compiler to see when data escapes.

Java on the other hand is pathologically bad for escape analysis, outside of ints and floats literally every object in java is presumed to be on the heap. Even C# has less heap pressure than java typically because of more value type options, and not type erasing greatly improves the JIT's escape analysis. After sufficient JITing Java may reach go level of garbage generation, it may not: but its not deterministic.


That's garbled in several ways I'm afraid.

Firstly type erasure and escape analysis are un-related. Whether an object escapes or not is unrelated to things like whether it's a List<Integer> or List<int>.

Secondly .NET doesn't do escape analysis at all:

https://github.com/dotnet/coreclr/issues/1784

Thirdly HotSpot will happily escape analyze and then scalar replace regular objects, it isn't limited to ints and floats. It can make a big difference in many situations.

Fourthly and finally, if it were "easy to see where data escapes" in Go there wouldn't need to be tools to help the programmer find out, and Go developers wouldn't spend time manually tweaking to try and ensure the optimisation works.


Type erasure and escape analysis are not entirely unrelated. If you have an array of pairs and store a pair into that array then the pair is considered to escape in Java because you're actually storing a pointer to your original pair. In C# that array can store a true array of pairs rather than an array of pointers to pairs. A pair you store gets copied into the array, so the original pair does not have to be considered to escape and can be stored on the stack and deallocated when the function exits.

Although C# does not have escape analysis, its value type vs reference type distinction serves roughly the same purpose, but under manual control of the programmer.


Only if you are speaking about Hotspot.

GraalVM and other Java JITs (J9, Azul, PTG, ...) have much better escape analysis algorithms.


In Go you can pass and return by value. That's why it's far more effective in Go and of very limited applicability in Java. For instance if you're working with a pair of numbers and only use that pair locally in one function, then Java escape analysis works. If that pair was returned from another function or passed to another function then escape analysis fails in Java but still works in Go. In Go the escape analysis only fails if you explicitly make a pointer to that pair of numbers and pass that to another function.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: