Cборщик мусора Green Tea в Go: что это и как работает

Коротко: в Go 1.25 появился экспериментальный сборщик мусора Green Tea. Его цель – уменьшить CPU-стоимость маркировки за счет лучшей локальности памяти и лучшей масштабируемости на многоядерных CPU. Типичные выигрыши по времени GC составляют около 10%, а на некоторых нагрузках – до 40%. В 1.26 его планируют сделать значением по умолчанию, с возможностью отключить. go.dev

🔥 https://t.me/+RAiQoS5k4Bg4NGYy – огромное количество уроков, библиотек и примеров с кодом в канале для Go разработчиков.


Зачем менять GC в Go

В классическом Go-GC основная цена – это фаза mark: на нее уходит ~90% времени GC, sweep – около 10%. При маркировке рантайм «затапливает» граф объектов, переходя по указателям. На реальном «железе» это приводит к множеству разрозненных чтений по памяти – кэш-промахи, остановки из-за ожидания данных и узкое место общей очереди заданий при параллельной работе на многих ядрах. go.dev


Главная идея Green Tea

Ключевой принцип: работать страницами памяти, а не отдельными объектами.

Cборщик мусора Green Tea в Go: что это и как работает

Вместо произвольного «скакания» по графу объектов Green Tea агрегирует работу по страницам хипа и проходит их длинными линейными сканами слева направо. Это резко повышает предсказуемость обращений к памяти, дружит с аппаратными префетчерами и разгружает общие очереди задач в многопоточном режиме. На больших хипах число «проходов» по памяти становится меньше и длиннее – именно в этом и «магия» Green Tea. go.dev

Здесь можно посмотреть видео про устройство green tea подробнее: https://www.youtube.com/watch?v=he5PfBfte2c


Аппаратные ускорения

Green Tea использует векторные инструкции современных x86-процессоров, чтобы обрабатывать метаданные целой страницы за несколько прямолинейных операций. Поддержка широких регистров (например, AVX-512) и продвинутых побитовых инструкций позволяет ускорить внутренний цикл сканирования. Эти векторные улучшения раскатывают постепенно и, по данным Go-команды, дадут дополнительное снижение CPU-стоимости GC примерно на 10%. go.dev

Cборщик мусора Green Tea в Go: что это и как работает

Что дают числа

  • В типичных бенчмарках команда Go видит снижение времени GC на 10%, в ряде случаев – до 40%. Если приложение тратило 10% CPU на GC, то общий выигрыш может составить 1-4% CPU. Похожие эффекты наблюдают и в проде в Google. go.dev
  • В релиз-ноутах 1.25 это зафиксировано как официальная оценка: «10-40% уменьшения накладных расходов GC» для программ, активно использующих GC. go.dev

Важно: не все нагрузки выигрывают. Если структура хипа такая, что на странице для сканирования часто оказывается ровно один объект, агрегирование только вредит. В реализации есть «спецкейс» для таких страниц – он сглаживает регрессии, но полностью их не исключает. Практика показала, что уже при заполнении страницы работой на ~2% Green Tea может обгонять классический обход графа. go.dev


Как включить и как откатить

Green Tea в Go 1.25 – эксперимент, его нужно явно включать при сборке:

# Обычная сборка
go build ./...

# С Green Tea GC
GOEXPERIMENT=greenteagc go build ./...

# Сборка без Green Tea, если вдруг он станет дефолтом в 1.26 и нужно отключить
GOEXPERIMENT=nogreenteagc go build ./...
  • Статус и инструкции подтверждены в блоге Go и в релиз-ноутах 1.25. В 1.26 Green Tea планируют включить по умолчанию, а векторные ускорения добавить на новой x86-архитектуре. go.dev+1

Когда ждать лучший эффект

Cборщик мусора Green Tea в Go: что это и как работает

Green Tea особенно полезен, когда:

  • у вас много мелких объектов и длинные цепочки ссылок – раньше это приводило к «рандомным» скачкам по памяти;
  • приложение сильно параллельно и работает на многих ядрах – новые структуры работы уменьшают узкие места общей очереди работы GC;
  • железо поддерживает широкие векторные инструкции – тогда в 1.26 можно рассчитывать на дополнительный бонус. go.dev

Когда возможны регрессии

  • Хип «разрежен» и объекты распределены так, что на странице часто один кандидат для сканирования – накладные расходы на «агрегацию по страницам» могут перекрыть выгоду;
  • Профиль приложения изначально почти не упирался в GC – тогда общий выигрыш будет трудно заметить. go.dev

Пример Docker multi-stage

FROM golang:1.25 AS build
WORKDIR /src
COPY . .
# соберём обе версии для A/B
RUN go build -o /out/app-vanilla ./cmd/app && \
    GOEXPERIMENT=greenteagc go build -o /out/app-greentea ./cmd/app

FROM gcr.io/distroless/base
COPY --from=build /out/app-greentea /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]

3) Как правильно мерить

Основная цель Green Tea – уменьшить CPU-стоимость маркировки. Значит, мерим именно это.

  1. GC traceGODEBUG=gctrace=1
    Запускаем оба бинаря под одинаковой нагрузкой и сравниваем суммарные времена mark и долю CPU. В блоге есть раздел с объяснением вывода и измерений. Go
  2. pprof CPU: снимаем профили и смотрим вклад функций рантайма scanobjectgreyobjectmarkroot и т. п.
# пример: HTTP-экспорт pprof в приложении
import _ "net/http/pprof"
# затем
go tool pprof -http=:0 http://localhost:6060/debug/pprof/profile?seconds=60
  1. Latency SLA: Green Tea сохраняет короткие паузы, но проверяем p95-p99 на своей нагрузке.
  2. Память: фиксируем пиковый RSS и динамику heap-in-use. Иногда структура хипа немного меняется.

Пример обвязки для A/B

# baseline
GOGC=100 GOMEMLIMIT=0 GODEBUG=gctrace=1 ./app-vanilla 2>trace.vanilla.txt

# green tea
GOGC=100 GOMEMLIMIT=0 GODEBUG=gctrace=1 ./app-greentea 2>trace.greentea.txt

# сравнить суммарные mark total:
grep "gc " trace.* | awk '{print $0}' | head

Нагружающие примеры кода

Ниже три небольших бенчкейса. Они не претендуют на академическую точность, но дают ощущение, где Green Tea раскрывается.

Много мелких объектов с ссылками – типичный выигрыш

// go test -bench=AllocGraph -benchmem -count=5
package gt

import "testing"

type node struct {
	next *node
	_    [56]byte // делаем объект ~64 байта
}

func makeChain(n int) *node {
	var head *node
	for i := 0; i < n; i++ {
		head = &node{next: head}
	}
	return head
}

var sink *node

func BenchmarkAllocGraph(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sink = makeChain(200_000)
	}
}

Ожидаемая картина: суммарное время mark снижается, так как объекты одного размера плотно ложатся на страницы 8 KiB и сканируются линейно. Это ровно тот профиль, под который проектировался Green Tea. GitHub

Map с короткоживущими значениями

// go test -bench=MapChurn -benchmem -count=5
package gt

import "testing"

func BenchmarkMapChurn(b *testing.B) {
	for i := 0; i < b.N; i++ {
		m := make(map[int]*[8]byte, 50_000)
		for k := 0; k < 50_000; k++ {
			v := new([8]byte) // мелкие объекты
			m[k] = v
		}
		// теряем ссылку на m - очищает GC
	}
}

Если у вас сервис активно churn’ит мелкие указательные значения, Green Tea обычно даёт минус 10-30% времени GC. Точные числа зависят от частоты коллекций и давления на память, но именно для такого workload Green Tea и создавался. Go

Анти-пример: по одному объекту на страницу

// go test -bench=Sparse -benchmem -count=5
package gt

import "testing"

// большой объект, чтобы на странице часто оказывался один кандидат
type big struct{ _ [7000]byte }

func BenchmarkSparse(b *testing.B) {
	for i := 0; i < b.N; i++ {
		s := make([]*big, 2048)
		for i := range s {
			s[i] = new(big)
		}
	}
}

Здесь профита может не быть, а иногда будет небольшой регресс – накопление «работы по странице» не окупится, если каждый раз сканируем ровно один объект. В Green Tea есть смягчающий спец-кейс, но он не волшебная палочка. Go

В какую сторону ждать эффекты

Где Green Tea «заходит» лучше всего:

  • много мелких объектов и указателей, дерево, граф, индекс в памяти
  • много ядер, конкурирующие воркеры GC меньше упираются в общую очередь
  • современное x86-железо – с выходом 1.26 добавятся SIMD-ускорения сканирования страниц Go

Где возможны регрессии:

  • разреженный хип и частые «одиночки на странице»
  • рабочая нагрузка почти не упиралась в GC – общий выигрыш растворится в шуме Go

Чеклист прод-включения

  1. Соберите два бинаря и прогоните свои нагрузочные тесты.
  2. Снимите gctrace и pprof, сравните суммарное время mark и вклад функций GC. Go
  3. Проверьте p95-p99 задержки на приемлемость.
  4. Прокатите canary на проценте трафика.
  5. Если заметите аномалии – команда Go просит делиться фидбеком в issue Green Tea. GitHub

Цифры от команды Go

  • Внутренние бенчмарки и прод-данные показывают минус 10-40% CPU времени GC без векторных улучшений. Если ваше приложение раньше тратило 10% CPU на GC, то общий выигрыш составит примерно 1-4% CPU. Go
  • План: сделать Green Tea дефолтом в Go 1.26, оставить GOEXPERIMENT=nogreenteagc для opt-out, добавить SIMD-ускорения на новом x86. Go


Что измерять при тесте у себя

  1. Время GC и доля CPU GC в профилях pprof.
  2. Паузу GC – хотя Green Tea и нацелен на CPU, проверьте, не изменились ли задержки в худшую сторону.
  3. Потребление памяти – новый порядок сканирования может менять локальные пики.
  4. Стабильность и хвостовые задержки под типичной и пиковыми нагрузками.

Если найдете аномалии – команда Go просит делиться результатами в issue-трекере Green Tea. go.dev


Итог

Green Tea – это не «косметика», а смена единицы работы GC: страницы вместо объектов. Благодаря этому Go лучше использует кэши и префетчеры, снимает часть ограничений на масштабирование по ядрам и в сумме заметно сокращает CPU-накладные GC на реальных сервисах. Включите Green Tea на стенде, прогоните свои бенчмарки и профили – для многих сервисов это простой способ «вернуть» несколько процентов CPU без правок кода. Статус – эксперимент в Go 1.25, план по умолчанию в Go 1.26, с возможностью opt-out. go.dev+1

Полезно знать: формальный трекер дизайна и прогресса Green Tea – в GitHub-issue Go, там же указаны детали для обратной связи. 

+1
0
+1
1
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *