В приложении число короткоживущих объектов резко выросло, но объём живой памяти почти не изменился. Почему производительность Go может заметно ухудшиться?
Производительность может снизиться из-за роста скорости аллокаций, даже если объём живых данных остаётся прежним. Go чаще запускает сборку мусора, тратит CPU на обслуживание аллокатора и может заставлять горутины участвовать в маркировке через mutator assist.
Go использует автоматическое управление памятью, чтобы разработчик не освобождал объекты вручную и не управлял временем их удаления. Для снижения пауз применяется конкурентный трассирующий GC, который выполняет значительную часть работы параллельно с пользовательскими горутинами.
Такой подход переносит часть стоимости управления памятью в рантайм. Поэтому важен не только объём занятой памяти, но и интенсивность создания объектов и частота циклов сборки.
Короткоживущие объекты быстро становятся недостижимыми, поэтому они могут почти не увеличивать объём живой кучи. Однако каждая аллокация требует работы: выбора участка памяти, обновления метаданных и последующего обнаружения объекта сборщиком.
При высокой скорости аллокаций GC запускается чаще или работает практически постоянно. В результате растёт загрузка CPU, увеличивается давление на аллокатор, а при недостаточном темпе маркировки приложение может выполнять часть работы GC вместо полезной работы.
Сборщик ориентируется на рост размера кучи относительно объёма, оставшегося после предыдущей сборки, с учётом настройки GOGC. Если приложение быстро создаёт и уничтожает объекты, порог следующего цикла достигается быстро, даже когда после сборки остаётся примерно тот же небольшой объём живых данных.
Во время цикла GC рантайм просматривает корни и объекты, содержащие указатели, чтобы определить достижимые данные. Короткоживущие объекты всё равно создают нагрузку на аллокатор и могут попадать в область, которую необходимо обработать до того, как станет ясно, что они недостижимы.
При высокой аллокационной нагрузке сборщик применяет пейсинг: горутины могут получать обязанность выполнять маркировочную работу. Это называется mutator assist и позволяет удерживать GC в заданном темпе, но непосредственно уменьшает CPU-время, доступное приложению.
Оптимизация должна начинаться с измерения: профилирование аллокаций показывает функции, создающие объекты, а профилирование CPU помогает оценить стоимость GC. Обычно полезнее уменьшить количество временных объектов или переиспользовать безопасные буферы, чем просто увеличивать GOGC.
Повышение GOGC может снизить частоту циклов и загрузку CPU, но увеличит допустимый объём памяти между сборками. Пулы объектов иногда уменьшают число аллокаций, однако усложняют владение объектами, могут удерживать память дольше необходимого и не всегда улучшают производительность.
Сервис обработки сообщений стал потреблять больше CPU после добавления промежуточного преобразования данных. Объём живой памяти почти не изменился, но профилирование показало большое число временных строк и небольших структур на каждый запрос.
Рассматривались три варианта. Увеличение GOGC быстро снижало частоту GC, но повышало пиковое потребление памяти. Глобальный пул объектов уменьшал число аллокаций, но создавал риск удержания крупных буферов и усложнял повторное использование между запросами.
Выбрали устранение промежуточных объектов в наиболее горячем пути и локальное переиспользование буфера с чётким временем его возврата. Это уменьшило скорость аллокаций без существенного роста удерживаемой памяти и снизило долю CPU, занятую GC.
Ответ: Порог запуска связан не только с текущим объёмом живых данных, а с тем, сколько памяти было выделено после предыдущего цикла и какой целевой рост допускает настройка GC. Быстрое создание мусора быстро приводит приложение к этому порогу. Поэтому стабильный размер живой кучи не означает низкую частоту сборки.
Ответ: Нет. Более редкие циклы обычно снижают CPU-расходы сборщика, но позволяют куче сильнее вырасти. Это увеличивает требования к памяти, может ухудшить локальность данных и повысить стоимость отдельных циклов. Оценивать нужно одновременно задержки, CPU, пиковую память и пропускную способность.
Ответ: Аллокация имеет стоимость независимо от того, насколько долго объект живёт и попадёт ли он в полноценную маркировку. Рантайм должен выдать память, обновить состояние аллокатора и учесть выделение в механизме планирования GC. Снижение числа таких операций уменьшает нагрузку на рантайм и вероятность mutator assist, даже если выигрыш от уменьшения объёма сканирования невелик.