В чём разница между Garbage Collector и Incremental Garbage Collector?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между Garbage Collector и Incremental Garbage Collector
Основное различие между классическим Garbage Collector (GC, сборщик мусора) и Incremental Garbage Collector (инкрементальный GC) заключается в стратегии выполнения: традиционный GC выполняет сборку мусора за один непрерывный "стоп-мир" (stop-the-world) этап, который может вызывать заметные фризы (просадки FPS), в то время как инкрементальный GC разбивает эту работу на множество маленьких этапов, выполняемых между кадрами игры, что значительно снижает воздействие на производительность и плавность геймплея. В контексте Unity это критически важно, поскольку движок исторически страдал от проблем с GC-фризами, особенно на платформах с ограниченными ресурсами (мобильные устройства, VR).
Классический Garbage Collector в Unity
До версии 2019.3 Unity использовала Boehm-Demers-Weiser garbage collector для управляемого кода (C#). Его работа выглядела так:
- Монофазный сбор: При запуске GC приложение полностью останавливается (stop-the-world).
- Mark and Sweep: Алгоритм проходит по всем "живым" объектам, начиная с корневых ссылок (статические поля, локальные переменные в стеке, регистры CPU), и помечает их. Все непомеченные объекты считаются мусором и удаляются в фазе очистки (sweep).
- Проблема: Если управляемая куча (managed heap) большая (содержит много объектов), процесс маркировки и очистки может занимать десятки миллисекунд. В игре, работающей на 60 FPS (где на кадр отводится всего ~16.6 мс), это приводит к явному "зависанию" или подёргиванию изображения.
// Пример кода, который может спровоцировать большой GC-аллокейшн и фриз
void Update() {
// Создание новой строки в каждом кадре -> аллокация в куче
string debugText = "Frame: " + Time.frameCount;
// Использование debugText...
// В конце кадра строка становится мусором.
// Когда куча заполнится, запустится Stop-the-World GC.
}
Incremental Garbage Collector (Incremental GC)
Начиная с версии Unity 2019.3, был введён Incremental Garbage Collector как усовершенствованная альтернатива (а позже — как настройка по умолчанию). Его ключевая идея — инкрементальность.
- Разделение работы: Тяжёлая фаза маркировки (marking) разбивается на множество мелких пакетов работ.
- Работа между кадрами: Unity выполняет небольшую порцию работы по маркировке в промежутках между кадрами, обычно в конце одного кадра или начале следующего, в течение выделенного временного интервала (например, 2-5 мс).
- Отсутствие длительных фризов: Поскольку работа растянута во времени, вместо одного длительного 50-миллисекундного фриза вы получаете, например, 10 едва заметных микро-фризов по 5 мс каждый, что гораздо меньше влияет на воспринимаемую плавность.
Важные технические детали Incremental GC в Unity:
- Не инкрементальная очистка: Фаза очистки (sweep) и сжатия (compaction, если включено) по-прежнему выполняются как единый стоп-мир этап. Однако они обычно значительно быстрее фазы маркировки.
- Влияние на производительность CPU: Инкрементальный GC добавляет небольшие постоянные накладные расходы на CPU (постоянная работа по маркировке), но исключает катастрофические пиковые нагрузки. Это trade-off в пользу стабильности.
- Требует настройки: Для эффективной работы нужно корректно настроить интервал работы GC (время, выделяемое на инкрементальную маркировку за кадр) через
GarbageCollector.incrementalTimeSliceNanoseconds.
// Пример настройки Incremental GC через скрипт (обычно делается один раз при запуске)
using UnityEngine;
using UnityEngine.Scripting;
public class GCConfigurator : MonoBehaviour {
void Start() {
// Установка целевого времени работы инкрементального GC за кадр (например, 1 миллисекунда).
// Значение в наносекундах: 1 мс = 1 000 000 нс.
GarbageCollector.incrementalTimeSliceNanoseconds = 1000000;
// Включение инкрементационного режима (по умолчанию включён в новых версиях).
// GarbageCollector.incrementalModeEnabled = true;
}
}
Практические выводы для Unity-разработчика
- Цель одна, методы разные: Оба сборщика преследуют одну цель — автоматическое освобождение памяти от неиспользуемых управляемых объектов (в C#). Разница — в алгоритмической реализации, влияющей на производительность.
- Incremental GC — не панацея: Он маскирует проблему, а не решает её. Если ваш код генерирует гигабайты мусора в секунду, инкрементальный GC не успеет его убирать, и стоп-мир этапы или постоянные микропаузы станут неизбежны. Оптимизация аллокаций (пулинг объектов, кэширование, отказ от генерации мусора в
Update()) остаётся первостепенной задачей. - Выбор в настройках проекта: В Player Settings > Other Settings > Configuration можно выбрать тип сборщика: Incremental (рекомендуется), Boehm GC (устаревший) или Disabled (только для продвинутых сценариев, например, с
Unity.Collectionsи ручным управлением). - Мониторинг: Используйте Unity Profiler (окно Memory) или Deep Profiling для отслеживания активности GC, размера кучи и того, какие вызовы методов вызывают аллокации.
Заключение: Переход с классического на инкрементальный сборщик мусора в Unity стал важнейшим шагом в улучшении пользовательского опыта, особенно для платформ, чувствительных к задержкам. Однако понимание принципа его работы лишь подчёркивает старую истину: самый эффективный сборщик мусора — тот, который не должен запускаться слишком часто, благодаря грамотно написанному коду разработчика.