Как работает сборщик мусора (GC) в Go и как можно повлиять на его производительность?

Ответ

Сборщик мусора в Go спроектирован для обеспечения низкой задержки (low latency) и работает параллельно с основной программой, минимизируя паузы "stop-the-world" (STW).

Как он работает?

Текущая реализация GC в Go — это конкурентный трехцветный маркировщик и сборщик (concurrent tri-color mark-and-sweep).

  1. Фаза маркировки (Marking): GC сканирует все объекты в памяти, начиная с корневых (глобальные переменные, стеки горутин), и помечает достижимые (живые) объекты. Эта фаза выполняется конкурентно, то есть параллельно с работой основной программы. Для отслеживания изменений указателей во время маркировки используется барьер записи (write barrier).
  2. Фаза завершения маркировки (Mark Termination): Короткая пауза STW для завершения маркировки, во время которой программа останавливается, чтобы обработать оставшиеся объекты.
  3. Фаза сборки (Sweeping): GC проходит по всей куче и освобождает память, занятую немаркированными (мертвыми) объектами. Эта фаза также выполняется конкурентно.

За счет конкурентного выполнения основных фаз, паузы STW в Go обычно очень короткие (часто меньше миллисекунды).

Как можно повлиять на производительность?

Хотя GC в Go работает автоматически, на его эффективность можно повлиять как через код, так и через настройки.

  1. Оптимизация кода (самый важный способ):

    • Уменьшение количества аллокаций: Чем меньше объектов создается, тем меньше работы у GC. В критичных к производительности участках кода (hot paths) следует избегать лишних выделений памяти.
    • Использование sync.Pool: Для переиспользования часто создаваемых объектов (например, буферов), чтобы снизить нагрузку на GC.
    • Правильный выбор структур данных: Избегайте лишних указателей, так как каждый указатель требует сканирования со стороны GC. Иногда плоские структуры или слайсы значений эффективнее, чем слайсы указателей.
    • Профилирование: Используйте pprof для поиска мест с наибольшим количеством аллокаций (go tool pprof -alloc_objects ...).
  2. Тюнинг GC:

    • Переменная окружения GOGC: Устанавливает процентное соотношение между объемом живых данных и общим объемом кучи, при котором запускается следующий цикл GC. GOGC=100 (значение по умолчанию) означает, что GC запустится, когда размер кучи станет вдвое больше, чем после предыдущей сборки. Уменьшение GOGC (например, до 50) заставит GC работать чаще, используя меньше памяти, но потребляя больше CPU. Увеличение GOGC приведет к обратному эффекту.
    • Функция debug.SetGCPercent: Позволяет изменять значение GOGC программно во время выполнения.