Go: Runtime и GC

30 вопросов

1 Из чего состоит runtime Go?

Планировщик (scheduler) горутин и потоков, сборщик мусора (GC), управление памятью (аллокации, куча), паника/recover, каналы и примитивы sync, сетевой поллер, системные вызовы. Код в runtime и runtime/internal; часть логики в ассемблере под платформу.

Открыть отдельно →
2 По какому алгоритму работает GC в Go?

Concurrent mark-and-sweep с трицветной разметкой. Фазы: mark (разметка достижимых объектов, параллельно с приложением), mark termination (короткая пауза), sweep (подчистка неиспользуемой памяти, может быть фоновой). С Go 1.5 GC конкурентный.

Открыть отдельно →
3 Что такое STW в GC?

Stop The World - пауза, когда все горутины останавливаются. В Go STW короткие: в основном на mark termination и при подготовке к sweep. Цель - минимизировать STW; большая часть работы GC идёт параллельно с приложением.

Открыть отдельно →
4 Как настраивать GC: GOGC, GOMEMLIMIT?

GOGC (по умолчанию 100): целевой процент роста кучи после GC. 100 значит "запускать GC, когда живая куча выросла в 2 раза с прошлого цикла". GOMEMLIMIT (Go 1.19+) - мягкий лимит памяти процесса; GC будет работать агрессивнее при приближении к лимиту.

GOGC=50 GOMEMLIMIT=512MiB ./app
Открыть отдельно →
5 Что значит GOGC=100?

Целевой коэффициент роста кучи 100%: следующий цикл GC запускается, когда размер живой кучи с прошлого GC вырос в 2 раза. Меньше GOGC - чаще GC, меньше пиковое потребление, больше CPU на GC. Больше GOGC - реже GC, больше памяти.

Открыть отдельно →
6 Когда объект попадает в кучу (heap) и когда на стек?

Компилятор решает по escape analysis. Если указатель на объект уходит за пределы функции (return, глобальная переменная, замыкание) - объект в куче. Локальные переменные, на которые нет ссылок снаружи - на стеке. Стек горутины ограничен; большие объекты часто в куче.

Открыть отдельно →
7 Что такое escape analysis?

Анализ компилятора: может ли указатель на переменную "убежать" из функции. Если да - переменная размещается в куче. Запуск с -gcflags="-m" выводит решения по escape. Цель - держать данные на стеке и снижать нагрузку на GC.

go build -gcflags="-m" 2>&1 | grep escape
Открыть отдельно →
8 Как растёт стек горутины?

Начальный стек маленький (порядка 2 КБ). При нехватке места рантайм выделяет новый блок стека (больше предыдущего), копирует туда текущий стек и переключается на него. Рост ограничен лимитом (порядка гигабайта). Копирование при росте - причина отказа от сегментированного стека в старых версиях.

Открыть отдельно →
9 Почему отказались от сегментированного стека?

Сегментированный стек при переполнении выделял новый сегмент; при возврате из глубокой цепочки сегмент освобождался. Частое выделение/освобождение в горячем цикле (hot split) создавало нагрузку и фрагментацию. Перешли на непрерывный стек с копированием при росте.

Открыть отдельно →
10 Что делает GOMAXPROCS?

Максимальное число потоков ОС (M), которые одновременно выполняют пользовательский код. По умолчанию равно числу CPU. Меняет только количество активных M; горутин может быть больше. Обычно не трогают; для отладки или ограничения параллелизма иногда уменьшают.

runtime.GOMAXPROCS(4)
Открыть отдельно →
11 Как интегрироваться с pprof?

Импорт _ "net/http/pprof" и запуск HTTP-сервера регистрируют эндпоинты /debug/pprof/. Профили: cpu, heap, goroutine, block, mutex. Сбор через go tool pprof или через API. В production обычно включают отдельный порт или путь с ограничением доступа.

import _ "net/http/pprof"
go func() { log.Println(http.ListenAndServe(":6060", nil)) }()
Открыть отдельно →
12 Какой оверхед у pprof?

CPU profiling - семплирование по таймеру, накладные расходы небольшие. Heap profiling - при аллокациях, заметнее при высокой частоте. Блокировки и мьютексы - при событиях блокировки. Для production обычно включают только нужные профили и ограничивают частоту или путь.

Открыть отдельно →
13 Как искать утечки памяти?

Сравнивать heap-профили за разные моменты (pprof, go tool pprof -base). Смотреть, какие объекты накапливаются. Проверять, что горутины и каналы не остаются в ожидании навсегда. Использовать тесты с ограничением памяти и race detector. Отключать таймеры и не закрывать каналы - частые причины.

Открыть отдельно →
14 Типичные причины утечки памяти в Go?

Глобальные кеши и мапы без ограничения размера. Горутины, блокирующиеся на канале без выхода. Таймеры/тикеры без Stop(). Незакрытые соединения и дескрипторы. Циклические ссылки при наличии глобальных ссылок. Удержание больших срезов в подмножестве слайса (держать ссылку на базовый массив).

Открыть отдельно →
15 Может ли GC собрать недостижимые объекты с циклическими ссылками?

Да. Go GC не подсчитывает ссылки; он помечает достижимые от корней объекты. Недостижимые циклы (никто снаружи на них не ссылается) будут собраны. Счётчики ссылок не используются.

Открыть отдельно →
16 Можно ли управлять GC вручную?

Частично. runtime.GC() запускает полный цикл GC. Для отладки или тестов - допустимо. В production обычно не вызывают - рантайм сам выбирает момент. Для предсказуемых пауз (например, перед критичным участком) иногда используют.

runtime.GC()
Открыть отдельно →
17 Как уменьшить количество аллокаций?

Избегать escape на кучу (не возвращать указатели на локальные, не захватывать в замыкание по указателю). Переиспользовать срезы (буферы) через sync.Pool или повторное использование. Избегать боксинга (interface{}, срезы из строк). Профилировать и смотреть -gcflags="-m".

Открыть отдельно →
18 Что такое memory ballast?

Техника: в начале программы выделить большой кусок памяти (например, большой срез), чтобы куча сразу выросла. GC реже срабатывает в начале; первый цикл откладывается. Уменьшает количество ранних GC и может стабилизировать латентность. Используют осторожно.

var ballast = make([]byte, 100<<20) // 100 MB
Открыть отдельно →
19 Как работает GOMEMLIMIT?

Go 1.19+: задаёт мягкий лимит памяти процесса. Рантайм старается удерживать использование памяти в рамках этого лимита, усиливая работу GC. Не жёсткий лимит - при пиках можно превысить. Помогает в контейнерах избежать OOM при сохранении производительности.

GOMEMLIMIT=512MiB
Открыть отдельно →
20 Что такое PGO в Go?

Profile-Guided Optimization (Go 1.20+): компилятор использует профиль выполнения (обычно CPU) для инлайнинга и размещения кода. Сбор профиля, затем сборка с -pgo=default.pgo. Может дать прирост на несколько процентов для горячих путей.

go build -pgo=default.pgo
Открыть отдельно →
21 Что такое инлайнинг в Go?

Подстановка тела функции в место вызова вместо вызова. Уменьшает накладные расходы и даёт оптимизации across call. Компилятор решает по размеру и сложности функции. Можно смотреть через -gcflags="-m". Инлайн ограничен эвристиками; большие функции не инлайнятся.

Открыть отдельно →
22 Как получить stack trace?

runtime/debug.Stack() возвращает байты со стеком текущей горутины. runtime.Stack(buf, true) - в буфер, второй параметр - включить стеки всех горутин. В обработчике паники через recover можно логировать debug.Stack(). Пакет pprof выводит стеки по профилю.

fmt.Println(string(debug.Stack()))
Открыть отдельно →
23 Как работает race detector?

При -race компилятор добавляет инструментацию к доступам к памяти. Рантайм отслеживает пары событий и отношения happens-before. При обнаружении конфликтующего доступа (data race) выводит отчёт. Дорого по CPU и памяти - только для тестов и отладки.

Открыть отдельно →
24 Что такое finalizer (runtime.SetFinalizer)?

Функция, вызываемая сборщиком мусора после того, как объект стал недостижим. Не гарантируется момент вызова и порядок. Используют для освобождения внешних ресурсов (C, файлы), но предпочтительнее явный Close. Нельзя полагаться на finalizer для критичной логики.

runtime.SetFinalizer(obj, func(o *T) { o.close() })
Открыть отдельно →
25 Что такое write barrier в GC?

Механизм в GC: при записи указателя в объект барьер помечает или обрабатывает целевую область, чтобы не потерять ссылки во время concurrent mark. Нужен для корректности трицветной разметки при работе приложения параллельно с GC.

Открыть отдельно →
26 Что такое soft memory limit?

То же, что GOMEMLIMIT: мягкий лимит памяти процесса. GC старается не превышать лимит, но не гарантирует жёсткую границу. В документации называется "soft limit".

Открыть отдельно →
27 Concurrent GC в Go?

С Go 1.5 GC по большей части конкурентный: разметка и подчистка идут параллельно с выполнением приложения. Короткие паузы только в mark termination. Write barrier позволяет приложению работать во время mark.

Открыть отдельно →
28 Что такое GC pacing?

Регулировка темпа работы GC: когда запускать следующий цикл и как интенсивно работать, чтобы уложиться в целевое использование памяти (GOGC) и не создавать длинных пауз. Рантайм балансирует между временем GC и объёмом кучи.

Открыть отдельно →
29 Что выводит GODEBUG=gctrace=1?

В stderr выводятся строки по каждому циклу GC: фаза (mark, sweep), длительность, размер кучи до/после, время паузы и т.п. Удобно для быстрой оценки поведения GC и пауз. В production обычно отключают из-за объёма вывода.

GODEBUG=gctrace=1 ./app
Открыть отдельно →
30 Какие есть инструменты для escape analysis?

Компилятор: go build -gcflags="-m" выводит решения по escape (и инлайну). Две буквы m дают больше вывода: -gcflags="-m -m". В коде нельзя "запросить" escape; только смотреть вывод компилятора и менять код, чтобы объекты не уходили в кучу.

go build -gcflags="-m" ./...
Открыть отдельно →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.