Escape Analysis в Go: почему переменная оказывается в куче

В Go часто говорят: "Если переменная локальная, она находится в стеке." Но это не всегда так. Например: func createUser() *User { user := User{} return &user } user - локальная переменная. Но после выхода из createUser() указатель на неё должен продолжать работать. Поэтому Go может переместить user в куче (heap). Как это проверить? У компилятора есть Escape Analysis: go build -gcflags="-m" . Например: ./main.go:5:2: moved to heap: user Компилятор буквально говорит: moved to heap А здесь: func sum() int { x := 10 y := 20 return x + y } x и y не нужно сохранять после выхода из функции. Поэтому им не нужно escape из функции. Зачем это знать? Потому что heap allocation может увеличивать нагрузку на GC. Особенно если allocation происходит внутри горячего цикла: for i := 0; i < 1_000_000; i++ { process(...) } Но moved to heap сам по себе ещё не означает проблему. Например, вернуть указатель на объект из функции может быть абсолютно нормальным дизайном. Поэтому я обычно смотрю на Escape Analysis вместе с benchmark: go test -bench=. -benchmem Например: BenchmarkCreateUser-20 1000000 1200 ns/op 256 B/op 2 allocs/op Если после изменения кода получаем: 1000000 900 ns/op 128 B/op 1 allocs/op тогда уже есть конкретный результат, который можно обсуждать. Практический подход Если видишь лишние allocations: 1\. benchmark ↓ 2\. -benchmem ↓ 3\. Escape Analysis ↓ 4\. найти причину allocation ↓ 5\. изменить код ↓ 6\. benchmark ещё раз И только если показатели реально улучшились, изменение имеет смысл. Важно помнить: Escape Analysis показывает, что решил компилятор. Она не говорит: "этот код плохой". Она помогает понять, откуда взялись allocations, а уже benchmark и profiler показывают, действительно ли это проблема. Именно поэтому -gcflags="-m" особенно полезен, когда пытаешься уменьшить allocs/op, нагрузку на GC и latency в горячем коде.