Перейти к основному содержимому

Истребители лагов. Часть первая

· 4 мин. чтения
Истребители лагов: Йохен Айзингер, Михаэль Липпаутц и Ханнес Пэйер

Лаги, или, другими словами, заметные задержки, могут быть обнаружены, когда Chrome не удаётся отобразить кадр за 16.66 мс (нарушая движение в 60 кадров в секунду). На сегодняшний день большая часть работы V8 по сборке мусора выполняется в основном потоке рендеринга, см. Рисунок 1, что часто приводит к лагам, когда необходимо обрабатывать слишком много объектов. Устранение лагов всегда было важным приоритетом для команды V8 (1, 2, 3). В этой статье обсуждаются некоторые оптимизации, которые были реализованы между Chrome 41 и Chrome 46 и значительно сокращают паузы при сборке мусора, обеспечивая более качественный пользовательский опыт.

Рисунок 1: Сборка мусора в основном потоке

Основным источником лагов во время сборки мусора является обработка различных структур данных учёта. Многие из этих структур данных позволяют проводить оптимизации, не связанные со сборкой мусора. Два примера — это список всех 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.

Рисунок 2: Некоторые операции сборки мусора, выполненные на потоках сборки мусора в фоновом режиме.

Влияние обсуждаемых оптимизаций явно видно в играх на основе WebGL, например, демо Oort Online от Turbolenz. Следующее видео сравнивает Chrome 41 с Chrome 46:

В настоящее время мы продолжаем делать больше компонентов сборки мусора инкрементальными, фоновыми и параллельными, чтобы еще больше сократить время пауз сборки мусора на основном потоке. Оставайтесь на связи, так как у нас есть интересные обновления в разработке.