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

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

Спекулятивный движок TurboFan и ловушка deopt-loop

Суть Sea-of-nodes IR TurboFan, escape-анализ, полиморфный инлайнинг, спекулятивные защиты, FeedbackVector подробно, однопроходный Sparkplug и диагностика deopt-loop с --trace-deopt.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 18 min

Критичная для производительности функция повторно deopt’нется в production — каждый вызов запускает 100мс перекомпиляцию TurboFan, затем снова deopt. Ваш горячий путь на порядки медленнее, чем некомпилированный. Это deopt-loop, и он начинается с одной неправильно понятой защиты.

Спекулятивный движок оптимизации TurboFan

TurboFan строит sea-of-nodes граф IR и выполняет:

  • Агрессивный инлайнинг — точки вызова с известными целями встраиваются, устраняя накладные расходы вызова и открывая возможности кросс-функциональных оптимизаций.
  • Escape-анализ — объекты, не выходящие за пределы функции, выделяются на стеке или вовсе устраняются (ноль аллокаций, нет давления на GC).
  • Полиморфный инлайнинг — до 4 известных целей вызова встраиваются с предварительной проверкой класса; общий медленный путь обрабатывает редкие формы.
  • Анализ диапазонов — переменная, известная как 0..255, остаётся в байтовом регистре; индекс цикла с известными границами избегает проверок переполнения.
  • Сужение типов — итеративное уточнение типов через граф, позволяющее создавать более точный машинный код.

При каждом спекулятивном предположении TurboFan устанавливает инструкцию защиты: «это smi» перед арифметикой, «этот объект имеет hidden class HCx» перед доступом к свойству. Сбой защиты означает deopt. Компромисс: код TurboFan часто в 3–10× быстрее Maglev при соблюдении защит; при их нарушении накладные расходы хуже, чем никогда не компилировать через TurboFan.

FeedbackVector подробно

У каждой функции есть связанный FeedbackVector — массив фиксированного размера в куче GC, по одному слоту на IC-сайт (загрузка свойства, вызов, бинарная операция). Слоты хранят:

  • Один указатель на hidden class (monomorphic)
  • Небольшой массив классов (polymorphic)
  • MegamorphicSentinel (сдался)

Все четыре уровня читают его: Sparkplug читает минимальный feedback (тип), Maglev читает более богатые данные (цепочки классов, частоты целей вызовов), TurboFan читает всё плюс количество итераций цикла и вероятности ветвлений.

Загрязнение FeedbackVector: один megamorphic-слот объясняет, почему V8 не может держать функцию оптимизированной даже после deopt-и-reopt циклов. %DebugPrintFeedback(fn) в d8 выводит вектор; его инспекция — отправная точка для диагностики постоянного deopt-поведения.

Sparkplug: JIT, который не оптимизирует

Sparkplug (V8 9.1, май 2021) генерирует машинный код из байткода за один линейный проход без SSA, инлайнинга или планирования инструкций. Для каждого байткода Ignition Sparkplug генерирует фиксированный блок нативных инструкций — примерно в 1.5–2× быстрее Ignition, так как накладные расходы цикла диспетчеризации интерпретатора (поиск в таблице, косвенный прыжок) заменяются прямым выполнением. Скорость компиляции: ~1мс/кБ байткода. Задокументированная средняя польза: 5–15% на реальных нагрузках. Цель: дать горячим-но-не-достаточно-горячим-для-Maglev функциям ускорение без оплаты стоимости компиляции Maglev.

Maglev: SSA, но дёшево

Maglev (2023) заполняет пробел между Sparkplug и TurboFan. Конвейер: байткод → Maglev IR (SSA) → линейный распределитель регистров → нативный код. IR проще, чем у TurboFan (без цепочек эффектов, без проходов перезаписи графа); распределитель регистров линейного сканирования вместо раскраски графа. Maglev выполняет некоторые спекулятивные оптимизации — специализирует загрузки свойств и арифметику по наблюдаемым типам — но пропускает escape-анализ, полиморфный инлайнинг и итерацию сужения типов. Результат: ~10мс компиляция, ~50–70% качества кода TurboFan, правильный компромисс для средне горячего кода.

V8: числа старшего уровня
Sparkplug отгружен
V8 9.1 (май 2021)
Maglev отгружен
V8 11.0 (середина 2023)
Время компиляции TurboFan
~100 мс / функция
Время компиляции Maglev
~10 мс / функция
Размер IC-слота
16 байт (8Б класс + 8Б хендлер)
Скорость компиляции Sparkplug
~1 мс / кБ байткода
TurboFan vs Maglev скорость
TurboFan ~1.5–3× быстрее при стабильных защитах
Проследи
1/5

Критичная для производительности функция deopt-loop'ится в production. Трасируйте и исправьте.

1
Step 1 of 5
Шаг 1: deopt-loop означает непрерывный tier-down затем tier-up. Что в --trace-deopt это подтвердит?
2
Locked
Шаг 2: причина — 'not a smi'. Что это означает?
3
Locked
Шаг 3: найдите проблемную строку. Как?
4
Locked
Шаг 4: исправление?
5
Locked
Шаг 5: предотвратить повторение?
Найди ошибку

Диагностируйте лог deopt V8 — в чём первопричина?

log
[deoptimizing (DEOPT eager): begin 0x... <JSFunction processItem (sfi = 0x...)> (opt #42) @14, FP to SP delta: 128, caller sp: 0x...]
          ;;; deoptimize at <main.js:42:18>, not a Smi
 bytecode position 14
[deoptimizing (eager): end 0x... processItem  @14 => node=4, pc=0x..., caller sp=0x..., took 0.012 ms]
[marking 0x... <JSFunction processItem (sfi = 0x...)> for non-concurrent optimization]
[compiling method 0x... <JSFunction processItem (sfi = 0x...)> using TurboFan]
[deoptimizing (DEOPT eager): begin 0x... <JSFunction processItem (sfi = 0x...)> (opt #43) @14, FP to SP delta: 128, caller sp: 0x...]
          ;;; deoptimize at <main.js:42:18>, not a Smi
 bytecode position 14

Одна функция продолжает deopt'иться с причиной 'not a Smi' в строке 42:18. Что происходит и как это исправить?

Викторина

V8 имеет ЧЕТЫРЕ JIT-уровня и конкурентный GC. Какова глубинная причина производительности, почему они необходимы вместо одного хорошего оптимизатора?

Викторина

Функция обрабатывает 10М элементов на кадр и monomorphic в тестах, но megamorphic в production. Какова наиболее вероятная причина?

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

Почему TurboFan использует sea-of-nodes IR вместо традиционного CFG с базовыми блоками? Sea-of-nodes представляет как поток управления, так и поток данных как рёбра в одном графе — нет понятия «порядок инструкций» до планирования. Это позволяет более агрессивные оптимизации: escape-анализ может поднять аллокации из циклов, анализ диапазонов распространяет границы через ветки, а устранение мёртвого кода работает на уровне выражений, а не только базовых блоков. Цена: компилятор значительно сложнее и труднее для отладки. Но для динамически типизированного языка, где компилятор должен рассуждать о типах, наблюдаемых во время выполнения, а не объявленных во время компиляции, гибкость стоит этого.

Вспомните перед уходом
  1. 01
    Что такое sea-of-nodes IR TurboFan и какие оптимизации он позволяет?
  2. 02
    Объясните 'загрязнение FeedbackVector' и как его диагностировать.
  3. 03
    Чем deopt-loop отличается от разового deopt?
Итог

TurboFan строит sea-of-nodes IR и применяет escape-анализ, полиморфный инлайнинг, сужение диапазонов и агрессивную специализацию типов. Каждое спекулятивное предположение становится инструкцией защиты; сбой защиты вызывает деоптимизацию. Единичный deopt стоит микросекунды; deopt-loop — где TurboFan перекомпилирует и deopt’ится при каждом вызове — стоит дороже, чем никогда не оптимизировать. FeedbackVector — профильные данные, движущие каждым уровнем: Ignition пишет его, TurboFan читает для знания форм и типов специализации. Загрязнение FeedbackVector (megamorphic-слоты) может полностью заблокировать TurboFan. Sparkplug (V8 9.1) — базовый уровень стоимостью ~1мс/кБ, дающий 1.5–2× над Ignition; Maglev (2023) добавляет SSA-специализацию за ~10мс компиляции, достигая 50–70% качества TurboFan. Правильная ментальная модель: уровни — не водопад, а лестница с лифтами — функции могут быть на разных уровнях одновременно, а эвристики тиеризации постоянно пересматриваются на основе данных FeedbackVector.

Связанные уроки
встречается в143
Продолжить восхождение ↑V8 в production: Isolates, сжатие указателей и реальные аварии
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.