Stack Mechanics & Optimizations

Understanding Memory: Stack vs. Heap in Go

To write high-performance Go, you must understand where your variables live. Go abstracts memory management, but it doesn’t eliminate the physics of hardware.

The Two Worlds

1. The Stack

  • What it is: A pre-allocated, contiguous block of memory for each goroutine (starts at 2KB).
  • Allocation: Instant (move a pointer).
  • Deallocation: Instant (move a pointer back).
  • GC Cost: Zero. The Garbage Collector (GC) does not scan the stack.
  • Cache Locality: Excellent.

2. The Heap

  • What it is: A global pool of memory for dynamic allocations.
  • Allocation: Slow (search for free block, possibly lock).
  • Deallocation: Garbage Collected (expensive scan).
  • GC Cost: High. Pointers on the heap must be traced.
  • Cache Locality: Poor (scattered).

Go’s Optimization Goal

Keep everything on the stack.

If the compiler can prove a variable’s life cycle is contained within a function (it doesn’t “escape”), it allocates it on the stack.

Stack Growth (Contiguous Stacks)

Goroutines start with 2KB stacks. If a function call needs more space, the runtime: 1. Allocates a larger stack (e.g., 4KB). 2. Copies everything from old stack to new stack. 3. Updates pointers to the new stack. 4. Frees the old stack.

Note: This “copying” is why pointers to stack variables are safe only if they don’t escape. If they escaped to the heap, moving the stack would invalidate external pointers.

2026: The “Mid-Stack” Inlining

Recent Go versions have become aggressive about inlining function calls mid-stack. * If UserFunc calls AllocFunc, and AllocFunc is small, the compiler copies AllocFunc’s body into UserFunc. * This removes the function call overhead and allows variables that might have escaped (due to being return values) to stay on the stack.

Visualization

Variable Scenario Destination Why?
x := 42 Stack Never leaves function references.
y := &x (used locally) Stack Address taken, but scope is local.
return &x Heap Escapes to caller; stack frame dies on return.
fmt.Println(x) Heap (usually) fmt.Println takes interface{}, which often causes escape.
make([]byte, 1024) Stack Size known at compile time, fits in stack.
make([]byte, n) Heap Size unknown at compile time.

Practical Rule

Don’t fear the Heap, but respect the Stack. If you are writing a hot loop (a game loop, a high-frequency trading handler), ensure your temporary variables do not escape to the heap.

Use detailed analysis tools to verify this (covered in next chapter).