JavaScript: процесс компиляции V8

JavaScript

   хорошо известноECMAScriptЯзыковые типы делятся на:Undefined,Null,Boolean,String,Symbol,Number,а такжеObject. Мы часто говорим, что первые шесть типов данных являются базовыми типами.ObjectДля ссылочного типа или данных сложного типа. Вот мы и подумали, почемуUndefined,Null,Boolean,String,Symbol,Numberявляется базовым типом иObjectЯвляется ссылочным типом? Не смотри на себяECMAScriptЯ серьезно не думал об этом вопросе до спецификации, все об этом говорили, и это было написано во многих книгах, поэтому я решил, что это так.

существуетECMAScriptВ спецификации не указано, чтоUndefined,Null,Boolean,String,Symbol,Numberявляется базовым типом,Objectявляется ссылочным типом. Посмотрим, что говорит спецификация:

4.3.2primitive value

member of one of the types Undefined, Null, Boolean, Number, Symbol, or String as defined in clause 6 NOTEA primitive value is a datum that is represented directly at the lowest level of the language implementation.

   определено в главе 6 спецификацииUndefined, Null, Boolean, Number, Symbol, а такжеStringявляется исходным значением, исходное значение непосредственно представляет данные самого низкого уровня, реализованные языком.

4.3.3object

member of the type Object An object is a collection of properties and has a single prototype object. The prototype may be the null value.

Объект    представляет собой набор свойств и имеет объект-прототип. Прототип может быть нулевым.

Спецификация    не различает базовые типы данных и справочные типы данных, так почему же вJavaScript中会有这两个概念呢? Это то же самое, чтоJavaScriptУправление памятью движка связано, поговорим об этом нижеJavaScriptПринцип компиляции движка.

тип языка

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

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

  C,C++Это компилируемый язык.Редактирование исходного кода, компиляция и запуск разделены, и это цепочка ответственности. После того, как мы напишем исходный код, после компиляции и линковки, мы, наконец, получим локальный бинарный код, а затем передаем его операционной системе.

  ruby,JavaScriptИ т. д. является типичным интерпретируемым языком, а также языком сценариев.Сценарий не требует компиляции, а напрямую интерпретируется интерпретатором во время запущенного процесса и вызывает ресурсы операционной системы для запуска. дляJavaScriptДругими словами, интерпретаторJavaScriptдвигатель, раннийJavaScriptДвижок тоже собран таким образомJavaScriptкод, но раноV8Движок — это не такой процесс компиляции, о котором речь пойдет ниже.

  javaтакже классифицируется как интерпретируемый язык, что вызывает споры, и некоторые говорят,javaЭто скомпилированный язык, не волнуйтесь слишком многоjavaЧто это за язык, давайте разбиратьсяjavaКак он составляется?javaПроцесс обработки кода чем-то похож на описанный выше процесс компиляции интерпретируемого языка, но процесс обработки более сложен и делится на два этапа: компиляцию и интерпретацию:

  • Компиляция: сначала что-то вродеC++языкоподобный компилятор, компилируетjavaкод, но иC++Компилятор генерирует машинный код по-разному, компилятор сначала преобразует его в абстрактное синтаксическое дерево, а затем преобразует в байт-код, который является своего рода промежуточным кодом.
  • Объяснение: Затем следует запустить байт-код, в основном полагаясь наjavaвиртуальная машина(JVM) загружает байт-коды, выполняет их с помощью интерпретатора и преобразует байт-коды в машинный код. Байт-код может быть независимым от операционной системы и платформы с помощьюjavaВиртуальная машина также реализует кроссплатформенную функцию, поэтому было сказаноjavaЭто кроссплатформенность, вот и все.

  JavaПроцесс добавленияJITКонцепция чего-либо,JITБайт-код можно преобразовать в собственный код, а затем выполнить, что повысит эффективность выполнения.JITВ основном для оптимизации производительности. многоJavaScriptДвигатель тоже используется.

  JavaScriptОн классифицируется как слабо типизированный интерпретируемый язык, поскольку это слабо типизированный язык, его также можно назвать динамически типизированным языком. Сравнение,C++илиjava等是静态类型语言,他们在编译的时候就能够知道每个变量的类型。 а такжеJavaScriptНет возможности узнать тип данных его переменных при редактировании, и его можно определить только во время выполнения, что приводит кJavaScriptСтолкнувшись с огромным давлением на производительность. Вычисление и определение типов данных во время выполнения может привести к серьезному снижению производительности, что также приводит кJavaScriptкоэффициент операционной эффективностиC++,javaЭто намного ниже.Чтобы повысить эффективность работы, многие производители прилагают усилия.Что они делают лучше в настоящее время, так этоChromeизV8двигатель,V8двигательJavaScriptдвигатель.

  JavaScriptреализация зависит отJavaScriptдвигатель,JavaScriptдвигатель аналогичныйJVM, является специализированнымJavaScriptскриптовая виртуальная машина,JavaScripВ настоящее время существует множество двигателей t:SpiderMonkey,JavaScriptCore,Chakra,V8подожди, современныйJavaScriptПредставлены двигателиJavaвиртуальная машина иC++Многие методы компилятора, а ранниеJavaScriptДвигатели работают по-разному:

  В первые дни интерпретатор мог их интерпретировать, то есть преобразовывать исходный код в абстрактное синтаксическое дерево, а затем интерпретировать и выполнять на абстрактном синтаксическом дереве.JavaScriptCoreВот как это работает, улучшено позже. С волейJavaвиртуальная машинаJITВнедрена технология, и текущая практика состоит в том, чтобы преобразовать абстрактное синтаксическое дерево в промежуточное представление (то есть байт-код), а затем передатьJITТехнология преобразована в местный код. Существуют также подходы, которые генерируют собственный код непосредственно из абстрактного синтаксического дерева.JITтехнологии, такие как ранниеV8.

JavaScript-движок

В настоящее времяJavaScriptдвигательная параJavaScriptпроцесс иjavaпохожий, ведь представилjavaТехнология составления, но отличия все же есть,javaОбработка делится на два этапа: компиляция и интерпретация, исходный код анализируется компилятором для генерации байт-кода, а затем байт-код преобразуется в машинный код с помощью JVM и запускается.JavaScriptКомпиляция движка сочетает в себе две фазы компиляции и интерпретации, как вJavaScriptВыполнено в движке, в настоящее времяJavaScriptДвигатель в основном состоит из следующих частей:

  • Компилятор: компилирует исходный код в абстрактное синтаксическое дерево в некоторых движках (таких какJavaScriptCore, сейчасV8) также включает преобразование абстрактного синтаксического дерева в байт-код
  • Интерпретатор: в некоторых двигателях (таких какJavaScriptCore), интерпретатор в основном принимает байткоды, интерпретирует и выполняет байткоды, но раноV8Нет интерпретатора в движке
  • JITИнструмент: преобразование байт-кода или абстрактного синтаксического дерева в собственный код, оптимизированный для использования.
  • Сборщик мусора и инструменты анализа (profiler): отвечает за сбор мусора и сбор информации в движке, чтобы помочь улучшить производительность и эффективность движка.

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

двигатель V8

Зачем говорить в этой статьеV8Процесс компиляции движка,V8это проект с открытым исходным кодом, который превосходит другие с точки зрения производительностиJavaScriptдвигатель,Chromeв использованииV8двигатель, доля рынка браузеров велика, иNodeтакже на основеV8развитый,V8Также поддерживаются многочисленные операционные системы и аппаратные архитектуры,V8представитель,V8Производительность неуклонно улучшалась с момента его выпуска в 2008 году:

   НижеV8Весь процесс исполнения движка в основном включает в себя следующие модули:

  • parse: ответственный заJavaScriptисходный код в абстрактное синтаксическое дерево (AST)
  • Ignition:interpreter, интерпретатор, преобразует AST в байт-код (Bytecode), разбирает исполняемый байт-код, а также собираетTurboFanИнформация, необходимая для оптимизированной компиляции
  • TurboFan:compiler,JITКомпилятор, утилизацияIgnitioТип собираемой информации будетBytecodeПреобразование в оптимизированный машинный код
  • Orinoco:garbage collector, модуль сборки мусора, отвечающий за освобождение памяти, которая больше не нужна программе.

Генерация абстрактного синтаксического дерева

  V8Движок сначала пропускается через компилятор (parse) анализирует исходный код в абстрактное синтаксическое дерево (AST), сгенерироватьASTОн разделен на два этапа: лексический анализ и синтаксический анализ:

  • Лексический анализ: разбиение исходного кода на мельчайшие неделимые лексические единицы (token). пример программыvar a = 2;. Программа обычно разбивается на следующие лексические единицы:var,a,=,2,;. Пять лексических единиц. Рассматриваются ли пробелы как лексические единицы, зависит от того, имеют ли пробелы значение в языке, вJavaScript, пробелы не рассматриваются как лексические единицы.
  • Синтаксический анализ: этот процесс заключается в преобразовании потока лексических единиц (массивов) в дерево элементов, представляющих синтаксическую структуру программы.Это дерево называется абстрактным синтаксическим деревом (AST).var a = 2;Может существовать абстрактное синтаксическое дерево, называемоеVariableDeclaration, за которым следует узел с именемIdentifier(его значениеa), и подузел с именемAssignmentExpressionдочерний узел .AssignmentExpressionузел имеетNumericLiteral(его значение2) дочерние узлы.

   Выше естьvar a = 2Процесс создания абстрактного синтаксического дерева, который можно выполнить с помощьюонлайн-инструментыПроверить.

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

   Говоря об этом, нужно упомянуть одно слово,Babel, учащиеся переднего плана должны быть знакомы с этим, некоторыеES6Синтаксис в настоящее время не поддерживается браузерами, вам необходимо преобразоватьES6Грамматика вES5грамматике этот процесс зависит отBabelреализовать,BabelЯвляетсяJavaScriptКомпилятор разделен на три этапа: синтаксический анализ, трансляция и генерация. БудуES6Исходный код разбирается наAST, а потомES6грамматическийASTПревратиться вES5изAST, и, наконец, используйте его для созданияES5исходный код, этоBabelОсновной принцип реализации.

  ESLintПринцип примерно тот же, и процесс обнаружения также заключается в преобразовании исходного кода вAST, с использованиемASTпроверить спецификацию кода.

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

генерировать байт-код

   Выше уже было описаноJavaScriptисходный код вAST, теперь необходимоASTПреобразовать в байт-код. упомянутый ранееV8До двигателя v5.6ASTПреобразовано в байт-код, напрямуюASTПреобразование в машинный код. Есть два компилятора:

  • full-codegen: Простой и быстрый компилятор, который может генерировать простой, но относительно медленный машинный код.
  • Grankshaft: сложнееJITКомпилятор, способный генерировать высокооптимизированный машинный код.

   Первое исполнениеJavaScriptкод, напрямую черезfull-codegenкомпилятор будетASTПреобразование в машинный код пропускает процесс преобразования в байт-код, что позволяет очень быстро выполнять машинный код.

Основной целью этого является сокращение времени преобразования промежуточного процесса преобразования в байт-код и повышение скорости выполнения кода.Все это также делается при загрузке страницы, что может улучшить возможности оптимизации, и выполнение производительность будет сравниваться.Большое улучшение, но и недостатки очевидны:

  • Это приведет к проблеме чрезмерного использования памяти, потому что абстрактное синтаксическое дерево полностью сгенерировано в машинный код, а машинный код занимает гораздо больше памяти, чем байт-код.
  • немногоJavaScriptБолее уместно использовать интерпретатор в сценарии использования, который разбирается в байт-код.Некоторые коды не требуют генерации машинного кода, тем самым максимально уменьшая проблему того, что машинный код занимает слишком много памяти.
  • Поскольку нет промежуточного представления, это уменьшит возможности для оптимизации, поскольку на один промежуточный уровень представления меньше.

  Перед выпуском v5.6 была проделана большая работа по оптимизации производительности.Чтобы уменьшить генерацию машинного кода, мы перепробовали много отложенного парсинга и компиляции (Lazy parsing and compiling)работай. Например, для фрагмента кода, если функция в этом коде не вызывается во время инициализации, вызывающий процесс будет «отложен» до тех пор, пока код, соответствующий функции, не будет скомпилирован при первом вызове функции.

пройти черезfull-codegenПосле того как компилятор сгенерирует машинный код, анализатор данных (Profiler) собирает некоторую информацию, которая будет предоставленаGrankshaftпереводчик.

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

  Хотя мы и занимались оптимизацией, слой оптимизации по-прежнему находится на машинном коде, а сам машинный код занимает много памяти.GrankshaftХотя компилятор также делаетJITоптимизирован, ноGrankshaftКаждый синтаксический анализ выполняется повторно из исходного кода.

   Официально сзадиV8Версия v5.6 по-прежнему будетASTДобавлен процесс конвертации в байт-код, возвращение к объятиям байт-кода. представилIgnitionпереводчик, черезIgnitionПереводчик будетASTПреобразовать в байт-код.

  V8вновь ввестиIgnitionпереводчик, будетASTПосле преобразования в байт-код значительно сокращается объем памяти, а для дальнейшей оптимизации можно использовать JIT-компилятор.

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

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

  Ignitionразработаны, чтобыV8Создайте интерпретатор для выполнения низкоуровневого байт-кода, чтобы код, который запускается только один раз или не активировался, можно было хранить более компактно в форме байт-кода. Поскольку байт-код меньше, время компиляции также будет значительно сокращено. В то же время байт-код может быть передан непосредственно вTurboFanгенератор графов, таким образом, вTurboFanПри оптимизации функции внутри можно избежать репарсаJavaScriptисходный код. то естьTurboFanКомпиляция основана на байт-коде, а не на исходном коде.

  IgnitionВведение интерпретатора полностью заменяетfull-codegen,Crankshaftтакже полностью заброшен,CrankshaftНевозможно разобрать оптимизированного байтекода, а затем TurboFanКомпилятор заменен.

Сгенерировать машинный код

  IgnitionПомимо быстрого создания неоптимизированного байт-кода, интерпретатор также отвечает за выполнение байт-кода. Когда байт-код выполняется в первый раз, также собираются данные профилирования, и интерпретатор интерпретирует их один за другим. если найденоhotкода (т. е. часть кода выполняется несколько раз), результирующий байт-код и данные анализа передаются вTurboFanКомпилятор, который генерирует оптимизированный машинный код из данных профилирования. При повторном выполнении этого кода необходимо выполнить только скомпилированный машинный код.

  TurboFanКомпилятор - это компилятор, оптимизирующий JIT, начиная сIgnitionЗапустите байт-код в интерпретаторе. В какой-то момент двигатель определяет, что код горячий, и запускается.TurboFanпередняя часть, этоTurboFanЧасть часть, он обрабатывает базовое машинное представление интегрированных данных анализа и кода сборки. а затем отправить его вTurboFan, для дальнейшего улучшения кода.V8Движок многопоточный,TurboFanКомпилятор генерирует байт-код, а не в том же потоке.

существуетTurboFanВремя работы,V8будет продолжатьIgnitionБайт-код выполняется в интерпретаторе. в какой-то момент,TurboFanЭто сделано, и у вас есть исполняемый машинный код, с которым можно продолжить выполнение.

Зависит отIgnitionДанные анализа, собранные интерпретатором,TurboFanиспользования, в основном с помощью метода, называемого спекулятивной оптимизацией (Speculative Optimization) метод генерации высокооптимизированного машинного кода.TurboFanсмотрит на типы значений, которые мы видели в прошлом, и предполагает, что мы увидим значения того же типа в будущем, что может сделатьTurboFanСпасает от многих ситуаций, с которыми не нужно иметь дело. Если предположение не выполняется, то возвращается к анализу байт-кода, что является антиоптимизацией (deoptimization).

здесьV8правильноJavaScriptПроцесс компиляции примерно завершен, и теперь оно должно быть понятно, когда я смотрю на картинку ниже. Каждый шаг не объясняется подробно.V8В двигателе задействовано слишком много всего, и сложно поставить его за короткий промежуток времени.V8Весь контент разобран.Это не та статья, которая может объяснить это внятно.Рассмотрите следующие статьи как можно больше.V8с точки зрения письма.

   В настоящее время современныйJavaScriptПроцесс компиляции движка во многом похож, и основной принцип один и тот же. Основное отличие состоит в том, что разные движки имеют разные уровни оптимизации, а это означает, что количество интерпретаторов и компиляторов для разных движков различно, в основном из-за использования интерпретаторы быстры.Существует фундаментальный компромисс между генерацией кода и использованием оптимизирующего компилятора для генерации кода средней школы. Добавление дополнительных уровней оптимизации позволяет нам принимать более детальные решения, но за счет дополнительной сложности и накладных расходов. Также существует компромисс между уровнем оптимизации и памятью, занимаемой сгенерированным кодом. На самом деле, в конечном счете, это улучшение работы двигателя. Заинтересованные студенты могут самостоятельно изучить процесс компиляции других движков~

   На данный момент я не ответил на вопрос, поставленный в начале.Почему типы данных делятся на базовые типы и ссылочные типы?Это связано с управлением памятью движка.V8Управление памятью, в следующей статье поговорим с вами, там много контента.

Эпилог

  Если в статье есть какие-то неточности, прошу меня поправить, и я надеюсь, что студенты, которым посчастливится прочитать статью, тоже что-то приобретут и подрастут вместе!

--------------------------Эта статья была впервые опубликована в личном паблике -------------- ----- ---

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