Погружение в V8. Часть 1. История.

Погружение в 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 устроена следующим образом:

  1. JavaScript-код парсится в абстрактное синтаксическое дерево (AST).
  2. AST компилируется в байткод интерпретатором Ignition.
  3. Байткод исполняется. При этом V8 отслеживает, какие участки кода повторяются чаще всего.
  4. «Горячий» код передаётся компилятору, который преобразует его в машинный код.
  5. Если поведение программы изменилось (например, типы данных стали другими), может произойти деоптимизация — возврат к интерпретации байткода.

Такой подход позволяет достичь баланса: сначала начать выполнение как можно быстрее, а затем — ускорить работу критических участков кода.

Дополнительно 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. От среды к среде.


Report Page