package main
func main() {
u := make([]int, 8191) // Does not escape to heap
_ = u
v := make([]int, 8192) // Escapes to heap = 64kb
_ = v
var w [1024 * 1024 * 1.25]int
_ = w
var x [1024 * 1024 * 1.25+1]int // moved to heap > 10 mb
_ = x
}
The output of the escape analysis is as follows
./main.go:3:6: can inline main
./main.go:13:6: moved to heap: x
./main.go:4:11: main make([]int, 8191) does not escape
./main.go:7:11: make([]int, 8192) escapes to heap
When slice size hits 64 KB, its memory allocation is on the heap. But when array size goes beyond 10 mb then it is moved to heap.
Why Golang waits for array size to go beyond 10 MB before moving to heap while same does not apply to slice?
Stack allocation is computationally easier to manage, so it is favored over the heap if possible. Cleaning up a stack frame can be as simple as popping the top of the stack into the CPU’s program counter whereas a heap allocation requires the garbage collector to track and follow pointers.
Based on my skim through the escape analysis code, the significant distinction between values and pointers seems to just be their semantics. A pointer variable might be backed by a stack-allocated value and a value variable might actually live in the heap. The difference is just the language semantics and the way the runtime of the Go reference implementation optimizes them.
If you want a large value to be on the stack, you “hint” that to the compiler by making it an explicit value variable. It will still escape to the heap if it has to, but that’s the way you signal your intention to the compiler. Same with how p := new(int) signals that p might escape to the heap (if it doesn’t need to be on the heap, why new it?), but the compiler might optimize it to a local variable if it can prove that it can while keeping the program correct and memory-safe.