Фоновая компиляция
TL;DR: Начиная с Chrome 66, V8 компилирует исходный код JavaScript в фоновом потоке, сокращая время, затрачиваемое на компиляцию в главном потоке, на 5–20% на типичных веб-сайтах.
Предыстория
Начиная с версии 41, Chrome поддерживает парсинг исходных файлов JavaScript в фоновом потоке через API V8 StreamedSource
. Это позволяет V8 начинать парсинг исходного кода JavaScript, как только Chrome загрузил первый фрагмент файла из сети, и продолжать парсинг параллельно с потоковой передачей файла через сеть в Chrome. Это может значительно улучшить время загрузки, так как V8 может почти завершить парсинг JavaScript к моменту завершения загрузки файла.
Однако из-за ограничений первоначального базового компилятора V8, V8 все еще нужно было вернуться в главный поток, чтобы завершить парсинг и скомпилировать скрипт в JIT машинный код, который выполняет код скрипта. С переходом на новый конвейер Ignition + TurboFan, мы теперь можем перенести компиляцию байткода в фоновый поток, освобождая главный поток Chrome для обеспечения более плавного и отзывчивого веб-серфинга.
Построение компилятора байткода для фонового потока
Компилятор байткода Ignition в V8 принимает абстрактное синтаксическое дерево (AST), созданное анализатором, в качестве входных данных и создает поток байткода (BytecodeArray
) вместе с сопутствующими метаданными, которые позволяют интерпретатору Ignition выполнять исходный JavaScript.
Компилятор байткода Ignition был изначально создан с учетом многопоточности, однако в конвейере компиляции потребовалось внести ряд изменений, чтобы включить фоновую компиляцию. Одним из основных изменений было предотвращение доступа конвейера компиляции к объектам в куче JavaScript в V8 при работе в фоновом потоке. Объекты в куче V8 не являются потокобезопасными, так как JavaScript однопоточен, и могут быть изменены в главном потоке или сборщиком мусора V8 во время фоновой компиляции.
В конвейере компиляции было два основных этапа, которые обращались к объектам в куче V8: интернализация AST и финализация байткода. Интернализация AST — это процесс, при котором литеральные объекты (строки, числа, шаблоны объектных литералов и т.д.), идентифицированные в AST, выделяются в куче V8, чтобы их можно было непосредственно использовать в созданном байткоде при выполнении скрипта. Этот процесс традиционно происходил сразу после того, как анализатор построил AST. В результате несколько шагов позднее в конвейере компиляции зависели от того, что литеральные объекты были выделены. Чтобы включить фоновую компиляцию, мы перенесли интернализацию AST на более поздний этап конвейера компиляции, после того как байткод был скомпилирован. Это потребовало изменения поздних этапов конвейера, чтобы обращаться к сырым литеральным значениям, встроенным в AST, вместо интернализированных значений в куче.
Финализация байткода включает создание окончательного объекта BytecodeArray
, используемого для выполнения функции, вместе с сопутствующими метаданными, например, ConstantPoolArray
, который хранит константы, упоминаемые в байткоде, и SourcePositionTable
, который сопоставляет строки и номера колонок исходного кода JavaScript с смещениями байткода. Так как JavaScript — динамический язык, все эти объекты должны находиться в куче JavaScript, чтобы их можно было собирать, если функция JavaScript, связанная с байткодом, собирается. Ранее некоторые из этих объектов метаданных выделялись и модифицировались во время компиляции байткода, что требовало доступа к куче JavaScript. Чтобы включить фоновую компиляцию, генератор байт-кода Ignition был переработан, чтобы отслеживать детали этих метаданных и откладывать их выделение в куче JavaScript до финальных этапов компиляции.
Благодаря этим изменениям почти вся компиляция скрипта может быть перенесена в фоновый поток, при этом только короткие этапы интернализации AST и финализации байткода выполняются в главном потоке непосредственно перед выполнением скрипта.
В настоящее время только код сценариев верхнего уровня и выражения функций с немедленным вызовом (IIFE) компилируются на фоне — вложенные функции по-прежнему компилируются лениво (при первом выполнении) в главном потоке. Мы надеемся в будущем расширить фоновую компиляцию на большее количество случаев. Однако, даже с этими ограничениями, фоновая компиляция оставляет главный поток свободным дольше, позволяя ему выполнять другие задачи, такие как реагирование на взаимодействие пользователя, рендеринг анимаций или создание более плавного и отзывчивого интерфейса.
Результаты
Мы оценили производительность фоновой компиляции, используя нашу систему тестирования производительности в реальных условиях на наборе популярных веб-страниц.
Доля компиляции, которая может выполняться в фоновом потоке, варьируется в зависимости от доли массива байт-кода, скомпилированного во время потоковой компиляции сценария верхнего уровня, в сравнении с ленивой компиляцией при вызове вложенных функций (что по-прежнему должно происходить в главном потоке). Таким образом, величина времени, сэкономленного в главном потоке, различается, причем большинство страниц демонстрируют сокращение времени компиляции в главном потоке от 5% до 20%.
Следующие шаги
Что может быть лучше, чем компиляция сценария на фоне? Вообще не компилировать сценарий! Вместе с фоновой компиляцией мы также работаем над улучшением системы кеширования кода V8, чтобы увеличить объем кода, кешируемого V8, таким образом ускоряя загрузку страниц для сайтов, которые вы посещаете часто. Мы надеемся вскоре поделиться с вами обновлениями в этом направлении. Оставайтесь с нами!