Истребители лагов. Часть первая
Лаги, или, другими словами, заметные задержки, могут быть обнаружены, когда Chrome не удаётся отобразить кадр за 16.66 мс (нарушая движение в 60 кадров в секунду). На сегодняшний день большая часть работы V8 по сборке мусора выполняется в основном потоке рендеринга, см. Рисунок 1, что часто приводит к лагам, когда необходимо обрабатывать слишком много объектов. Устранение лагов всегда было важным приоритетом для команды V8 (1, 2, 3). В этой статье обсуждаются некоторые оптимизации, которые были реализованы между Chrome 41 и Chrome 46 и значительно сокращают паузы при сборке мусора, обеспечивая более качественный пользовательский опыт.
Основным источником лагов во время сборки мусора является обработка различных структур данных учёта. Многие из этих структур данных позволяют проводить оптимизации, не связанные со сборкой мусора. Два примера — это список всех ArrayBuffers и список представлений каждого ArrayBuffer. Эти списки обеспечивают эффективную реализацию операции DetachArrayBuffer без негативного влияния на доступ к представлению ArrayBuffer. Однако в ситуациях, когда веб-страница создаёт миллионы ArrayBuffers (например, в играх на основе WebGL), обновление этих списков во время сборки мусора вызывает значительные задержки. В Chrome 46 эти списки были удалены, а вместо этого были введены проверки перед каждой загрузкой и записью в ArrayBuffers для обнаружения отключённых буферов. Это распределяет стоимость обработки большого списка учёта во время GC по всему выполнению программы, что приводит к меньшему количеству лагов. Хотя проверки при доступе теоретически могут замедлить скорость выполнения программ, активно использующих ArrayBuffers, на практике оптимизирующий компилятор V8 часто удаляет лишние проверки и выносит оставшиеся проверки за пределы циклов, обеспечивая более стабильный профиль выполнения без значительного общего ущерба производительности.
Другим источником лагов является учёт, связанный с отслеживанием времени жизни объектов, общих для Chrome и V8. Хотя куча памяти Chrome и V8 различны, их необходимо синхронизировать для определённых объектов, таких как DOM-узлы, которые реализованы в C++ коде Chrome, но доступны из JavaScript. V8 создаёт непрозрачный тип данных, называемый дескриптором (handle), который позволяет Chrome манипулировать объектом кучи V8, не зная деталей его реализации. Время жизни объекта связано с дескриптором: пока Chrome сохраняет дескриптор, сборщик мусора V8 не удалит объект. V8 создаёт внутреннюю структуру данных, называемую глобальной ссылкой для каждого дескриптора, который он передаёт обратно в Chrome через API V8, и эти глобальные ссылки сообщают сборщику мусора V8, что объект всё ещё активен. Для игр на WebGL Chrome может создавать миллионы таких дескрипторов, и V8, в свою очередь, должен создавать соответствующие глобальные ссылки для управления их жизненным циклом. Обработка огромного количества глобальных ссылок в основной паузе сборки мусора наблюдается как лаги. К счастью, объекты, передаваемые в WebGL, часто просто передаются и никогда не модифицируются, что позволяет провести статический анализ выхода. По сути, для функций WebGL, о которых известно, что они обычно принимают малые массивы в качестве параметров, данные копируются в стек, что делает глобальную ссылку излишней. Результатом такого смешанного подхода является сокращение времени паузы на до 50% для игр на WebGL, требующих интенсивного рендеринга.
Большая часть сборки мусора V8 выполняется в основном потоке рендеринга. Перенос операций по сборке мусора на конкурирующие потоки сокращает время ожидания сборщика мусора и ещё больше снижает лаги. Это по своей природе сложная задача, так как основное приложение JavaScript и сборщик мусора могут одновременно наблюдать и изменять одни и те же объекты. До сих пор параллелизм был ограничен очисткой старого поколения регулярной кучи объектов JS. Недавно мы также реализовали параллельную очистку пространства кода и пространства карт кучи V8. Кроме того, мы реализовали параллельное удаление неиспользуемых страниц, чтобы сократить объём работы, который необходимо выполнить в основном потоке, см. Рисунок 2.
Влияние обсуждаемых оптимизаций явно видно в играх на основе WebGL, например, демо Oort Online от Turbolenz. Следующее видео сравнивает Chrome 41 с Chrome 46:
В настоящее время мы продолжаем делать больше компонентов сборки мусора инкрементальными, фоновыми и параллельными, чтобы еще больше сократить время пауз сборки мусора на основном потоке. Оставайтесь на связи, так как у нас есть интересные обновления в разработке.