Ответ
Escape analysis (анализ побега) — это процесс, в ходе которого компилятор Go во время компиляции определяет, где должна быть размещена переменная: в стеке (stack) или в куче (heap).
Если компилятор может доказать, что время жизни переменной строго ограничено рамками вызывающей функции, она размещается на стеке. Если же на переменную есть ссылки, которые могут существовать и после завершения функции (т.е. переменная 'убегает' из её области видимости), она 'эвакуируется' или, более точно, аллоцируется в куче.
Почему это важно?
- Стек: Очень быстрая память. Выделение и освобождение памяти на стеке — это простое смещение указателя стека. Не требует участия сборщика мусора (GC).
- Куча: Более медленная память. Выделение памяти в куче — более сложная операция, и объекты в куче должны отслеживаться и очищаться сборщиком мусора, что создает дополнительную нагрузку.
Основные причины 'побега' (эвакуации в кучу):
-
Возврат указателя на локальную переменную. Это классический пример:
// v "убегает" в кучу, так как ссылка на неё будет жить и после выхода из функции func createInt() *int { v := 42 return &v }
-
Отправка указателя в канал или сохранение в глобальную переменную.
-
Размер переменной неизвестен во время компиляции. Например, при создании среза, размер которого зависит от внешних данных.
-
Срез (slice) растет и превышает свою изначальную ёмкость (capacity). Новый, больший массив для среза будет аллоцирован в куче.
-
Вызов метода на интерфейсе. Если у вас есть значение конкретного типа, и вы вызываете на нем метод через интерфейс, значение может 'убежать' в кучу, так как компилятор не знает конкретный тип во время компиляции.
Как обнаружить 'побег'?
Можно попросить компилятор сообщить о своих решениях с помощью флага -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, связанную с аллокациями в куче.