Что такое ‘escape analysis’ (анализ побега) в Go и когда переменные ‘убегают’ в кучу?

Ответ

Escape analysis (анализ побега) — это процесс, в ходе которого компилятор Go во время компиляции определяет, где должна быть размещена переменная: в стеке (stack) или в куче (heap).

Если компилятор может доказать, что время жизни переменной строго ограничено рамками вызывающей функции, она размещается на стеке. Если же на переменную есть ссылки, которые могут существовать и после завершения функции (т.е. переменная 'убегает' из её области видимости), она 'эвакуируется' или, более точно, аллоцируется в куче.

Почему это важно?

  • Стек: Очень быстрая память. Выделение и освобождение памяти на стеке — это простое смещение указателя стека. Не требует участия сборщика мусора (GC).
  • Куча: Более медленная память. Выделение памяти в куче — более сложная операция, и объекты в куче должны отслеживаться и очищаться сборщиком мусора, что создает дополнительную нагрузку.

Основные причины 'побега' (эвакуации в кучу):

  1. Возврат указателя на локальную переменную. Это классический пример:

    // v "убегает" в кучу, так как ссылка на неё будет жить и после выхода из функции
    func createInt() *int {
        v := 42
        return &v
    }
  2. Отправка указателя в канал или сохранение в глобальную переменную.

  3. Размер переменной неизвестен во время компиляции. Например, при создании среза, размер которого зависит от внешних данных.

  4. Срез (slice) растет и превышает свою изначальную ёмкость (capacity). Новый, больший массив для среза будет аллоцирован в куче.

  5. Вызов метода на интерфейсе. Если у вас есть значение конкретного типа, и вы вызываете на нем метод через интерфейс, значение может 'убежать' в кучу, так как компилятор не знает конкретный тип во время компиляции.

Как обнаружить 'побег'?

Можно попросить компилятор сообщить о своих решениях с помощью флага -gcflags="-m":

# Для файла main.go
go build -gcflags="-m" main.go

# Пример вывода для функции createInt:
# ./main.go:6:9: moved to heap: v

Как избежать 'побега'?

  • По возможности, передавайте значения, а не указатели, если это не приводит к копированию огромных структур.
  • Заранее выделяйте срезам достаточную ёмкость (make([]T, length, capacity)), чтобы избежать реа́ллокаций в куче.
  • Используйте sync.Pool для переиспользования часто создаваемых объектов, чтобы снизить нагрузку на GC, связанную с аллокациями в куче.