Статьи

JavaScript и V8 TurboFan

В последнее время инженеры Google приземлился новый оптимизирующий компилятор JavaScript для V8, под кодовым названием турбовентиляторном . Как следует из названия, это должно еще больше повысить скорость выполнения JavaScript, вероятно, будет лучше, чем его предшественник, Crankshaft . Хотя TurboFan все еще находится на ранней стадии, это не значит, что мы не можем на это взглянуть.

Играть с этим TurboFan ароматом V8 не сложно. Прежде всего , необходимо построить на Кровотечение-краевую ветвь , где это 70000 строк кода в настоящее время проживает. После того, как новая оболочка V8 будет свежеиспечена, мы можем немного повеселиться и осмотреть работу TurboFan.

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

Давайте иметь простую тестовую программу:

function answer() {
  return 42;
}
print(answer());

Если это так test.js, то мы можем поиграть с TurboFan, запустив:

/path/to/v8/shell --always-opt \
  --trace-turbo \
  --turbo-types \
  --turbo-filter=answer \
  test.js

Мы используем эту --always-optопцию, чтобы код был оптимизирован немедленно (в противном случае будут оптимизированы только, например, горячие циклы). Для того, чтобы осмотреть TurboFan, --trace-turboи --turbo-typesварианты необходимы. И последнее, но не менее важное: нас интересует только наша собственная функция answer (), которая должна быть рассмотрена, и, следовательно, использование --turbo-filteroption. Если мы передадим * вместо этого, V8 будет сбрасывать слишком много информации, в основном о других внутренних элементах, немного не относящихся к этому обсуждению.

Мы можем видеть, что TurboFan делает свое волшебство, посмотрев первые несколько строк вывода:

---------------------------------------------------
Begin compiling method answer using Turbofan

Для дальнейшего изучения лучше перенаправить вывод в файл журнала. Файл журнала будет содержать данные для 4 различных графов: начальный нетипизированный граф, контекстный специализированный граф, пониженный типизированный граф и пониженный общий граф. Это результат компиляции Turbofan. Каждый график легко визуализировать, поскольку он напечатан в формате фактической точки .

Во-первых, нам нужно отделить каждый отдельный график:

csplit -s -f graph log "/--/" {4}

Предполагая, что GraphViz установлен, мы можем увидеть первый график, начальный нетипизированный, запустив:

tail -n +2 graph01 | dot -T png > untyped.png

что показано на следующем снимке экрана:

нетипизированным

Это ориентированный граф промежуточного представления (IR). Вы можете узнать некоторые узлы в графе напоминающих исходный код JavaScript , такие как NumberConstant[42]и Returnузлы. У каждого узла есть оператор и связанный с ним ИК-код операции. Это очень похоже на IR- подход Sea of ​​Nodes от Cliff Click (см. « Объединение анализа, объединение оптимизаций и Простое промежуточное представление на основе графа» ), используемый компилятором Java HotSpot .

Приведенный выше график построен как первый этап конвейера компилятора путем обхода абстрактного синтаксического дерева. Здесь нет ничего удивительного. Ребра Start (узел № 0) и End (узел № 10) говорят сами за себя. Для каждой функции информация о ее параметре (узел № 4) всегда обязательна. Проверка стека является неотъемлемой частью внутренних компонентов V8, поэтому требуется JSCallRuntime (узел № 5).

Внутри тела функции каждое утверждение будет посещаться строителем AST. В нашем примере есть только один, оператор возврата. Для этого сборщик также должен посетить аргумент, который оказывается числовым литералом. Конечным результатом является узел, который представляет код возврата Return (узел # 7), который также ссылается на константу (узел # 6).

Серый узел ( Return , узел № 9) указывает, что он «мертв», то есть не используется. На самом деле это специальный оператор возврата (возвращающий неопределенный), который играет роль, только если функция не имеет явного возврата. Так как здесь это не так, узел нигде не используется и не передается, следовательно, он не работает.

После того, как этот начальный график получен, следующим этапом являются специализация контекста , анализ типов и снижение IR . Эти три темы выходят за рамки (каламбур) того, что я хочу осветить прямо сейчас, поэтому нам придется обсудить их в другой раз. Тем не менее, обратите внимание, что наш test.jsочень прост, здесь нет присваивания или каких-либо сложных операций, и, следовательно, последующие этапы компилятора не улучшают IR-граф каким-либо значимым образом. Фактически, если вы строите график graph02(используя команду, аналогичную dotпредыдущей), вы увидите, что полученное изображение точно такое же, как на предыдущем скриншоте.

В конечном счете, TurboFan необходимо сгенерировать машинный код. Как и ожидалось, у него есть собственный генератор кода (в настоящее время для x86 и ARM, как 32-разрядных, так и 64-разрядных), он не использует существующие генераторы кода водорода и лития от Crankshaft. Машинный код выводится из последовательности команд. Если вы посмотрите на файл журнала, соответствующая часть последовательности:

      14: ArchRet v6(=rax)

Если вы узнаете, что такое v6 , это относится к константе 42. На x86-64 эта инструкция может быть превращена в MOVQ RAX, 0x2A00000000последующую RET 8. Просто, не правда ли?

TurboFan еще очень молод, и я уверен, что еще есть куда расти. В последнем эпизоде ​​оптимизации движка JavaScript WebKit получает повышение скорости благодаря новому FTL (JIT-компилятору четвертого уровня на основе LLVM ), в то время как Firefox продолжает совершенствовать свой JIT-компилятор всего метода IonMonkey . Станет ли TurboFan ответом V8 на них?

Добро пожаловать в мир, ТурбоФан!