liveBook · Manning

Memory allocation

As explained in 100 Go Mistakes and How to Avoid Them

Memory allocation is the process of reserving memory for data or runtime objects.

Returning a pointer to avoid copying can make code slower. Go allocates a variable on the stack when it can prove the value will not be referenced after its function returns. Otherwise, the value escapes to the heap. section 12.5.1 section 12.5.2

Return a value or a pointer?

With inlining disabled, `sumValue` returns the sum as an `int`; `sumPtr` returns `&z`, so `z` must remain valid after the call. The pointer version therefore requires a heap allocation. A pointer is appropriate when the value needs to be shared, but it is not automatically a faster substitute for a copy. section 12.5.1

func main() {
    a := 3
    b := 2
 
    c := sumPtr(a, b)
    println(*c)
}
 
//go:noinline
func sumPtr(x, y int) *int {     #1
    z := x + y
    return &z
}

Inspect the returned address: `z` cannot disappear with the function’s stack frame because the caller still uses it. section 12.5.1

Each goroutine has its own stack; goroutines share the heap. Stack allocation is quick and stack frames clean themselves up. Heap allocation takes more work and leaves cleanup to the garbage collector, so unnecessary heap allocations can affect data-intensive applications. section 12.5.1 section 12.9

BenchmarkSumValue-4   992800992    1.261 ns/op   0 B/op   0 allocs/op
BenchmarkSumPtr-4     82829653     14.84 ns/op   8 B/op   1 allocs/op

The reported benchmark shows the value-returning function at 0 allocations per operation and the pointer-returning function at 1. These results use the examples with inlining disabled; measure your own code rather than treating the timings as a general rule. section 12.5.1

When allocations matter, choose a change that fits the code:

  • Check whether an API unnecessarily makes a value escape. Passing a pointer into a function does not by itself require a heap allocation; the value may stay on the caller’s stack. section 12.5.2
  • Use `strings.Builder` for suitable string concatenation, avoid unnecessary `[]byte`-to-string conversions, and preallocate slices or maps when their size is known. section 12.6
  • Consider compiler optimizations and `sync.Pool` where appropriate, while keeping readability and semantics ahead of premature optimization. section 12.6 section 12.0

To test an assumption about an escape, inspect compiler output with `go build -gcflags "-m=2"`. Allocation reports from `testing.B.ReportAllocs` count heap allocations, not stack allocations. Diagnose the costly path before changing an API solely to reduce allocations. section 12.5.2 section 12.5.1

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains stack frames, heap escapes, and their costs through contrasting functions and benchmarks, then gives allocation-reduction options.
Examples
Contrasts `sumValue` with `sumPtr`, reports their allocations per operation, and shows compiler output identifying an escape.
What it assumes
Assumes familiarity with Go functions and pointers while explaining stack-versus-heap allocation and escape decisions.
What you can do after
Diagnose/debug with it by checking compiler escape decisions and benchmark allocation counts before changing code.
How it connects
Connects memory allocation to escape analysis, pointers, goroutine stacks, garbage collection, benchmarking, and `sync.Pool`.

Also explained in

Compare how 11 books explain memory allocation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.