awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

Четырёхуровневый JIT-конвейер V8 и профилированная тиеризация

Суть Как Ignition, Sparkplug, Maglev и TurboFan работают вместе — затраты на компиляцию, скорость выполнения, FeedbackVector и рычаги управления для удержания кода на быстром уровне.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Страница ощущается отзывчивой через 200мс после загрузки. Через несколько секунд становится ещё быстрее. Третье ускорение появляется после тысяч взаимодействий. Это не магия — V8 перемещает горячий код выше по уровням, оставляя холодный дешёвым.

Четырёхуровневый конвейер

V8 имеет четыре режима выполнения, упорядоченных по соотношению стоимость компиляции / скорость выполнения.

Ignition — интерпретатор байткода. Парсер создаёт AST; Ignition преобразует его в компактный регистровый байткод и выполняет на виртуальной машине. При интерпретации Ignition записывает type feedback в FeedbackVector функции — слоты для каждой «интересной» точки (загрузка свойства, вызов функции, арифметика, сравнение). Дёшево входить (без затрат на компиляцию), ~10× медленнее на инструкцию по сравнению с оптимизированным нативным кодом.

Sparkplug — базовый JIT, добавлен в 2021 году. Sparkplug генерирует неспециализированный машинный код за один линейный проход по байткоду — без SSA, без реальной оптимизации, просто точный перевод каждого байткода в небольшой блок машинных инструкций плюс хвостовой вызов. Стоимость компиляции практически нулевая (~1 мс/кБ байткода); ускорение по сравнению с Ignition — 1.5–2×.

Maglev — оптимизирующий компилятор среднего уровня, добавлен в 2023 году. Maglev использует наблюдаемые типы из FeedbackVector и SSA-оптимизации для генерации специализированного машинного кода, но пропускает тяжёлые проходы TurboFan. ~10× медленнее компиляция, чем Sparkplug, ~10× быстрее, чем TurboFan; код примерно на полпути между ними по скорости выполнения.

TurboFan — тяжёлый оптимизирующий компилятор. Агрессивный инлайнинг, escape-анализ, анализ диапазонов, специализация на основе hidden class, спекулятивные оптимизации с возможностью deopt. Высокая стоимость компиляции (десятки-сотни мс для сложных функций); выдаёт самый быстрый код.

Стоимость компиляции и выполнения на уровнях JIT
Скорость компиляции Sparkplug
~1 мс / кБ байткода
Ускорение Sparkplug vs Ignition
~1.5–2×
Время компиляции Maglev
~10 мс / функция
Качество кода Maglev vs TurboFan
~50–70%
Время компиляции TurboFan
~100 мс / функция
Порог продвижения Sparkplug
~100 вызовов
Порог продвижения Maglev
несколько тысяч вызовов
Порог продвижения TurboFan
десятки тысяч вызовов

Профилированная тиеризация

Функции попадают в Ignition. При превышении порогов счётчика выполнения (для каждой функции, динамически) V8 продвигает на следующий уровень. FeedbackVector отслеживает не только типы, но и частоты веток, поэтому TurboFan может расположить горячий путь с оптимальным предсказанием ветвления. Продвижение происходит вне потока (concurrent compilation) — JS основной поток продолжает работать на текущем уровне, пока рабочий поток компилирует следующий.

Функции могут пропускать уровни: очень горячая функция, увиденная при старте, может прыгнуть с Ignition прямо в Maglev. Функции с нестабильным feedback (частые переходы IC) откладываются или пропускаются — TurboFan на нестабильной функции произведёт код, который немедленно deopt’нется, потратив 100 мс работы по компиляции впустую.

Операционные рычаги

Восемь шаблонов, удерживающих код на быстром уровне V8:

  1. Шаблон конструктора — объявляйте все свойства объекта в одном месте, чтобы hidden classes были стабильными.
  2. Избегайте delete — используйте присвоение null или undefined; delete принудительно переводит в dictionary mode.
  3. Держите функции monomorphic — не передавайте разные формы на одну точку вызова.
  4. Избегайте arguments в старом коде; используйте rest-параметры.
  5. Предварительно выделяйте массивы с известной ёмкостью, чтобы избежать изменения размера.
  6. Переиспользуйте пулы объектов для короткоживущих нагрузок (canvas, анимация) вместо создания новых объектов на каждый кадр.
  7. Избегайте огромных объектных литералов в горячих путях — они быстро становятся megamorphic.
  8. Для горячих числовых циклов используйте TypedArrays — V8 имеет высокоточные пути для Uint32Array и других, которые полностью обходят IC-слой.
Проследи
1/5

React-приложение падает до 20fps после добавления нового пропа к компоненту. Найдите JS-причину на уровне производительности.

1
Step 1 of 5
Шаг 1: 20fps означает, что кадр занимает >50мс при норме <16мс. Какие подсистемы могут поедать бюджет?
2
Locked
Шаг 2: Performance показывает 40мс Scripting на кадр вместо прежних 8мс. Новый проп задействован. Какова форма проблемы на уровне V8?
3
Locked
Шаг 3: как подтвердить нестабильность hidden class?
4
Locked
Шаг 4: структурное исправление?
5
Locked
Шаг 5: предотвратить регрессию?
Расставь шаги по порядку

Упорядочите шаги доступа к свойству в monomorphic IC, скомпилированном TurboFan:

  1. 1 TurboFan-скомпилированная функция получает указатель на объект в регистре
  2. 2 Первая инструкция: загрузить указатель hidden class из заголовка объекта
  3. 3 Сравнить загруженный hidden class с ожидаемым из времени компиляции
  4. 4 Если равно: прочитать свойство по заранее вычисленному смещению (1 инструкция MOV)
  5. 5 Если не равно: deopt — выбросить скомпилированный код, откатиться на нижний уровень
  6. 6 Продолжить выполнение на нижнем уровне; IC записывает новый hidden class и может сменить состояние
  7. 7 Функция может быть повторно оптимизирована позже с обновлёнными данными IC
Викторина

Почему V8 имеет ЧЕТЫРЕ уровня компиляции вместо двух?

Викторина

Функция внезапно замедляется с 50мкс до 2мс после 'небольшого' рефакторинга. Куда смотреть в первую очередь?

Почему это работает

Почему не компилировать всё через TurboFan с самого начала? Три причины. Первая: большинство функций выполняется один раз или никогда — стоимость компиляции TurboFan была бы чистыми потерями. Вторая: TurboFan нужен type feedback от Ignition для хороших спекуляций; без прогрева нет информации для специализации. Третья: задержка компиляции напрямую влияет на воспринимаемое время загрузки. Страница, загружающаяся 2 секунды, потому что каждая onload-функция была TurboFan-скомпилирована, ощущается сломанной. Многоуровневый дизайн ставит на первое место дешёвый Ignition, добираясь до дорогого TurboFan только для малой части кода, которая это оправдывает.

Вспомните перед уходом
  1. 01
    Почему V8 не компилирует каждую функцию через TurboFan немедленно?
  2. 02
    Что такое FeedbackVector и кто его читает?
  3. 03
    Каковы две главные причины существования Maglev рядом с TurboFan?
Итог

Четыре уровня V8 охватывают всю ось компромисса стоимость компиляции / скорость выполнения: Ignition (бесплатная компиляция, код в 10× медленнее), Sparkplug (~1мс компиляция, в 1.5–2× быстрее), Maglev (~10мс компиляция, 50–70% скорости TurboFan), TurboFan (~100мс компиляция, максимальная скорость). Функции поднимаются по уровням только когда они достаточно горячи и их FeedbackVector достаточно стабилен, чтобы оправдать стоимость компиляции. FeedbackVector — ключевая структура данных: по одному слоту на точку вызова, отслеживающему наблюдаемые hidden classes и частоты вызовов. Конкурентная компиляция держит основной поток работающим на текущем уровне, пока рабочий поток строит следующий. Операционные рычаги — стабильные конструкторы, без delete, monomorphic точки вызова, TypedArrays для числовых циклов — удерживают функции на быстром уровне на протяжении всего срока их жизни.

Связанные уроки
встречается в143
Продолжить восхождение ↑Hidden classes, деревья переходов и расположение в памяти
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.