GreenTea GC: Умиротворение чашки зеленого чая, отнятое Go runtime
Почему появился новый GC?
Причины существования текущего GC
В настоящее время GC в Go можно описать следующими словами:
- concurrent tri-color mark and sweep
- Для отслеживания состояния объектов используются три цвета (белый, серый, черный).
- Белый: объект, который еще не был посещен.
- Серый: объект, который был посещен, но не все дочерние объекты были посещены.
- Черный: объект, все дочерние объекты которого были посещены.
- После завершения обхода все белые объекты собираются.
- Write Barrier используется для защиты вновь создаваемой памяти во время GC.
- Время включения/выключения Write Barrier составляет значительную часть так называемого Stop-The-World (далее STW).
- Этот процесс выполняется параллельно.
- Для отслеживания состояния объектов используются три цвета (белый, серый, черный).
- No Compaction
- GC в Go не выполняет уплотнение.
- Может возникнуть фрагментация памяти.
- Однако для объектов размером менее 32 КБ фрагментация минимизируется за счет использования кэша per-P аллокатора памяти языка Go.
- Non-Generational
- GC в Go не управляет объектами по поколениям.
- Все объекты принадлежат одному поколению.
- Escape Analysis
- Go использует Escape Analysis для определения того, будет ли объект выделен в куче или в стеке.
- Грубо говоря, если используются висячие указатели или интерфейсы, то можно считать, что объект будет выделен в куче.
Важный аспект
GC в Go исследует все объекты от корня и выполняет трехцветную маркировку. Этот процесс можно описать одной строкой как алгоритм параллельного обхода графа с топологической сортировкой
. Однако каждый объект, скорее всего, находится в разных областях памяти. Проще говоря,
- Предположим, существуют две области памяти, расположенные на расстоянии около 32 МБ друг от друга.
- В этих двух областях памяти выделено по 5 объектов.
- Объекты A, B, C находятся в области 1.
- Объекты D, E находятся в области 2.
- Порядок ссылок на объекты:
A -> D -> B -> E -> C
. - Когда начинается GC, он посещает A, затем D, затем B, затем E, затем C.
- В этом случае, поскольку A и D находятся в разных областях памяти, при переходе от A к D необходимо перемещаться между областями памяти.
- В этом процессе возникают так называемые затраты на "скачок" памяти, такие как перемещение между областями памяти и заполнение новых кэш-линий. Значительная часть времени ЦП, используемого приложением, может быть выделена на такие операции GC.
- Эти затраты постоянно возникают на протяжении всего выполнения GC.
В чем же проблема?
Такое поведение GC является крайне неэффективным в следующих случаях:
- При большом количестве ядер и большом объеме памяти
- GC в Go по умолчанию выполняется параллельно.
- Однако, если область памяти обширна и объекты распределены, несколько ядер будут параллельно исследовать объекты в различных областях памяти.
- В этом процессе, если шина между ЦП и памятью недостаточно широка, пропускная способность памяти может стать узким местом.
- Кроме того, поскольку существует физическое расстояние, каждый обход будет относительно большой задержкой.
- При большом количестве мелких объектов
- Если вы управляете деревом или графом с большой глубиной или множеством дочерних элементов в Go, GC должен исследовать все эти объекты.
- В этом процессе, если объекты распределены по разным областям памяти, возникает задержка по первой причине.
- Кроме того, если мелких объектов много, GC должен исследовать все эти объекты, поэтому во время выполнения GC ядро ЦП будет выделять значительную часть своих ресурсов на GC и выполнять работу.
Для решения этих проблем команда Golang представила GreenTea GC.
Вам больше не будет позволено пить зеленый чай
Что лишает вас зеленого чая?
Областью, где, как считается, можно применить самое быстрое решение для существующего GC, является локальность памяти. То есть минимизация скачков памяти путем размещения объектов близко друг к другу. Однако невозможно принудить программиста к определенному шаблону кодирования, и выделение объектов непредсказуемо в зависимости от рабочего процесса.
Поэтому метод, выбранный командой Golang, — это Memory Span.
Что такое Memory Span?
Memory Span — это относительно большая область памяти, выделяемая для размещения небольших объектов. И новый GC, названный GreenTea GC, выполняет сбор мусора в этих Memory Span. Детальное поведение почти идентично существующему Tri-Color Mark and Sweep.
Работа GreenTea GC
Сначала GreenTea GC выделяет Memory Span. Размер, как было описано ранее, составляет 8 КиБ. Внутри него можно размещать объекты размером до 512 байт. Это примерно размер узла дерева или графа, который был приведен в примере, или размер структуры, которая обычно "ускользает" в кучу, и, по нашему мнению, не может быть больше. Объекты размером менее 512 байт накапливаются в этом Memory Span каждый раз, когда они "ускользают" в кучу, и когда этот Memory Span заполняется, выделяется новый Memory Span.
Теперь, когда происходит GC, GreenTea GC помещает эти Memory Span в очередь и последовательно проверяет их. В этом процессе GreenTea GC использует планирование, почти идентичное существующей модели GMP. Также реализованы области, такие как кража работы. В любом случае, работник, извлекающий Memory Span из очереди, проверяет внутренние объекты выделенного ему Memory Span. В этом процессе Tri-Color Mark and Sweep используется аналогичным образом.
В чем преимущество?
Разница между этим процессом и существующим GC в основном одна. Единицей выполнения GC стала не объект, а Memory Span. Благодаря этому GC получает следующие преимущества:
- Локальность памяти: Поскольку объекты собраны в Memory Span, скачки памяти минимизируются при поиске объектов. То есть максимально используется кэш ЦП.
- Улучшение производительности GC: Выполняя GC на уровне Memory Span, GC работает более эффективно в многоядерной среде.
- Оптимизация выделения памяти: Поскольку Memory Span выделяется фиксированного размера, накладные расходы на выделение памяти при размещении небольших объектов уменьшаются, и вероятность фрагментации снижается. Это повышает эффективность освобождения памяти.
Короче говоря, теперь мы можем чаще и с меньшими затратами выделять объекты размером менее 512 байт.
Однако и здесь есть определенные благоприятные сценарии.
- Древовидные/графовые структуры: когда fan-out высокий и изменения происходят редко.
- B+ дерево, Trie, AST (Abstract Syntax Tree)
- Структуры данных, дружественные кэшу
- Пакетная обработка: рабочий процесс массового выделения небольших данных.
- Множество мелких объектов, создаваемых при парсинге JSON.
- Объекты DAO из наборов результатов базы данных.
В этих случаях GreenTea GC может демонстрировать лучшую производительность, чем существующий GC. Однако это применимо не во всех случаях, и особенно когда объекты распределены по разным областям памяти, по-прежнему трудно ожидать значительного улучшения производительности.
Заключение
Команда Golang, вероятно, планирует продолжать улучшать GC в долгосрочной перспективе. Этот GreenTea GC можно рассматривать как небольшое изменение в узкой области, и он не заменяет существующий GC. Однако GreenTea GC представляется интересным примером, позволяющим взглянуть на проблемы, с которыми сталкивается или предвидит команда Golang. Была впечатляющей и попытка решить, казалось бы, сложную проблему относительно простым добавлением концепции. Лично я считаю это интересным случаем.
GreenTea GC — это экспериментальная функция, которая будет представлена в Go 1.25. Ее можно активировать, используя переменную окружения GOEXPERIMENT=greenteagc
. Эта функция пока экспериментальна, поэтому перед использованием в производственной среде требуется тщательное тестирование.