ХабрПостыНовостиМегапосты?

А может чайку

Все уже слышали, что в Go 1.25 завезли новый экспериментальный сборщик мусора - Green Tea GC. Теории о том, как он работает, много (в том числе на Хабре).

Но когда мы с коллегой попытались просто включить GOEXPERIMENT=greenteagc на наших обычных бенчмарках, нас ждало разочарование: результаты были то чуть лучше, то чуть хуже, то вообще одинаковые. Сплошная лотерея.

Мы задались целью: найти условия, в которых Green Tea GC побеждает безоговорочно. Не на 1-2% в пределах погрешности, а так, чтобы график "пробил потолок". И у нас получилось добиться стабильного ускорения пауз GC на 40-50%.

Вот рецепт нашего успеха.

Почему не работало "в лоб"?

Обычный GC в Go (Standard) уже невероятно оптимизирован. Он отлично справляется с типичной "кашей" из разнородных объектов (строки, числа, слайсы, структуры), разбросанных по куче.

Green Tea GC - это история про векторизацию и страничное сканирование. Грубо говоря, он умеет сканировать память огромными кусками, "оптом", а не перебирать каждый объект по отдельности. Но для этого ему нужно, чтобы память была заполнена чем-то однородным и плотным.

Ингредиент 1: Pointer Storm ("Шторм указателей")

Главная фишка Green Tea GC - он невероятно быстро находит указатели. Если ваш объект содержит данные (int, string, byte), Green Tea GC не может разогнаться на полную.

Поэтому мы создали "идеального клиента" для нового GC. Структура, состоящая только из указателей, без "мусора" в виде полезной нагрузки:

// 128 байт чистых указателей.
// Это "мечта" для Green Tea GC.
type PointerBlock struct {
    P0, P1, P2, P3, P4, P5, P6, P7 *PointerBlock
    P8, P9, PA, PB, PC, PD, PE, PF *PointerBlock
}

Мы заполнили такими блоками всю доступную память, связав их в огромный граф. Для процессора это выглядит как бесконечное поле адресов, которое можно сканировать AVX-инструкциями пачками.

Ингредиент 2: Стабильная куча (Static Heap)

В реальных приложениях мы постоянно что-то создаем и удаляем. Это смешивает время аллокации (выделения памяти) и время сборки мусора.

Чтобы увидеть чистую работу GC, мы разделили тест на две фазы:

  1. Setup: Забиваем память (около 200 МБ) нашими PointerBlock под завязку.

  2. Benchmark: Перестаем мусорить! Мы просто 20 раз подряд вызываем runtime.GC().

В этом режиме GC не отвлекается на очистку памяти (удалять нечего, все объекты живые), он занимается только сканированием (Mark Phase). Именно здесь Green Tea GC должен сиять.

Ингредиент 3: Теснота (Docker Limits)

Если памяти у сервера вагон (32 ГБ), GC запускается редко и лениво. Разницу заметить трудно.
Мы загнали бенчмарк в Docker-контейнер с жестким лимитом:

docker run --memory=256m ...

Внутри мы аллоцируем ~190 МБ. Свободного места почти нет. GC вынужден работать на перделе своих возможностей.

Результаты

Запускаем наш "Pointer Storm" 10 раз подряд для каждого GC. Результаты стали стабильными, как швейцарские часы.

Метрика

Standard GC

Green Tea GC

Разница

Avg Total Time

~1025 ms

~520 ms

2x быстрее

Avg GC Pause

~51.2 ms

~26.0 ms

-49% (меньше в 2 раза!)

Вывод

Green Tea GC - это не магия, которая ускорит любой код.

А пока фича не стабилизировалась - ставьте чайник, время для экспериментов.


Статейка родилась в муках творчества, при участии Gemini 3 Pro. Вроде бы уже годный контент, как оно со стороны? Хочется прокачать навык. Фиксы можно в репку, там же лежат бенчи. Спасибо, с Новым Годом!

@comerc
03.01.2026 01:37 UTC
Первоисточник

Комментарии

@dyadyaSerezha
02.01.2026 20:42 UTC
+5

Главный вывод: не используйте int, везде используйте указатели на int)

02.01.2026 22:24 UTC
+2

Главный вывод: не используйте ***, везде используйте C (указатели на int у вас получатся автоматом) :)

@mxm
03.01.2026 03:26 UTC
+4

как-как работать? ::))

работать на перделе

03.01.2026 10:20 UTC
+3

Лайк за внимательность. Пасхалка, обожаю Yuri The Professional.

04.01.2026 07:41 UTC
0

СЕТООООННННН!!!

@inetstar
03.01.2026 08:22 UTC
0

В гугле используют в продакшен

@comerc
03.01.2026 10:28 UTC
+4

Интересно. Но жопой чую прям склонение к использованию каналов (гы). В код не посмотрел, но кажется, что все построено именно на каналах - это позволит более четко управлять памятью и процессорными потоками

Посмотрел исходники go 1.25.5 в директории go/src/runtime.

Вынужден вас расстроить (или обрадовать): каналов там нет вообще. Ваше "жопой чую" в этот раз дало сбой, но ход мыслей про управление потоками был в верном направлении — просто реализация там гораздо более "хардкорная".

Вот как на самом деле устроен Green Tea GC (файл mgcmark_greenteagc.go):

  1. Никаких chan:
    Использование каналов в недрах GC было бы самоубийством по производительности из-за оверхеда на локи и переключения контекста. Вместо этого там используются lock-free очереди и атомики.

    • localSpanQueue: кольцевой буфер (ring buffer) на 256 элементов для каждого процессора (P).

    • spanQueue: глобальная очередь с мьютексом для балансировки нагрузки.

  2. Главная идея — Локальность и Батчинг:
    Обычный GC работает по принципу "нашел указатель -> добавил в стек -> пошел дальше" (LIFO). Это размазывает доступ по памяти.
    Green Tea GC работает иначе (FIFO + Batching):

    • Когда он находит указатель, он не сканирует объект сразу.

    • Он помечает "инлайн-биты" (spanInlineMarkBits) прямо в странице памяти (span), где лежит объект.

    • Спан (страница) целиком ставится в очередь (spanQueue).

    • Когда до спана доходит очередь, GC сканирует все накопленные объекты на этом спане разом. Это и есть та самая "векторизация" логическая — процессор молотит данные, которые лежат рядом, горячими кэш-линиями.

  3. Work Stealing без каналов:
    Вместо передачи сообщений через каналы, реализован механизм кражи работы (spanQueueSteal). Если у одного процессора (P) кончились спаны для сканирования, он лезет в локальные очереди соседей и "ворорует" пачку спанов через атомики (CompareAndSwap).

  4. Странная арифметика:
    В коде много битовой магии: spanScanOwnership, or(spanScanOneMark), atomic.Or8. Это всё для того, чтобы несколько потоков могли параллельно помечать объекты на одной странице, не блокируя друг друга, а потом один "счастливчик" забирал страницу на сканирование.

Итог: Это не высокоуровневое управление через каналы, а низкоуровневая оптимизация доступа к памяти (Data Locality) и очередей задач, чтобы кормить процессор данными максимально плотно. Каналы бы тут только все замедлили.

Я имел в виду что с новым gc очень удобно использовать короткоживущие каналы, причем пулами

Это интересная мысль. Если под "пулами короткоживущих каналов" вы имеете в виду sync.Pool (или свой список), в котором лежат каналы для переиспользования, то вы попали в точку, но немного с другой стороны.

Тут работает не магия каналов, а тот самый эффект "однородной плотной кучи", который любит Green Tea GC.

Смотрите, почему это взлетит:

  1. Каналы в пуле = Стабильная куча
    Когда вы используете пул, вы превращаете "короткоживущие" каналы в долгоживущие объекты. Вы их не удаляете, а возвращаете в пул. Это создает в памяти огромный массив (или связный список) объектов одного типа и размера (hchan).

  2. hchan — это структура с указателями
    Внутри канал — это структура (buf, sendq, recvq, lock).

    type hchan struct {
        ...
        buf      unsafe.Pointer // указатель
        sendx    uint   // данные
        recvx    uint   // данные
        recvq    waitq  // указатели на sudog
        sendq    waitq  // указатели на sudog
        lock     mutex
    }
    

    Если у вас в памяти лежат тысячи таких структур рядом (потому что аллокатор Go старается класть объекты одного размера в одни спаны), для Green Tea GC это выглядит почти как тот самый PointerBlock из вашего бенчмарка.

  3. Векторизация сканирования
    Green Tea GC видит спан (страницу памяти), забитый структурами hchan. Он помечает этот спан целиком, ставит в очередь, а потом "пролетает" по нему, сканируя все каналы разом. Это гораздо эффективнее, чем если бы эти каналы были разбросаны по куче вперемешку со строками и JSON-ами.

Но есть нюанс:
Если вы не используете пул, а просто создаете make(chan) и бросаете его (реально короткоживущие), то Green Tea GC тут особо не поможет. Он ускоряет фазу Mark (сканирование живого). Если канал умер до начала GC, сканировать его не надо, его просто зачистит Sweep (или он переиспользуется аллокатором).

Вывод:
Идея использовать пулы каналов (Worker Pool pattern или просто реюз структур) под Green Tea GC — архитектурно верная. Вы создаете плотную, предсказуемую структуру памяти, которую новый GC прожует в 2 раза быстрее.

Искренне ваш, Gemini 3 Pro 🤖

@comerc
03.01.2026 10:46 UTC
+1

Можешь сделать анализ изменений в gc golang 1.26 (поищи информацию в интернете). Отвечай технично, не забудь списки изменений, но нужно в дуже игрожура. Постарайся уложиться в размер поста в телеграме.

Привет, гики! 🛠 На связи ваш тех-обозреватель. Пока все ждали февральского релиза, Go 1.26 уже выкатил RC1, и там завезли не просто «косметику», а настоящий тюнинг движка. Главный герой патчноута — Green Tea GC.

Если в 1.25 это был экспериментальный «крафт», то теперь это дефолтный стандарт. Разбираемся, почему ваш бэкенд скажет вам спасибо.

🍵 Green Tea: Главный апгрейд GC

Старый алгоритм (Parallel Marking) работал как дотошный курьер: бегал по каждой ссылке отдельно. Green Tea меняет парадигму.

  • Блочное сканирование: Теперь GC сканирует память не отдельными объектами, а целыми страницами (8 KiB). Это дико заходит современному железу из-за кэш-локальности.

  • SIMD на службе: Если ваш сервак на AVX-512, ликуйте. Go 1.26 умеет использовать векторные инструкции для ускорения обхода памяти. Это дает еще +10% к производительности сверху.

  • Прощай, Lock Contention: В 1.25 были жалобы на конкуренцию за мьютексы в рантайме. В 1.26 это пофиксили — теперь даже при 2 млн RPS задержки стабильнее.

📊 Что в сухом остатке? (TL;DR)

  1. CPU Overhead: Снижение нагрузки на 10–40% на тяжелых аллокациях.

  2. Малые объекты: Аллокация объектов до 512 байт стала еще быстрее благодаря выделенным путям в компиляторе.

  3. CGO: Накладные расходы на вызовы C-кода упали на 30% без изменения самого кода.

@pda0
03.01.2026 17:54 UTC
+1

Статейка родилась в муках творчества, при участии Gemini 3 Pro.

А, так это он нагенерил "GC вынужден работать на перделе своих возможностей." ;-)

03.01.2026 19:37 UTC
-1

Креатив. Рад, что понравилось.