Обучение подруги глубокому пониманию движка JS.

внешний интерфейс JavaScript V8
Обучение подруги глубокому пониманию движка JS.

Вкусная ценность: 🌟🌟🌟🌟🌟

Вкус: Томатная жирная говядина

Хозяйка кафетерия: Босс, вас спросят в интервью о принципе работы двигателя Chrome V8?

Владелец кафетерия: Эти знания можно не только задать на собеседовании, но и изучить принцип работы JS-движка, а также лучше понять JavaScript, лексический анализ Babel и синтаксический анализ во фронтенд-экосистеме, принцип проверки синтаксиса ESLint и React, Vue. и другие интерфейсные фреймворки. Короче говоря, можно сказать, что принцип механизма обучения служит нескольким целям.

Хозяйка столовой: Ладно, не многословие, давайте начнем~

Макро вид V8

V8 – это наш знаменитый интерфейс в Интернете. Он написан на C++. Это высокопроизводительный движок Google с открытым исходным кодом для JavaScript и WebAssembly. Он в основном используется в Chrome, Node.js, Electron... .

Прежде чем мы начнем говорить о нашем главном двигателе V8, давайте поговорим о положении V8 с точки зрения макроэкономики и установим мировоззрение.

Сегодня, с бурным развитием информационных технологий, этот сумасшедший мир полон различных электронных устройств, таких как мобильные телефоны, компьютеры, электронные часы, умные колонки и все больше и больше электромобилей, которыми мы пользуемся каждый день.

Как инженеры-программисты, мы можем коллективно понимать их как «компьютеры», состоящие из中央处理器(CPU)、存储以及输入、输出设备构成. Процессор похож на повара, отвечающего за выполнение команд в соответствии с рецептом приготовления. Хранилище похоже на холодильник, ответственный за сохранение данных и команд (ингредиентов), которые должны быть выполнены.

Когда компьютер включен, ЦП начинает читать инструкции из определенного места в хранилище, выполняет инструкции одну за другой в соответствии с инструкциями и начинает работать. Компьютер также может получить доступ к различным внешним устройствам, таким как: мышь, клавиатура, экран, движок и так далее. ЦП не обязательно знать все возможности этих устройств, он отвечает только за обмен данными с портами этих устройств. Производители оборудования также поставят оборудование с программным обеспечением, которое соответствует оборудованию для работы с ЦП. Сказав это, мы получили самый простой компьютер, который также представляет собой архитектуру, предложенную отцом компьютера фон Нейманом в 1945 году.

Однако из-за того, что машинные инструкции очень недружественны для человека, их трудно читать и запоминать, поэтому люди изобрели языки программирования и компиляторы. Компиляторы могут преобразовывать языки, которые легче понять людям, в машинные инструкции. Кроме того, нам также нужна операционная система, которая поможет нам решить проблему управления программным обеспечением. Мы знаем, что существует множество операционных систем, таких как Windows, Mac, Linux, Android, iOS, Hongmeng и т. д. Существует бесчисленное множество устройств, использующих эти операционные системы. Браузер был создан для того, чтобы устранить разнообразие клиентов, обеспечить кросс-платформенность и предоставить единый программный интерфейс.

Таким образом, мы можем думать о браузере как об операционной системе поверх операционной системы, а для кода JavaScript, с которым мы больше всего знакомы у разработчиков интерфейса, движок браузера (например, V8) — это весь его мир.

Самый мощный движок JavaScript на планете

Без сомнения, V8 — самый популярный и мощный движок JavaScript, а название V8 навеяно двигателем классического «мускулара» 50-х годов.

Programming Languages Software Award

V8 также получил одобрение научных кругов и выиграл ACM SIGPLAN.Programming Languages Software Award.

Основной JS-движок

Основные движки JavaScript следующие:

Цикл выпуска V8

Команда V8 использует 4 канала выпуска Chrome, чтобы предлагать пользователям новые версии.

  • Канарские релизы (ежедневно)
  • Релизы для разработчиков (еженедельно)
  • Бета-версии (каждые 6 недель)
  • Стабильные релизы (каждые 6 недель)

Чтобы узнать больше, нажмитеПроцесс выпуска версии двигателя V8.

История развития архитектуры V8

2 сентября 2008 г. исходный код V8 был открыт в тот же день, что и Chrome, а первоначальная фиксация кода датируется 30 июня 2008 г. Вы можете просмотреть визуальную эволюцию кодовой базы V8 по ссылке ниже.

В то время архитектура V8 была простой и грубой, с однимCodegenпереводчик.

В 2010 году V8 добавилCrankshaftОптимизирующий компилятор значительно повышает производительность во время выполнения. Машинный код, сгенерированный Crankshaft, в два раза быстрее предыдущего компилятора Codegen, при этом он на 30% меньше.

В 2015 году для дальнейшего повышения производительности V8 представилTurboFanОптимизирующий компилятор.

Затем наступил переломный момент: до этого V8 предпочитала компилировать исходный код непосредственно в архитектуру машинного кода. Однако с ростом популярности Chrome на мобильных устройствах команда V8 обнаружила фатальные проблемы в этой архитектуре: время компиляции слишком велико, а память машинного кода очень велика.

Поэтому команда V8 провела рефакторинг архитектуры двигателя и представила его в 2016 году.Ignition 解释器和字节码.

В 2017 году V8 по умолчанию открывает новую компиляцию.pipeline(Ignition + TurboFan), и удаленоFull-codegen 和 Crankshaft.

Высокопроизводительный движок JS требует не только высокооптимизированного компилятора, такого как TurboFan, но также есть много возможностей для оптимизации, прежде чем компилятор сможет начать работать.

Итак, в 2021 году V8 представляет новый конвейер компиляции.Sparkplug.

Для получения дополнительной информации о Sparkplug, пожалуйста, нажмитеSparkplug

Canteen Boss: Оказывается, архитектура V8 претерпела столько изменений

Владелец столовой: Да, команда V8 приложила немало усилий для постоянной оптимизации характеристик двигателя.

Как работает V8

敲黑板,进入本文的重点。

Хозяйка столовой: Достаньте блокнот и запишите его.

Основной процесс выполнения кода JavaScript V8 разделен на следующие два этапа:

  • компилировать
  • воплощать в жизнь

编译阶段指 V8 将 JavaScript 转换为字节码或者二进制机器码,执行阶段指解释器解释执行字节码,或者 CPU 直接执行二进制机器码。

Чтобы лучше понять общий механизм работы V8, давайте сначала разберемся со следующими понятиями.

Машинный язык, язык ассемблера, язык высокого уровня

Набор инструкций ЦП — это машинный язык, а ЦП может распознавать только двоичные инструкции. Но людям трудно читать и запоминать двоичный код, поэтому люди преобразуют двоичный код в язык, который можно распознать и запомнить, то есть в язык ассемблера, а инструкции по ассемблеру можно преобразовать в машинные инструкции с помощью компилятора ассемблера.

Различные ЦП имеют разные наборы инструкций, и программирование на языке ассемблера должно быть совместимо с различными архитектурами ЦП, такими как ARM, MIPS и т. д., а стоимость обучения относительно высока. Абстракции языка ассемблера далеко не достаточно, поэтому по мере необходимости появляются языки высокого уровня.Языки высокого уровня скрывают детали компьютерной архитектуры и совместимы со многими различными архитектурами ЦП.

Процессор также не знает языка высокого уровня.Как правило, есть два способа выполнить код языка высокого уровня, а именно:

  • объяснить исполнение
  • Скомпилировать и выполнить

Интерпретировать выполнение, скомпилировать выполнение

Интерпретация и выполнение сначала скомпилируют входной исходный код в промежуточный код через синтаксический анализатор, а затем напрямую используют интерпретатор для интерпретации и выполнения промежуточного кода и вывода результата.

Компиляция и выполнение также преобразуют исходный код в промежуточный код, а затем компилятор компилирует промежуточный код в машинный код.Обычно скомпилированный машинный код хранится в виде двоичного файла, а двоичный файл выполняется для вывода результат. Скомпилированный машинный код также может быть сохранен в памяти, а двоичный код в памяти может выполняться напрямую.

JIT (Just In Time)

解释执行启动速度快,执行速度慢,而编译执行启动速度慢,执行速度快。

V8 использует выполнение как интерпретации, так и выполнения компиляции после взвешивания всех за и против.Этот метод смешанного использования называетсяJIT (即时编译).

Когда V8 выполняет исходный код JavaScript, синтаксический анализатор сначала преобразует исходный код в абстрактное синтаксическое дерево AST, а интерпретатор (Ignition) преобразует AST в байт-код и выполняет его во время интерпретации.

Интерпретатор также будет записывать количество исполнений фрагмента кода.Если количество выполнений превышает определенный порог, код будет помечен как горячий код, а информация о выполнении будет возвращена оптимизирующему компилятору TurboFan.Информация об обратной связи, оптимизирует и компилирует байт-код и, наконец, генерирует оптимизированный машинный код.

Леди из столовой: То есть, когда этот код выполняется снова, интерпретатор может напрямую запустить оптимизированный машинный код без необходимости его повторной интерпретации, что значительно улучшит производительность, верно?

Владелец столовой: Верно!

Очень интересны названия интерпретатора и компилятора V8: интерпретатор Ignition означает igniter, а компилятор TurboFan — turbo.

Поняв общий механизм работы V8, давайте углубимся и рассмотрим принцип работы основных модулей V8.

Как работают основные модули V8

Основные модули V8 включают:

  • Parser: анализатор отвечает за преобразование кода JavaScript в абстрактное синтаксическое дерево AST.
  • Ignition: интерпретатор отвечает за преобразование AST в байт-код и сбор информации об оптимизированной компиляции, необходимой для TurboFan.
  • TurboFan: преобразование байт-кода в оптимизированный машинный код с использованием информации, собранной интерпретатором.

V8 необходимо дождаться завершения компиляции, прежде чем запускать код, поэтому производительность во время синтаксического анализа и компиляции очень важна.

Парсер Парсер

Процесс разбора парсера делится на два этапа:

  • Лексический анализ (Scanner Lexical Analyzer)
  • Парсер (предварительный парсер, парсер парсера)

лексический анализ

Scannerответственный за получениеUnicode Streamпоток символов, который анализируется какtokens, предоставляемый анализаторуParser.

Например следующий код:

let myName = '童欧巴'

будет проанализирован какlet,myName,=,'童欧巴', которые являются ключевыми словами, идентификаторами, операторами присваивания и строками соответственно.

Разбор

Затем синтаксический анализ преобразует токены, сгенерированные на предыдущем шаге, в AST в соответствии с правилами синтаксиса.Если в исходном коде есть синтаксическая ошибка, он завершится на этом этапе и выдаст синтаксическую ошибку.

Структуру AST можно посмотреть на этом сайте:astexplorer.net/

Вы также можете пройти по этой ссылкеresources.joint JS.com/demos/Java — это…Сгенерируйте изображение напрямую, как показано ниже:

Получите AST, V8 сгенерирует код для执行上下文.

Ленивый разбор

Основные движки JavaScript приняли惰性解析(Lazy Parsing), потому что если исходный код полностью анализируется перед выполнением, это не только приведет к тому, что время выполнения будет слишком большим, но и потребует больше памяти и дискового пространства.

Ленивый синтаксический анализ означает, что если встретится функция, которая не выполняется немедленно, она будет только выполнена.预解析(Pre-Parser), который не будет полностью разрешен до тех пор, пока не будет вызвана функция.

Во время предварительного синтаксического анализа он только проверяет правильность синтаксиса функции, анализирует объявление функции и определяет область действия функции, а не генерирует AST.Эта работа выполняетсяPre-ParserПрепаратор готов.

Зажигание интерпретатора

После получения AST и контекста выполнения интерпретатор преобразует AST в байт-код и выполняет его.

Хозяйка столовой: Зачем вводить байт-код?

Внедрение байт-кода является инженерным компромиссом.Как видно из рисунка, сгенерированный машинный код уже занимает большой объем памяти для файла размером всего в несколько КБ.

По сравнению с машинным кодом,字节码不仅占用内存少,而且生成字节码的时间很快,提升了启动速度. Хотя скорость выполнения байт-кода не такая высокая, как у машинного кода, стоит немного пожертвовать эффективностью выполнения.

Более того, байт-код не имеет ничего общего с конкретным типом машинного кода, он может выполняться после преобразования байт-кода в машинный код интерпретатором, что также делает V8 более удобным для портирования на разные архитектуры ЦП.

Вы можете просмотреть байт-код, сгенерированный кодом JavaScript, с помощью следующей команды.

node --print-bytecode index.js

Его также можно посмотреть по следующим ссылкам:

Давайте посмотрим на кусок кода:

// index.js
function add(a, b) {
    return a + b
}

add(2, 4)

Приведенный выше код сгенерирует следующий байт-код после выполнения команды:

[generated bytecode for function: add (0x1d3fb97c7da1 <SharedFunctionInfo add>)]
Parameter count 3
Register count 0
Frame size 0
   25 S> 0x1d3fb97c8686 @    0 : 25 02             Ldar a1
   34 E> 0x1d3fb97c8688 @    2 : 34 03 00          Add a0, [0]
   37 S> 0x1d3fb97c868b @    5 : aa                Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 8)
0x1d3fb97c8691 <ByteArray[8]>

Среди них Parameter count 3 представляет три параметра, включая входящие a, b и this. Детали байт-кода следующие:

Ldar a1 // 表示将寄存器中的值加载到累加器中
Add a0, [0] // 从 a0 寄存器加载值并且将其与累加器中的值相加,然后将结果再次放入累加器
Return // 结束当前函数的执行,并把控制权传给调用方,将累加器中的值作为返回值

Каждая строка байт-кода соответствует определенной функции, и каждая строка байт-кода собирается, как блоки Lego, для формирования полной программы.

Обычно есть два типа переводчиков,基于栈а также基于寄存器的解释器, ранний интерпретатор V8 также был основан на стеке, а текущий интерпретатор V8 использует структуру на основе регистров, поддерживает операции с инструкциями регистров и использует регистры для сохранения параметров и промежуточных результатов вычислений.

Когда интерпретатор Ignition выполняет байт-код, он в основном использует通用寄存器а также累加寄存器, соответствующие параметры функции и локальные переменные будут сохранены в общих регистрах, а регистр накопления сохранит промежуточные результаты.

В процессе выполнения инструкций центральному процессору необходимо читать и записывать данные, если они напрямую читаются и записываются в память, это серьезно повлияет на производительность выполнения программы. Поэтому ЦП вводит регистры и сохраняет в регистрах некоторые промежуточные данные для повышения скорости выполнения ЦП.

Компилятор ТурбоФан

Что касается компиляции, команда V8 также провела множество оптимизаций.Давайте взглянем на встроенный анализ и анализ escape-последовательности.

встраивание

Что касается встраивания, давайте сначала посмотрим на кусок кода:

function add(a, b) {
  return a + b
}
function foo() {
  return add(2, 4)
}

Как показано в приведенном выше коде, мы вызываем функцию add в функции foo.Функция add получает два параметра a и b и возвращает их сумму. Если он не оптимизирован компилятором, машинный код, соответствующий этим двум функциям, будет генерироваться отдельно.

Для повышения производительности оптимизирующий компилятор TurboFan перед компиляцией встроит две вышеуказанные функции. Встроенная функция выглядит так:

function fooAddInlined() {
  var a = 2
  var b = 4
  var addReturnValue = a + b
  return addReturnValue
}

// 因为 fooAddInlined 中 a 和 b 的值都是确定的,所以可以进一步优化
function fooAddInlined() {
  return 6
}

После встроенной оптимизации машинный код, созданный при компиляции, будет значительно упрощен, а эффективность выполнения также значительно повысится.

Анализ побега

Анализ побега понять несложно, это значит, что分析对象的生命周期是否仅限于当前函数, давайте посмотрим на кусок кода:

function add(a, b){
  const obj = { x: a, y: b }
  return obj.x + obj.y
}

如果对象只在函数内部定义,并且对象只作用于函数内部的话,就会被认为是“未逃逸”的, мы можем оптимизировать приведенный выше код:

function add(a, b){
  const obj_x = a
  const obj_y = b
  return obj_x + obj_y
}

После оптимизации нет необходимости в определениях объектов, и мы можем загружать переменные непосредственно в регистры, больше не нужно обращаться к свойствам объекта из памяти. Не только снижает потребление памяти, но и повышает эффективность выполнения.

Что касается анализа побегов, Chrome также взорвал дыры в безопасности, что замедлило работу всего Интернета.Если вам интересно, пожалуйста, ткнитеОшибка от команды V8, которая замедлила весь интернет

В дополнение к различным схемам и модулям оптимизации, упомянутым выше, V8 также имеет множество методов оптимизации и основных модулей, таких как: использование скрытых классов для быстрого получения атрибутов объекта, использование встроенных кэшей для повышения эффективности выполнения функций,Orinocoуборщик мусора,LiftoffКомпилятор WebAssembly и т. д., в этой статье не будет слишком многого, если вам интересно, вы можете изучить их самостоятельно.

резюме

Эта статья представляет и обобщает V8, историю развития архитектуры V8, рабочий механизм V8 и принцип работы основных модулей V8 с точки зрения макросов.Мы можем обнаружить, что будь то Chrome или Node.js, они просто bridge, отвечающий за транспортировку кода JavaScript, написанного нашими фронтенд-инженерами, в конечный пункт назначения, преобразование его в машинный код соответствующей машины и его выполнение. Во время этого путешествия команда V8 приложила немало усилий, чтобы оказать им максимальное уважение.

Хотя набор команд ЦП ограничен, программы, написанные нашими инженерами-программистами, не фиксированы, именно эти программы в конечном итоге выполняются ЦП, и можно изменить мир.

Вы лучшие, меняющие мир программы!

Хозяйка столовой: Тонгтонг, ты самый толстый! ^_^

Стоя на плечах гигантов

  • Как V8 выполняет код JavaScript? - Лао Цзян
  • Графический Google V8 — Bing
  • Курс архитектуры Сюй Шивэя
  • v8.dev/blog

❤️Любовное тройное комбо

1. Если вы считаете, что еда и напитки в столовой все еще аппетитны, просто поставьте лайк и поддержите это, вашеотличныймоя самая большая мотивация.

2. Подпишитесь на официальный аккаунт前端食堂,Ешьте каждый прием пищи!

3. Нравится, комментирует, пересылает === призывает больше!

透明的footer.png