Go’s compiler escape analysis is such a simple yet powerful concept for performance.
It boils down to the difference between the stack and the heap. So let us explore that.
When a function is invoked it gets a new frame in the stack with all its local variables tucked in next to each other, if we know anything about paging we know if the CPU reads one local variable from memory in that function we get a whole page and L cache it and chances that we have access to all functions variables are very high. This avoids additional RAM accesses. (This reminds me of range scans in b+trees where we avoid disk io instead.)
The beauty of the stack is when the function returns, destroying the frame is as simple as changing the stack pointer.
Compare this to the heap, a shared area between all functions in the process where we can dynamically allocate memory using
new. So technically there is no guarantee that variables and objects live next to each other and thus chances of cache locality are lower as random memory reads increases. further more memory must be deallocated manually or we risk memory leaks.
So what Go does is to avoid heap allocation when possible. if it sees you create an object/variable in a function, as long as that object is only accessed in that function and it doesn’t escape to other functions, Go can safely allocates the memory in the stack instead of the heap. Otherwise it goes to the heap. It does that by statically analyzing the code.
Remember, the less objects in the heap the less Go’s garbage collector has to work to manage those objects and free them. So more performance overall, from cache locality in stack and less GC management.
The problem I see with this is striking a balance when it comes to object size since stack space is limited.
I strongly recommend watching this video titled “allocator wrestling” talk by Eben Freeman .
Read a community post on YouTube by @hnasr.
You can read the discussion when David Thomas on Google Groups asked about this .