V8 рассказала про Oilpan: сборщик мусора для C++ сократил время sweeping на основном потоке на 42%
V8 (движок JavaScript от Google, используемый в Chrome) опубликовал первый пост новой серии про Oilpan, сборщик мусора для C++, управляющий памятью в рендер-движке Blink. Большая часть Blink написана на C++, но объектный граф вокруг DOM тесно переплетён с объектами JavaScript: страница создаёт DOM-узлы через JS, а обрабатывает их уже C++-код браузера. Несколько лет назад команда Chromium перешла на Oilpan именно для управления этой перемешанной C++/JS-памятью: Oilpan связывается с V8 через кросс-компонентную трассировку и рассматривает C++- и JS-объекты как единую кучу (heap).
Oilpan, сборщик типа mark-sweep: у него две фазы, marking, разметка достижимых объектов, и sweeping, уборка (освобождение) памяти недостижимых объектов. На фазе marking сборщик обходит граф объектов от корней (регистры, стек выполнения, глобальные переменные) и помечает всё достижимое. C++-объекты, управляемые Oilpan, сами описывают исходящие указатели на другие объекты через метод Trace(), в примере из поста класс LinkedNode, унаследованный от GarbageCollected
На фазе sweeping сборщик проходит по куче, находит непомеченные (мёртвые) объекты и освобождает их память, предварительно вызвав деструктор каждого такого объекта, нетривиальные деструкторы реализованы как финализаторы. Поскольку порядок вызова деструкторов не определён, Oilpan через отдельный плагин для Clang статически проверяет, что финализаторы не обращаются к другим объектам на управляемой куче.
Sweeping в Oilpan прошёл три этапа развития. Изначально это был stop-the-world sweeping, уборка внутри финализирующей паузы сборки мусора, останавливающая выполнение приложения на основном потоке. Затем sweeping сделали инкрементальным: работа разбивается на задачи основного потока и по возможности выполняется в простое, не мешая обычному выполнению JS. Наконец, с ростом объектных графов в реальных приложениях команда перешла к конкурентному sweeping, очистка памяти идёт в фоновом потоке параллельно с основным, пока выполняются два условия: фоновый поток обрабатывает только заведомо мёртвую (недостижимую) память, а приложение выделяет новую память только на уже обработанных страницах. Поскольку в C++ финализаторы обязаны выполняться на основном потоке, чтобы не создавать гонок данных в коде приложения, Oilpan откладывает их вызов: фоновый поток лишь ставит такие объекты в очередь, а сама финализация проходит отдельной фазой на основном потоке.
По данным внутреннего бенчмарк-фреймворка Google на реальных сценариях, фоновый (конкурентный) sweeping сократил время sweeping на основном потоке на 25-50%, в среднем на 42%. Функция доступна начиная с Chrome M78. Оставшееся время на основном потоке уходит на выполнение финализаторов, и команда параллельно работает над сокращением их числа у часто создаваемых типов объектов в Blink, при отсутствии финализаторов sweeping автоматически становится ещё быстрее.
Отдельно в посте отмечено: сейчас Oilpan реализован внутри Blink, но команда переносит его в V8 в виде самостоятельной библиотеки сборки мусора для C++. Цель, сделать C++-сборщик мусора доступным для всех разработчиков, встраивающих V8 в свои приложения (V8 embedders), а не только для Chromium. Конкретных сроков переноса или релиза в посте не названо; это первый материал серии, дальнейшие посты обещаны отдельно про фазу marking и обновления библиотеки Oilpan.
Ключевые факты
- Blink (рендер-движок Chrome) тесно перемешивает C++-объекты (DOM) с JS-объектами V8, поэтому для C++-памяти используется отдельный сборщик мусора Oilpan, связанный с V8 через кросс-компонентную трассировку единого графа объектов
- Oilpan, mark-sweep сборщик: сначала помечает достижимые объекты (marking), затем в фазе sweeping освобождает память недостижимых
- Sweeping прошёл путь от stop-the-world (останавливает приложение) к инкрементальному (по кусочкам в задачах основного потока), а затем к конкурентному, очистка памяти идёт в фоновом потоке параллельно с работой приложения
- Конкурентный (фоновый) sweeping сократил время sweeping на основном потоке на 25-50%, в среднем на 42%, по данным внутреннего бенчмарк-фреймворка Google; функция доступна с версии Chrome M78
- Сейчас Oilpan реализован внутри Blink, но переезжает в V8 отдельной библиотекой, чтобы её могли использовать все разработчики, встраивающие V8 в свои приложения (V8 embedders), а не только Chromium
Почему это важно
Blink, рендер-движок Chrome, написан в основном на C++, но его объектный граф вокруг DOM тесно переплетён с JS-объектами V8. Управлять такой смешанной памятью безопасно и быстро, нетривиальная задача, и пост показывает, как команда V8 довела очистку памяти до конкурентного (фонового) выполнения, сократив время sweeping на основном потоке в среднем на 42%.
Кому это важно
Разработчикам браузерных движков и Chromium, C++-инженерам, пишущим приложения поверх V8 (V8 embedders, например, встраивающие движок в собственный рантайм), и всем, кто интересуется внутренним устройством GC. Рядовым пользователям Chrome это тоже касается опосредованно: меньше времени на сборку мусора на основном потоке, меньше подтормаживаний страниц.
Как это применить
В посте описан базовый API Oilpan: класс наследуется от GarbageCollected
Можно ли доверять
Источник, официальный технический блог V8 (v8.dev), который ведёт сама команда движка; в посте приведены конкретные цифры бенчмарков и фрагменты кода. Числа получены из внутреннего бенчмарк-фреймворка Google на реальных сценариях, это не независимо проверенные измерения, а данные самой команды разработки.
Риски и подводные камни
Это первый пост в серии, и часть деталей (например, устройство фазы marking) обещана отдельными материалами позже. Сроки переноса Oilpan из Blink в V8 и его публичного релиза как отдельной библиотеки в посте не названы. Заявленное ускорение sweeping, усреднённый результат внутреннего набора бенчмарков, а не гарантия для любого конкретного сайта или приложения; итоговая скорость по-прежнему зависит от того, сколько финализаторов использует код, поскольку они остаются на основном потоке.