awesome-everything EN
↑ Обратно к восхождению

Производительность

GC tradeoffs: пауза, throughput, память и давление аллокаций

Суть Каждый коллектор выбирает две из трёх осей. Давление аллокаций — причина, пауза — симптом. Исправить первопричину всегда выгоднее, чем настраивать следствие.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Две команды. Одна переходит с G1 на ZGC и снижает паузы с 60 мс до <1 мс — но throughput падает на 12%, а память растёт на 18%. Другая сокращает аллокации на 60% и снижает паузы с 200 мс до 15 мс — без смены коллектора. Какая правка долгосрочнее?

3-way tradeoff

Каждый GC выбирает две из трёх:

КоллекторПаузаThroughputПамятьДля кого
JVM ParallelGCВысокая (сотни мс)ОтличныйНизкаяBatch-задачи без latency-требований
JVM G1Средняя (10–50 мс)ХорошийСредняяБольшинство production-сервисов
JVM ZGC<1 мс-10–15%+10–20%Latency-sensitive, кучи >8 ГБ
Go concurrent<1 мс-5–10%УмереннаяGo-сервисы со смешанным workload’ом
V8 Orinoco5–15 мсХорошийУмереннаяNode.js web workloads

Выбор коллектора — это вопрос workload’а. Batch-сервисам без требований к latency подходит throughput → ParallelGC. Latency-sensitive интерактивным сервисам нужны короткие паузы → ZGC, Shenandoah, Go. Неправильный выбор может стоить 30–50% throughput или 10x p99 latency.

Справочные числа GC

  • Go concurrent GC, типичный STW: ~100 мкс – 1 мс
  • JVM G1 по умолчанию: 200 мс (цель)
  • JVM ZGC пауза: <1 мс на кучах несколько ТБ
  • V8 Orinoco типичная пауза: 5–15 мс
  • Накладные расходы write barrier: ~2–10% CPU
  • Rate аллокаций, при котором GC начинает доминировать: ≥500 МБ/с/ядро
  • GOGC по умолчанию: 100 (куча удваивается между циклами)
  • Доля GC в здоровом сервисе: <5% CPU типично

Давление аллокаций как первопричина

Если сервис аллоцирует 1 ГБ/с, GC будет занят вне зависимости от коллектора. Давление аллокаций — причина; время паузы — симптом.

Сокращение давления аллокаций имеет неограниченный потенциал; настройка GC — ограниченный (оптимизирует стоимость работы, но не устраняет её). Паттерн среднего+ разработчика: когда GC широко представлен в профиле, смотрим на профиль аллокаций. Широкий листовой узел в профиле аллокаций — цель.

Приоритет правок (от большего рычага к меньшему):

  1. Устранить аллокацию целиком (мутация на месте, struct-of-arrays, примитивы).
  2. Пул/переиспользование аллокации (sync.Pool в Go, пулы объектов в Java/JS).
  3. Escape analysis — стековая аллокация вместо кучи (работает в Go, .NET, частично в JVM с C2).
  4. Уменьшить аллокацию (меньший struct, меньший буфер, предварительно sized контейнер).
  5. Убрать аллокацию с hot path (вычислить один раз, переиспользовать).
  6. Настроить коллектор (GOGC, MaxGCPauseMillis, max-old-space-size).
  7. Сменить алгоритм GC (ParallelGC → G1 → ZGC).
Почему это работает

Почему сокращение аллокаций улучшает и p99, и throughput одновременно, хотя их обычно представляют как tradeoff? Аллокации стоят дважды: один раз при аллокации (малая стоимость), второй при сборке GC (пропорционально аллокациям). Сокращение аллокаций снижает обе. p99 улучшается, потому что GC запускается реже и паузы короче. Throughput улучшается, потому что CPU, ранее тратившийся на GC (mark, scan, write barriers), теперь доступен для кода. Tradeoff «throughput vs latency» применим к настройке GC (длинные паузы → выше throughput); к сокращению аллокаций он не применим.

Object pooling: когда работает, когда нет

sync.Pool в Go, Apache Commons Pool в Java, пулы объектов в .NET — паттерны переиспользования аллокаций для снижения давления на GC.

Работает хорошо: объекты дорогие в создании и кратко используются на hot path — bytes.Buffer, JSON-энкодеры, regexp-объекты, scratch slices.

Не работает или вредит:

  • Объекты дёшевы в создании (экономия меньше накладных расходов пула).
  • Объекты долгоживущие (пул держит память без освобождения).
  • Координационные расходы потоков превышают экономию от аллокации.

sync.Pool в Go имеет преимущество: GC может опустошить пул между циклами — память не теряется навсегда. Пулы JVM/.NET держат память до явного освобождения.

Правило: пулить только то, что профиль отмечает как горячий узел аллокации. Не пулить превентивно.

Викторина

JVM-сервис видит паузы G1 периодически 200 мс, несмотря на -XX:MaxGCPauseMillis=50. Самый вероятный рычаг с наибольшим влиянием:

Викторина

V8-сервис в Node.js имеет память, растущую до 1,4 ГБ, после чего падает. Наиболее вероятная причина:

Расставь шаги по порядку

Расставьте рычаги снижения давления GC по приоритету — от наибольшего к наименьшему:

  1. 1 Устранить аллокацию целиком (мутация на месте, примитивы)
  2. 2 Пул/переиспользование аллокации (sync.Pool, ObjectPool)
  3. 3 Позволить escape analysis выполнить стековую аллокацию
  4. 4 Уменьшить аллокацию (меньший struct, предварительно sized)
  5. 5 Убрать аллокацию с hot path (вычислить один раз, переиспользовать)
  6. 6 Настроить коллектор (GOGC, MaxGCPauseMillis)
  7. 7 Сменить алгоритм GC (ParallelGC → G1 → ZGC)
Вспомните перед уходом
  1. 01
    Опишите 3-way tradeoff GC и объясните, почему сокращение аллокаций лучше настройки параметров коллектора.
  2. 02
    Когда object pooling снижает давление GC, а когда вредит?
Итог

Каждый коллектор выбирает две из трёх осей: пауза, throughput, память. JVM ParallelGC оптимизирует throughput ценой длинных пауз; ZGC покупает субмиллисекундные паузы за счёт throughput и памяти; G1 балансирует между всеми тремя. Go concurrent GC даёт субмиллисекундные паузы при умеренной стоимости. Давление аллокаций — причина высоких пауз; пауза — симптом. Сокращение аллокаций улучшает и p99, и throughput одновременно без ограничений, поскольку устраняет сам объём работы GC. Правильный приоритет: устранить аллокацию → пул → escape analysis → уменьшить → вынести с hot path → настроить → сменить коллектор. Object pooling снижает давление на горячих путях, но вредит для дешёвых или долгоживущих объектов.

Связанные уроки
встречается в159
Продолжить восхождение ↑Настройка GC: пейсинг, форма кучи и наблюдаемость аллокаций
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.