Что такое escape-анализ в Go и как он определяет размещение переменных
Что проверяют
Понимаете ли вы, что в Go место под переменную выбирает компилятор, а не синтаксис, и умеете ли это проверить. Следом обычно дают пару функций и спрашивают, какая переменная уйдёт в кучу, или просят объяснить, как сократить аллокации в горячем коде.
Как отвечать
Escape-анализ это статический анализ на этапе компиляции. Компилятор смотрит, может ли ссылка на значение пережить функцию или стать видна за её пределами. Если не может, значение живёт на стеке горутины и исчезает вместе с кадром без участия сборщика мусора. Если может, оно размещается в куче, и за ним потом следит GC.
Типичные причины «убегания»:
- возврат указателя на локальную переменную:
return &x; - сохранение ссылки в глобальную переменную, в поле объекта из кучи или в мапу;
- замыкание или горутина, которые захватывают переменную и могут пережить функцию;
- преобразование в интерфейс: передача значения как
any, например вfmt.Println, часто приводит к аллокации; - размер неизвестен при компиляции:
make([]byte, n)с переменнойn, а также слишком крупные объекты; - указатель в канале: компилятор не знает, кто и когда его прочтёт.
Проверяется это флагом go build -gcflags=-m: в выводе будут строки moved to heap: x, escapes to heap и does not escape. С -m -m компилятор объясняет цепочку причин. Эффект в цифрах показывает go test -bench . -benchmem, колонка allocs/op, а в продакшене профиль pprof по аллокациям.
Учтите встраивание: после inlining указатель, который возвращает маленькая функция, может остаться на стеке вызывающей стороны. Поэтому решения escape-анализа меняются от версии к версии компилятора.
Где спотыкаются
Считают, что new(T) и &T{} всегда дают кучу, а var x T всегда стек. В Go ни то ни другое не гарантировано, решает только анализ.
Повторяют, что стек маленький и быстрый, а куча медленная сама по себе. Стек горутины растёт динамически, а дорого стоит не обращение к куче, а сама аллокация и нагрузка на GC.
Бросаются переписывать всё на значения вместо указателей без профиля. Копирование крупной структуры бывает дороже аллокации, поэтому решение принимают по бенчмарку, а не по интуиции.