об авторе
Зийи(Заинтересованные студенты могут щелкнуть, чтобы просмотреть домашнюю страницу Ziyi Nuggets, внутри есть много отличных статей), член проприетарной команды интерфейса DingTalk, ответственный за разработку, применение и доставку проприетарного подключаемого модуля клиентского модуля DingTalk для ПК. в разработке.
Введение
Эта статья представляет собой введение в принципы компиляции V8, целью которого является дать каждому перцептивное понимание процесса синтаксического анализа JavaScript в V8. Основной процесс написания этой статьи выглядит следующим образом:
- Интерпретатор и компилятор:Введение в основы принципов компьютерной компиляции
- Принцип компиляции V8:Основываясь на знании принципов компьютерной компиляции, поймите процесс синтаксического анализа V8 для JavaScript.
- Производительность V8 во время выполнения:В сочетании с принципом компиляции V8 отработайте конкретную производительность V8 в процессе синтаксического анализа.
Напоминание: могут быть элементы знаний, которые автор не представил или не дал ссылки в статье. Заинтересованные студенты могут проверить это самостоятельно или подписаться на нашу учетную запись команды. Более подробная серия статей «Введение в JavaScript» будет опубликована в будущем.
Интерпретаторы и компиляторы
Возможно, вы задавались вопросом: является ли JavaScript интерпретируемым языком? Чтобы разобраться в этой проблеме, сначала нужно понять, что такое интерпретатор и компилятор и каковы их характеристики.
устный переводчик
Роль интерпретатора состоит в том, чтобы принимать исходную программу, написанную на определенном языке, в качестве входных данных и результат выполнения исходной программы в качестве вывода.Например, Perl, Scheme, APL и т. д. все используют интерпретатор для преобразования и выполнения:
переводчик
Проект компилятора представляет собой очень большую и сложную программную систему, и при фактическом проектировании необходимо решить две относительно важные проблемы:
- Как анализировать исходные программы, разработанные на разных языках программирования высокого уровня
- Как сопоставить функциональную эквивалентность исходных программ с целевыми машинами с разными системами команд
Промежуточное представление (IR)
Промежуточное представление (IR) — это представление структуры программы, которое ближе к языку ассемблера или набору инструкций, чем к абстрактному синтаксическому дереву (AST), а также сохраняет некоторую высокоуровневую информацию в исходной программе.Конкретные роли включают:
- Легко отлаживать ошибки компилятора и легко определить, находится ли проблема во внешнем интерфейсе до IR или в бэкэнде после IR.
- Это может сделать обязанности компилятора более разделенными, и при компиляции исходной программы больше внимания уделяется тому, как преобразовать ее в IR, а не адаптации к различным наборам инструкций.
- IR ближе к набору инструкций, что экономит место в памяти по сравнению с исходным кодом
оптимизирующий компилятор
Сам IR может выполнять несколько итераций для оптимизации исходной программы.В процессе каждой итерации могут быть записаны детали оптимизации, что удобно для последующих итераций, чтобы найти и использовать эту информацию об оптимизации, и, наконец, может эффективно выводить лучшую цель. программа:
Оптимизатор может обрабатывать IR за один или несколько проходов, что приводит к более быстрому выполнению или меньшим целевым программам (например, поиск инвариантных вычислений в циклах и их оптимизация для уменьшения количества операций), а также может использоваться для целевых программ, которые генерируют меньшее количество операций. исключения или потребляют меньше энергии. Кроме того, интерфейс и серверная часть также могут быть разделены на несколько этапов обработки, как показано на следующем рисунке:
Сравнение характеристик двух
Особенности интерпретаторов и компиляторов сравниваются следующим образом:
Типы | устный переводчик | переводчик |
---|---|---|
Рабочий механизм | Компиляция и выполнение выполняются одновременно | Разделение компиляции и выполнения |
скорость запуска | относительно быстро | относительно медленный |
Ходовые характеристики | относительно низко | Относительно высокая |
Обнаружение ошибок | обнаружение во время выполнения | обнаружение во время компиляции |
Следует отметить, что ранний веб-интерфейс требует быстрого запуска страницы, поэтому он принимает способ интерпретации и выполнения, но производительность страницы во время рабочего процесса относительно низкая. Чтобы решить эту проблему, код JavaScript необходимо оптимизировать во время выполнения, поэтому технология JIT внедряется в механизм синтаксического анализа JavaScript.
Технология JIT-компиляции
Компилятор JIT (Just In Time) — это технология динамической компиляции. По сравнению с традиционным компилятором самое большое отличие состоит в том, что время компиляции и время выполнения не разделены. Это технология, которая динамически компилирует код во время выполнения процесса.
Типы | устный переводчик | переводчик | JIT-компилятор |
---|---|---|---|
Рабочий механизм | Компиляция и выполнение выполняются одновременно | Разделение компиляции и выполнения | Компиляция и выполнение выполняются одновременно |
скорость запуска | быстро | середина | медленный |
Ходовые характеристики | относительно низко | Относительно высокая | В зависимости от оптимизации, как правило, лучше, чем производительность интерпретатора |
Обнаружение ошибок | обнаружение во время выполнения | обнаружение во время компиляции | обнаружение во время выполнения |
Технология гибридной динамической компиляции
Чтобы решить проблему низкой производительности JavaScript во время выполнения, мы можем внедрить технологию JIT и использовать гибридную динамическую компиляцию для повышения производительности JavaScript.Конкретные идеи заключаются в следующем:
После использования вышеуказанной среды компиляции язык JavaScript можно сделать:
- Высокая скорость запуска: когда JavaScript запускается, он работает в режиме интерпретации и выполнения, используя преимущества высокой скорости запуска интерпретатора.
- Высокая производительность: код можно отслеживать во время выполнения JavaScript, чтобы его можно было скомпилировать и оптимизировать с использованием технологии JIT.
Принцип компиляции V8
V8Это виртуальная машина JavaScript с открытым исходным кодом, которая в настоящее время в основном используется в браузере Chrome (включаяChromium) и Node.js, основная функциональность предназначена для синтаксического анализа и выполнения языка JavaScript. Чтобы решить проблему низкой производительности раннего JavaScript, после того, как V8 претерпел множество исторических изменений фреймворка компиляции (заинтересованные студенты могут узнать о дизайне фреймворка компиляции раннего V8), для решения проблемы была введена технология гибридной динамической компиляции. скомпилированный фреймворк выглядит так:
Интерпретатор зажигания
Первичная роль зажигания - преобразовать ASTBytecode(байт-код, промежуточное представление). В процессе выполнения также используется технология TypeFeedback для вычисления горячего кода (HotSpot, многократно исполняемый код, который может быть методом или телом цикла) и, наконец, передача его в TurboFan для динамической оптимизации компиляции во время выполнения. Поток выполнения интерпретации Ignition выглядит следующим образом:
В процессе интерпретации и выполнения байт-кода информация времени выполнения, которую необходимо оптимизировать для повышения производительности, будет указывать на соответствующий вектор обратной связи (вектор обратной связи, ранее также известный как Type Feedback Vector), Feeback Vector будет содержать информацию о различных типах слотов (слот вектора обратной связи), хранящуюся в соответствии со встроенным кешем (Inline Cache, IC), например слот BinaryOp (тип данных результата двоичной операции), счетчик вызовов. (количество вызовов функции) и информацию об оптимизированном коде.
Напоминание: Детали каждого процесса выполнения не будут подробно объясняться здесь и будут объяснены в последующих сериях статей. Показанная выше информация о векторе обратной связи (вектор обратной связи) будет напечатана в следующей демо-версии среды выполнения V8.
Оптимизирующий компилятор TurboFan
TurboFan использует технологию компиляции JIT, и его основная функция заключается в компиляции и оптимизации кода JavaScript во время выполнения Конкретный процесс выглядит следующим образом:
Напоминание: источник изображенияAn Introduction to Speculative Optimization in V8.
Необходимо обратить внимание на раздел обратной связи профилирования, который в основном предоставляет Ignition для объяснения информации о векторе обратной связи во время выполнения. , и передать график.Для интерфейсной части код будет оптимизирован и деоптимизирован на основе информации о векторе обратной связи.
Напоминание: деоптимизация здесь относится к возврату кода в Ignition для интерпретации и выполнения. Суть деоптимизации заключается в том, что машинный код больше не может соответствовать текущим требованиям. Например, переменная преобразуется из строкового типа в тип числовой тип, а машинный код компилирует строковый тип, текущие требования больше не могут быть выполнены в это время, поэтому V8 выполнит действия по деоптимизации и вернет код в Ignition для интерпретации и выполнения.
Производительность V8 во время выполнения
После понимания принципа компиляции V8 нам нужно использовать инструменты отладки V8 для подробного просмотра информации о компиляции и запуске JavaScript, чтобы углубить наше понимание процесса компиляции V8.
Инструмент отладки D8
Если вы хотите понять информацию о времени компиляции и времени выполнения JavaScript в V8, вы можете использовать инструмент отладки D8. D8 – это оболочка командной строки ядра V8. Вы можете просматривать такую информацию, как генерация AST, байт-код промежуточного кода, оптимизированный код, деоптимизированный код, статистика оптимизированного компилятора и сборщик мусора кода. Существует множество способов установки D8, например:
- Способ 1: Согласно официальной документации V8 Using d8 так же какBuilding V8 with GN Загрузите и скомпилируйте набор инструментов
- Способ 2: Используйте инструменты D8, которые были скомпилированы другими, может быть отставание в версии, напримерверсия для Mac
- Способ 3. Например, с помощью инструмента управления версиями движка JavaScript.jsvu, вы можете скачать последний скомпилированный движок JavaScript
В этой статье для установки инструмента v8-debug используется метод 3. После завершения установки выполните v8-debug --help, чтобы узнать, какие команды доступны:
# 执行 help 命令查看支持的参数
v8-debug --help
Synopsis:
shell [options] [--shell] [<file>...]
d8 [options] [-e <string>] [--shell] [[--module|--web-snapshot] <file>...]
-e execute a string in V8
--shell run an interactive JavaScript shell
--module execute a file as a JavaScript module
--web-snapshot execute a file as a web snapshot
SSE3=1 SSSE3=1 SSE4_1=1 SSE4_2=1 SAHF=1 AVX=1 AVX2=1 FMA3=1 BMI1=1 BMI2=1 LZCNT=1 POPCNT=1 ATOM=0
The following syntax for options is accepted (both '-' and '--' are ok):
--flag (bool flags only)
--no-flag (bool flags only)
--flag=value (non-bool flags only, no spaces around '=')
--flag value (non-bool flags only)
-- (captures all remaining args in JavaScript)
Options:
# 打印生成的字节码
--print-bytecode (print bytecode generated by ignition interpreter)
type: bool default: --noprint-bytecode
# 跟踪被优化的信息
--trace-opt (trace optimized compilation)
type: bool default: --notrace-opt
--trace-opt-verbose (extra verbose optimized compilation tracing)
type: bool default: --notrace-opt-verbose
--trace-opt-stats (trace optimized compilation statistics)
type: bool default: --notrace-opt-stats
# 跟踪去优化的信息
--trace-deopt (trace deoptimization)
type: bool default: --notrace-deopt
--log-deopt (log deoptimization)
type: bool default: --nolog-deopt
--trace-deopt-verbose (extra verbose deoptimization tracing)
type: bool default: --notrace-deopt-verbose
--print-deopt-stress (print number of possible deopt points)
# 查看编译生成的 AST
--print-ast (print source AST)
type: bool default: --noprint-ast
# 查看编译生成的代码
--print-code (print generated code)
type: bool default: --noprint-code
# 查看优化后的代码
--print-opt-code (print optimized code)
type: bool default: --noprint-opt-code
# 允许在源代码中使用 V8 提供的原生 API 语法
--allow-natives-syntax (allow natives syntax)
type: bool default: --noallow-natives-syntax
Сгенерировать АСТ
Мы пишем файл index.js, пишем код JavaScript в файле и выполняем простую функцию добавления:
function add(x, y) {
return x + y
}
console.log(add(1, 2));
Используйте параметр --print-ast для печати информации AST функции добавления:
v8-debug --print-ast ./index.js
[generating bytecode for function: ]
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . FUNCTION "add" = function add
. EXPRESSION STATEMENT at 41
. . ASSIGN at -1
. . . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"
. . . CALL
. . . . PROPERTY at 49
. . . . . VAR PROXY unallocated (0x7fb8c080e6f0) (mode = DYNAMIC_GLOBAL, assigned = false) "console"
. . . . . NAME log
. . . . CALL
. . . . . VAR PROXY unallocated (0x7fb8c080e470) (mode = VAR, assigned = true) "add"
. . . . . LITERAL 1
. . . . . LITERAL 2
. RETURN at -1
. . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"
[generating bytecode for function: add]
--- AST ---
FUNC at 12
. KIND 0
. LITERAL ID 1
. SUSPEND COUNT 0
. NAME "add"
. PARAMS
. . VAR (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VAR (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. DECLS
. . VARIABLE (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VARIABLE (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. RETURN at 25
. . ADD at 34
. . . VAR PROXY parameter[0] (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . . VAR PROXY parameter[1] (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
Опишем графически полученное дерево AST:
Узел VAR PROXY подключается к узлу VAR по соответствующему адресу на этапе фактического анализа.
генерировать байт-код
AST будет генерировать байт-код (промежуточное представление) через функцию BytecodeGenerator интерпретатора Ignition.Мы можем распечатать информацию о байт-коде через параметр --print-bytecode:
v8-debug --print-bytecode ./index.js
[generated bytecode for function: (0x3ab2082933f5 <SharedFunctionInfo>)]
Bytecode length: 43
Parameter count 1
Register count 6
Frame size 48
OSR nesting level: 0
Bytecode Age: 0
0x3ab2082934be @ 0 : 13 00 LdaConstant [0]
0x3ab2082934c0 @ 2 : c3 Star1
0x3ab2082934c1 @ 3 : 19 fe f8 Mov <closure>, r2
0x3ab2082934c4 @ 6 : 65 52 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
0x3ab2082934c9 @ 11 : 21 01 00 LdaGlobal [1], [0]
0x3ab2082934cc @ 14 : c2 Star2
0x3ab2082934cd @ 15 : 2d f8 02 02 LdaNamedProperty r2, [2], [2]
0x3ab2082934d1 @ 19 : c3 Star1
0x3ab2082934d2 @ 20 : 21 03 04 LdaGlobal [3], [4]
0x3ab2082934d5 @ 23 : c1 Star3
0x3ab2082934d6 @ 24 : 0d 01 LdaSmi [1]
0x3ab2082934d8 @ 26 : c0 Star4
0x3ab2082934d9 @ 27 : 0d 02 LdaSmi [2]
0x3ab2082934db @ 29 : bf Star5
0x3ab2082934dc @ 30 : 63 f7 f6 f5 06 CallUndefinedReceiver2 r3, r4, r5, [6]
0x3ab2082934e1 @ 35 : c1 Star3
0x3ab2082934e2 @ 36 : 5e f9 f8 f7 08 CallProperty1 r1, r2, r3, [8]
0x3ab2082934e7 @ 41 : c4 Star0
0x3ab2082934e8 @ 42 : a9 Return
Constant pool (size = 4)
0x3ab208293485: [FixedArray] in OldSpace
- map: 0x3ab208002205 <Map>
- length: 4
0: 0x3ab20829343d <FixedArray[2]>
1: 0x3ab208202741 <String[7]: #console>
2: 0x3ab20820278d <String[3]: #log>
3: 0x3ab208003f09 <String[3]: #add>
Handler Table (size = 0)
Source Position Table (size = 0)
[generated bytecode for function: add (0x3ab20829344d <SharedFunctionInfo add>)]
Bytecode length: 6
// 接受 3 个参数, 1 个隐式的 this,以及显式的 x 和 y
Parameter count 3
Register count 0
// 不需要局部变量,因此帧大小为 0
Frame size 0
OSR nesting level: 0
Bytecode Age: 0
0x3ab2082935f6 @ 0 : 0b 04 Ldar a1
0x3ab2082935f8 @ 2 : 39 03 00 Add a0, [0]
0x3ab2082935fb @ 5 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 0)
Функция добавления в основном содержит следующие 3 последовательности байт-кода:
// Load Accumulator Register
// 加载寄存器 a1 的值到累加器中
Ldar a1
// 读取寄存器 a0 的值并累加到累加器中,相加之后的结果会继续放在累加器中
// [0] 指向 Feedback Vector Slot,Ignition 会收集值的分析信息,为后续的 TurboFan 优化做准备
Add a0, [0]
// 转交控制权给调用者,并返回累加器中的值
Return
Здесь интерпретация Ignition выполняет эти байт-коды, используя регистровую архитектуру структуры адресных инструкций.
Напоминание: для получения дополнительной информации о байт-коде вы можете просмотретьПонимание байт-кода V8. Архитектура регистра здесь подробно объясняется в следующей серии статей.
оптимизировать и деоптимизировать
JavaScript — это язык со слабой типизацией. Ему не нужно ограничивать типы данных формальных параметров вызовов функций, как в строго типизированных языках. Вместо этого он может гибко передавать различные типы параметров для обработки, как показано ниже:
function add(x, y) {
// + 操作符是 JavaScript 中非常复杂的一个操作
return x + y
}
add(1, 2);
add('1', 2);
add(null, 2);
add(undefined, 2);
add([], 2);
add({}, 2);
add([], {});
Для выполнения операции оператора + часто необходимо вызывать множество API-интерфейсов во время базового выполнения, таких как ToPrimitive (чтобы определить, является ли это объектом), ToString, ToNumber и т. д., для выполнения обработки преобразования данных, которая соответствует оператор + для входящих параметров.
Здесь V8 будет спекулировать на формальных параметрах x и y подобно строго типизированному языку в JavaScript, так что некоторые побочные эффекты ветки могут быть исключены во время выполнения процесса. исключений, поэтому его можно использовать для кода.Оптимизирован для максимальной производительности. Информация обратной связи (вектор обратной связи) собирается с помощью байт-кода в Ignition следующим образом:
Чтобы просмотреть информацию обратной связи о времени выполнения функции добавления, мы можем распечатать информацию о времени выполнения функции добавления через собственный API, предоставляемый V8, как показано ниже:
function add(x, y) {
return x + y
}
// 注意这里默认采用了 ClosureFeedbackCellArray,为了查看效果,强制开启 FeedbackVector
// 更多信息查看: A lighter V8:https://v8.dev/blog/v8-lite
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
// 打印 add 详细的运行时信息
%DebugPrint(add);
Базовый Native API %DebugPrint может быть вызван в JavaScript с помощью параметра --allow-natives-syntax (дополнительные API см.runtime.hголовной файл):
v8-debug --allow-natives-syntax ./index.js
DebugPrint: 0x1d22082935b9: [Function] in OldSpace
- map: 0x1d22082c2281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x1d2208283b79 <JSFunction (sfi = 0x1d220820abbd)>
- elements: 0x1d220800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x1d2208293491 <SharedFunctionInfo add>
- name: 0x1d2208003f09 <String[3]: #add>
// 包含 Ignition 解释器的 trampoline 指针
- builtin: InterpreterEntryTrampoline
- formal_parameter_count: 2
- kind: NormalFunction
- context: 0x1d2208283649 <NativeContext[263]>
- code: 0x1d2200005181 <Code BUILTIN InterpreterEntryTrampoline>
- interpreted
- bytecode: 0x1d2208293649 <BytecodeArray[6]>
- source code: (x, y) {
return x + y
}
- properties: 0x1d220800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x1d2208004bb5: [String] in ReadOnlySpace: #length: 0x1d2208204431 <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d2208004dfd: [String] in ReadOnlySpace: #name: 0x1d22082043ed <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d2208003fad: [String] in ReadOnlySpace: #arguments: 0x1d2208204365 <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d22080041f1: [String] in ReadOnlySpace: #caller: 0x1d22082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d22080050b1: [String] in ReadOnlySpace: #prototype: 0x1d2208204475 <AccessorInfo> (const accessor descriptor), location: descriptor
}
// 以下是详细的反馈信息
- feedback vector: 0x1d2208293691: [FeedbackVector] in OldSpace
- map: 0x1d2208002711 <Map>
- length: 1
- shared function info: 0x1d2208293491 <SharedFunctionInfo add>
- no optimized code
- optimization marker: OptimizationMarker::kNone
- optimization tier: OptimizationTier::kNone
- invocation count: 0
- profiler ticks: 0
- closure feedback cell array: 0x1d22080032b5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x1d2208002955 <Map>
- length: 0
- slot #0 BinaryOp BinaryOp:None {
[0]: 0
}
0x1d22082c2281: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- constructor
- has_prototype_slot
- back pointer: 0x1d22080023b5 <undefined>
- prototype_validity cell: 0x1d22082044fd <Cell value= 1>
- instance descriptors (own) #5: 0x1d2208283c29 <DescriptorArray[5]>
- prototype: 0x1d2208283b79 <JSFunction (sfi = 0x1d220820abbd)>
- constructor: 0x1d2208283bf5 <JSFunction Function (sfi = 0x1d220820acb9)>
- dependent code: 0x1d22080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
Напоминание: здесь SharedFunctionInfo (SFI) сохраняет информацию об указателе InterpreterEntryTrampoline, каждая функция будет иметь указатель на интерпретатор Ignition всякий раз, когда V8 необходимо ввестиоптимизировать, этот указатель используется для возврата кода к соответствующему местоположению выполнения функции интерпретатора.
Чтобы функция добавления была оптимизирована, как код HotSpot, здесь принудительно выполняется оптимизация функции:
function add(x, y) {
return x + y
}
add(1, 2);
// 强制开启函数优化
%OptimizeFunctionOnNextCall(add);
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
// 打印 add 详细的运行时信息
%DebugPrint(add);
Информацию об оптимизации компиляции функции добавления можно отследить с помощью параметра --trace-opt:
v8-debug --allow-natives-syntax --trace-opt ./index.js
[manually marking 0x3872082935bd <JSFunction add (sfi = 0x3872082934b9)> for non-concurrent optimization]
// 这里使用 TurboFan 优化编译器对 add 函数进行编译优化
[compiling method 0x3872082935bd <JSFunction add (sfi = 0x3872082934b9)> (target TURBOFAN) using TurboFan]
[optimizing 0x3872082935bd <JSFunction add (sfi = 0x3872082934b9)> (target TURBOFAN) - took 0.097, 2.003, 0.273 ms]
DebugPrint: 0x3872082935bd: [Function] in OldSpace
- map: 0x3872082c2281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x387208283b79 <JSFunction (sfi = 0x38720820abbd)>
- elements: 0x38720800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x3872082934b9 <SharedFunctionInfo add>
- name: 0x387208003f09 <String[3]: #add>
- formal_parameter_count: 2
- kind: NormalFunction
- context: 0x387208283649 <NativeContext[263]>
- code: 0x387200044001 <Code TURBOFAN>
- source code: (x, y) {
return x + y
}
- properties: 0x38720800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x387208004bb5: [String] in ReadOnlySpace: #length: 0x387208204431 <AccessorInfo> (const accessor descriptor), location: descriptor
0x387208004dfd: [String] in ReadOnlySpace: #name: 0x3872082043ed <AccessorInfo> (const accessor descriptor), location: descriptor
0x387208003fad: [String] in ReadOnlySpace: #arguments: 0x387208204365 <AccessorInfo> (const accessor descriptor), location: descriptor
0x3872080041f1: [String] in ReadOnlySpace: #caller: 0x3872082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor
0x3872080050b1: [String] in ReadOnlySpace: #prototype: 0x387208204475 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x387208293685: [FeedbackVector] in OldSpace
- map: 0x387208002711 <Map>
- length: 1
- shared function info: 0x3872082934b9 <SharedFunctionInfo add>
- no optimized code
- optimization marker: OptimizationMarker::kNone
- optimization tier: OptimizationTier::kNone
// 调用次数增加了 1 次
- invocation count: 1
- profiler ticks: 0
- closure feedback cell array: 0x3872080032b5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x387208002955 <Map>
- length: 0
- slot #0 BinaryOp BinaryOp:SignedSmall {
[0]: 1
}
0x3872082c2281: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- constructor
- has_prototype_slot
- back pointer: 0x3872080023b5 <undefined>
- prototype_validity cell: 0x3872082044fd <Cell value= 1>
- instance descriptors (own) #5: 0x387208283c29 <DescriptorArray[5]>
- prototype: 0x387208283b79 <JSFunction (sfi = 0x38720820abbd)>
- constructor: 0x387208283bf5 <JSFunction Function (sfi = 0x38720820acb9)>
- dependent code: 0x3872080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
Следует отметить, что V8 автоматически обнаружит структурные изменения в коде для выполнения деоптимизации. Например следующий код:
function add(x, y) {
return x + y
}
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
%OptimizeFunctionOnNextCall(add);
add(1, 2);
// 改变 add 函数的传入参数类型,之前都是 number 类型,这里传入 string 类型
add(1, '2');
%DebugPrint(add);
Мы можем отслеживать информацию о деоптимизации функции добавления с помощью параметра --trace-deopt:
v8-debug --allow-natives-syntax --trace-deopt ./index.js
// 执行去优化,reason: not a Smi(Smi 在后续的系列文章中进行讲解,这里说明传入的不是一个小整数类型)
[bailout (kind: deopt-eager, reason: not a Smi: begin. deoptimizing 0x08f70829363d <JSFunction add (sfi = 0x8f7082934c9)>, opt id 0, node id 58, bytecode offset 2, deopt exit 1, FP to SP delta 32, caller SP 0x7ffee9ce7d70, pc 0x08f700044162]
DebugPrint: 0x8f70829363d: [Function] in OldSpace
- map: 0x08f7082c2281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x08f708283b79 <JSFunction (sfi = 0x8f70820abbd)>
- elements: 0x08f70800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x08f7082934c9 <SharedFunctionInfo add>
- name: 0x08f708003f09 <String[3]: #add>
- formal_parameter_count: 2
- kind: NormalFunction
- context: 0x08f708283649 <NativeContext[263]>
- code: 0x08f700044001 <Code TURBOFAN>
- interpreted
- bytecode: 0x08f7082936cd <BytecodeArray[6]>
- source code: (x, y) {
return x + y
}
- properties: 0x08f70800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x8f708004bb5: [String] in ReadOnlySpace: #length: 0x08f708204431 <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f708004dfd: [String] in ReadOnlySpace: #name: 0x08f7082043ed <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f708003fad: [String] in ReadOnlySpace: #arguments: 0x08f708204365 <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f7080041f1: [String] in ReadOnlySpace: #caller: 0x08f7082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f7080050b1: [String] in ReadOnlySpace: #prototype: 0x08f708204475 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x8f708293715: [FeedbackVector] in OldSpace
- map: 0x08f708002711 <Map>
- length: 1
- shared function info: 0x08f7082934c9 <SharedFunctionInfo add>
- no optimized code
- optimization marker: OptimizationMarker::kNone
- optimization tier: OptimizationTier::kNone
- invocation count: 1
- profiler ticks: 0
- closure feedback cell array: 0x8f7080032b5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x08f708002955 <Map>
- length: 0
- slot #0 BinaryOp BinaryOp:Any {
[0]: 127
}
0x8f7082c2281: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- constructor
- has_prototype_slot
- back pointer: 0x08f7080023b5 <undefined>
- prototype_validity cell: 0x08f7082044fd <Cell value= 1>
- instance descriptors (own) #5: 0x08f708283c29 <DescriptorArray[5]>
- prototype: 0x08f708283b79 <JSFunction (sfi = 0x8f70820abbd)>
- constructor: 0x08f708283bf5 <JSFunction Function (sfi = 0x8f70820acb9)>
- dependent code: 0x08f7080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
Следует отметить, что в процессе деоптимизации код вызовет потерю производительности, поэтому в повседневной разработке рекомендуется использовать TypeScript для объявления типа кода, что может повысить производительность кода до определенного степень.
Суммировать
Исследование V8 в этой статье все еще находится на стадии перцептивного познания и не углубилось в лежащий в основе исходный код V8. Благодаря этой статье вы сможете получить перцептивное представление о принципе компиляции V8, а также предложить вам использовать TypeScript, который действительно может в определенной степени помочь в написании кода JavaScript. Вы можете самостоятельно следить за аккаунтом нашей команды, и в будущем мы опубликуем более подробную серию статей «Введение в JavaScript».