Погружение в V8. Часть 1. История.
Anastasia KotovaВведение
В предыдущих статьях мы много говорили о Node.js: как устроен его Event Loop и какую роль в этом играет библиотека libuv.
Теперь настало время поговорить про то, что объединяет Node.js и браузеры. Речь, конечно, о V8 — движке, на котором исполняется JavaScript.
В этом небольшом цикле статей мы разберём основные компоненты V8, интересные особенности и внутренние механизмы. Мы попробуем заглянуть внутрь, а также проанализировать работу JavaScript-кода с помощью профилирования. Но начать стоит с истории.
Происхождение V8
V8 — это высокопроизводительный движок с открытым исходным кодом, разработанный для выполнения JavaScript. Он был представлен Google в 2008 году вместе с первым релизом браузера Chrome.
Сегодня V8 используется в Chrome и Node.js. В последнем он позволяет исполнять JavaScript вне браузера и обращаться к системным возможностям.
Название «V8», кстати, отсылает к восьмицилиндровому двигателю внутреннего сгорания — и действительно, этот движок стал одним из самых быстрых благодаря гибридной компиляции, множеству оптимизаций и эффективной системе управления памятью.
JIT-компиляция и байткод
Всё, что исполняется на компьютере, в конечном итоге превращается в машинный код — последовательность нулей и единиц, понятная процессору. Это низкоуровневый язык, специфичный для архитектуры устройства (например, x86 или ARM).
Разработчики, конечно, пишут не на машинном коде, а на языках высокого уровня. Чтобы такой код можно было исполнить, он должен пройти через серию преобразований. Существуют разные подходы к этому процессу:
- Интерпретация. Код исполняется построчно, без предварительного преобразования в машинный код. Такой подход прост и гибок, но медленный.
- AOT-компиляция (ahead-of-time). Программа полностью компилируется в машинный код заранее, до запуска. Это даёт высокую скорость выполнения, но снижает гибкость.
- JIT-компиляция (just-in-time). Промежуточный подход: код компилируется в машинный во время исполнения, по мере необходимости.
До появления V8 большинство JavaScript-движков работали как интерпретаторы. Код выполнялся строчка за строчкой, что значительно ограничивало производительность. Например, в SpiderMonkey от Mozilla использовалась именно такая модель.
С ростом популярности динамичных веб-приложений началась гонка за скорость. WebKit представил проект SquirrelFish Extreme, в котором уже применялась JIT-компиляция, но её архитектура была менее производительной и универсальной.
Революционность V8 заключалась в том, что он первым стал компилировать JavaScript напрямую в машинный код, минуя промежуточную стадию байткода. Это обеспечило значительный прирост производительности по сравнению как с интерпретаторами, так и с ранними JIT-подходами.
Байткод — это промежуточный код, создаваемый на основе исходного. Он не зависит от архитектуры процессора и не исполняется напрямую, но может интерпретироваться или служить основой для компиляции в машинный код.
Со временем выяснилось, что отсутствие байткода затрудняет управление памятью и замедляет запуск, особенно при выполнении кода, который вызывается однократно. Поэтому в 2016 году в V8 появился Ignition — интерпретатор, преобразующий JavaScript в байткод и исполняющий его. Это позволило ускорить старт программы.
Сейчас JIT-компиляция в V8 устроена следующим образом:
- JavaScript-код парсится в абстрактное синтаксическое дерево (AST).
- AST компилируется в байткод интерпретатором Ignition.
- Байткод исполняется. При этом V8 отслеживает, какие участки кода повторяются чаще всего.
- «Горячий» код передаётся компилятору, который преобразует его в машинный код.
- Если поведение программы изменилось (например, типы данных стали другими), может произойти деоптимизация — возврат к интерпретации байткода.
Такой подход позволяет достичь баланса: сначала начать выполнение как можно быстрее, а затем — ускорить работу критических участков кода.
Дополнительно V8 использует скрытые классы, встроенные кэши и продвинутые алгоритмы управления памятью, что позволяет запускать сложные веб-приложения с производительностью, близкой к нативным.
Этапы развития
V8 появился в 2008 году вместе с браузером Chrome. Его разработкой руководил Ларс Бак. В ранних версиях использовались простые парсер и компилятор. Тогда же использовался ассемблер Strongtalk.
Позднее движок развивался следующим образом:
- 2010 — появление Crankshaft, оптимизирующего компилятора, который значительно повысил скорость исполнения.
- 2015 — переход к TurboFan, который стал более универсальным и позволил улучшить поддержку новых возможностей JavaScript.
- 2016 — добавлен Ignition — интерпретатор, исполняющий байткод. Это позволило ускорить запуск и снизить издержки на старте.
- 2021 — появился SparkPlug, компилятор, призванный сократить разрыв между интерпретацией и оптимизацией.
- 2023 — добавлен Maglev, компилятор с упором на скорость и эффективное использование памяти.
Альтернативы V8
Существуют и другие движки JavaScript, используемые в разных браузерах и системах:
- JavaScriptCore (JSC) — движок от Apple, лежит в основе Safari. Он сфокусирован не только на скорости, но и на энергоэффективности, что особенно важно для мобильных устройств.
- SpiderMonkey — движок Mozilla Firefox. Один из первых движков для JavaScript, с поддержкой всех современных стандартов.
Главное отличие V8 — приоритет на JIT-компиляцию и адаптацию под современные веб-приложения. Его широкое распространение через Chrome и Node.js сделало его де-факто стандартом в мире фронтенд-разработки.
Следующие части
Погружение в V8. Часть 2. Из чего состоит движок.
Погружение в v8. Часть 3. Парсинг, AST и анализ кода.
Погружение в v8. Часть 4. Управление памятью и сборка мусора.
Погружение в v8. Часть 5. Скрытые оптимизации.
Погружение в v8. Часть 6. От среды к среде.