v8引擎系列之解释&编译

V8 引擎是 JS 运行引擎中的一种,在浏览器和node中比较常见。新开V8引擎系列文章,研究探索引擎背后的调用流程和设计思路。V8引擎系列文章主要参考V8引擎开源源码,辅助开源社区资料(部分资料已经过时)进行总结。如有总结错误之处,望不吝赐教,在此拜谢。

前置-背景

什么是 V8引擎

V8 是一个C++编写的程序,它用于编译执行JavaScript代码。提供以下基础能力:

💡
1、编译并执行JS代码

2、以某种顺序执行函数,处理调用栈

3、管理对象的内存分配,堆内存

4、垃圾回收

5、提供所有 JS 语言支持的数据类型、运算符、API 和公共函数

V8 提供可选事件循环(浏览器中运行JS时,事件循环由浏览器提供)。V8 是一个单线程运行JS代码的多线程应用,V8引擎本身是多线程程序,V8采用单线程运行JS代码。

💡
V8运行JS代码是是单线程。一个V8实例,运行一个单独的JS执行上下文。在浏览器或者 node.js 开发的进程中可以同时存在多个V8线程实例来实现多线程并发,比如:通过WebWorker等技术开辟新的执行JS上下文并调用V8线程执行。

V8 引擎内部多线程大体划分为几类:

💡
1、主线程:负责执行 JS 代码解析、编译、运行。处理所有关于 JS 代码执行相关的任务,包括调用栈管理和事件循环。

2、垃圾回收线程:负责自动管理内存。

3、TurboFan 编译线程: V8 执行JS代码过程中会标记 hot 代码,TurboFan 线程会将 hot 代码编译优化,通常hot代码会被转换为机器码。

4、I/O线程:node.js 环境中与 libuv 集成。负责文件系统操作,网络请求等等I/O操作。

5、其他辅助线程:debug、性能分析等等

V8 引擎运行 JS 代码流程大体如下:

image.png

V8 只负责执行 JS,部分运行环境需要调用宿主提供。

Host Env 运行环境

V8的运行宿主环境有多种,最为常见的是浏览器 Browser 和 NodeJS 。Browser 环境和 NodeJS 环境有相同的成员如:ECMAScript standard (JavaScript核心内置API)、函数调用栈、Heap 内存、垃圾回收等等。不一致的在于特殊 API + 事件循环。

特殊点如下:

Browser 环境:

💡
Web API: 浏览器提供的接口,用于程序和浏览器交互 Canvas、ServiceWorker、WebStorage、Fetch、 Audio/Video、Geolocation等等

DOM API:文档对象模型,用于对 HTML 进行操作。主要包含以下方面 API 功能Element CRUD、Event Handing 事件监听处理、CSSOM 样式表修改

Event Loop: 浏览器单独提供的时间循环与渲染周期结合

NodeJS 环境:

💡
文件系统 API: fs 模块,解决 i/o 读写

网络 API:http、https、net等模块

环境变量 proces :环境变量维护

Cluster集群和子线程:多线程并发API

其他模块: C++ 模块

Event Loop:NodeJS 的 Event Loop采用了多阶段设计,允许更细粒度的控制

image.png

事件驱动模型中唤起的回调,经过事件循环调用V8引擎执行。页面事件/浏览器生命周期/WebAPI 等等来源触发的回调函数,被添加到事件循环的任务队列中【这里也可称之为宏任务队列】。事件循环与微任务队列的关系在本文中不会深入,会单开一文。

以浏览器环境调用V8运行JS为例

image.png

到此,正式进入 JS 代码如何在引擎中运行过程。第一步是:解释执行编译优化

编译 & 运行

过程

V8 引擎将JS代码编译转换成字节码和部分机器码。步骤如下:

💡

  1. 解析器将 JS Code 解析转换成 AST 并分析出关联的 Scopes 作用域, 构建AST 的过程称之为解析 Parse。
  2. 随后进行第一次编译 AST + Scopes 编译转换成字节码, 第一个编译器名字叫做 Ignition ,ignition 将 AST 转变成 bytecode 字节码。
  3. 解释执行字节码。字节码被解释器 inceptor 解释执行,解释执行过程中会进行代码优化标记,。
  4. IC 优化。
  5. Hot 代码三级优化,转成机器码。针对Hot代码,V8引擎目前采用三级优化模式。分别是:Sparkplug、Maglev、TurboFan 。三位大师负责将标记为 Hot 代码进一步编译成为机器码,以此提升代码运行速度。机器码被 CPU 直接运行。

image.png

到了这里会有两个疑问,为什么不全部编译成为机器码?第二什么情况下会进行优化,什么情况下会退出优化?

💡

  • 最快的启动运行和不太大的内存。
    • JIT 编译成为字节码能够节约时间,直接编译成机器码需要分析整个JS脚本推断所有对象类型和优化条件。Web 站点快速加载、响应流畅需要最快启动JS脚本执行。
    • 大部分JS代码不会重复运行,运行初始编译成机器码耗时多且内存占用非常大。
  • JS 语言特性:动态类型不确定性
    • 运行时频繁修改变量类型,未运行前无法推断准确的类型。
    • 通过解释器 Ignition 运行字节码时,标记变量类型结构固定且频繁执行的 代码为 Hot,将Hot代编译成码机器码提升运行速率。
  • 跨平台兼容性
    • bytecode 与平台无关,可以在不同的硬件架构上运行。机器码由 CPU 直接运行,与 CPU 架构以及支持的编码强相关。

字节码 VS 机器码

编译有两种常见的模式 JIT (just in time)即时编译和 AOT(Ahead of time)提前编译。JIT 是在代码运行期间动态编译,编译-运行-编译-运行。AOT 一般常见于C++ 和 JAVA 这类强类型语言,打包构建时直接产出字节码或机器码。V8 引擎编译JS代码采用的是 JIT编译模式。

V8 执行 JS 代码过程中先翻译成 bytecode, 多次运行会标记可被优化的代码标记为 Hot TuboFan 将 Hot 代码从 bytecode 编译成机器码,加速运行【CPU直接运行机器码】。

image.png

V8 提供的字节码映射表:

https://github.com/v8/v8/blob/master/src/interpreter/bytecodes.h

10k 的 JS 代码就全编译成机器码需要 20M 的空间,1M的JS代码则需要约 2G 的内存空间。

V8采用折中方案:解释执行大部分代码,对部分高频代码进行优化。

V8 引擎运行优化

V8 引擎优化 “在内存、CPU、等资源合适的情况下,优化调用频繁;类型结构稳定;执行时间长;代码规模适中;代码控制流程简单的代码” 提升运行效率。剔除死代码,死代码包括:不可达代码;无用计算;冗余的类型检查。

在分析优化逻辑之前,需要先介绍一下 feedback_vector 。JS 代码在V8引擎中运行过程中,所有的代码分析 / 运行记录 等信息都是记录在 feedback_vector 上。

image.png

JS source → bytecode -> intercept execute - … → Maglev … → TurboFan → MachineCode .. 过程中代码的分析结果,运行中的返回值、稳定性、耗时等信息都存储在 feedback_vector 上.

💡
分类介绍 feedback_vector 存储的重要信息

1、代码编译分析信息:

2、运行时优化信息

V8 基于 各个环节提取的 feedback_vector 信息进行优化/去优化分析。包括:死代码消除、IC 内联代码优化、Hot 标记优化。

标记优化的大流程如下:

image.png

死代码消除

V8 对代码进行标记,不可达的代码;无用计算的代码;冗余的类型检查等等代码会被标记为 isDead。这些代码不会被执行也不会被内联优化检查到!

内联优化 【src/compiler/js-inlining-heuristic.cc】

减少函数调用开销(调用栈切换);减少局部变量访问次数来提升实现优化。函数体积小、调用频率高、参数类型稳定、没有复杂控制流。一句话,高频调用且可预测的小函数。

内联优化的小 case

1
2
3
4
5
6
7
8
9
10
11
12
// 原代码
function add(a, b) {
return a + b;
}
function calc(x, y) {
return add(x, y) * 2;
}

// 内联优化后
function calc(x, y) {
return (x + y) * 2;
}

内联优化标记策略:

💡

  1. 构造函数调用或者普通函数调用
  2. 存在直接递归调用的不优化
  3. 未达到最低调用频率不优化
  4. 强制内联小函数【27字节码以内,转换JS代码大概1-2行】注意:即使是小函数不符合前三条也不会优化!
  5. 考虑内存和性能预算,当内存预算到了瓶颈停止内联,内存占用超过预算甚至会将部分内联进行退化。小函数优势在此!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// file: src/compiler/js-inlining-heuristic.cc
Reduction JSInliningHeuristic::Reduce(Node* node) {

// 非构造函数调用或者普通函数调用 退出检查
if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();

// 累计内联大小超过上限--退出
if (total_inlined_bytecode_size_ >= max_inlined_bytecode_size_absolute_) {
return NoChange();
}

...
for (int i = 0; i < candidate.num_functions; ++i) {


// 直接递归调用---不优化
if (frame_info.shared_info().ToHandle(&frame_shared_info) &&
frame_shared_info.equals(shared.object())) {
TRACE("Not considering call site #" << node->id() << ":"
<< node->op()->mnemonic()
<< ", because of recursive inlining");
candidate.can_inline_function[i] = false;
}

// 强制优化小函数
if (candidate.can_inline_function[i]) {
can_inline_candidate = true;
BytecodeArrayRef bytecode = candidate.bytecode[i].value();
candidate.total_size += bytecode.length();
unsigned inlined_bytecode_size = 0;
if (OptionalJSFunctionRef function = candidate.functions[i]) {
if (OptionalCodeRef code = function->code(broker())) {
inlined_bytecode_size = code->GetInlinedBytecodeSize();
candidate.total_size += inlined_bytecode_size;
}
}
candidate_is_small = candidate_is_small &&
IsSmall(bytecode.length() + inlined_bytecode_size);
}
}

if (!can_inline_candidate) return NoChange();

// 未达到最低调用频率不优化:min_inlining_frequency = 0.15
if (candidate.frequency.IsKnown() &&
candidate.frequency.value() < v8_flags.min_inlining_frequency) {
return NoChange();
}

seen_.insert(node->id());

// 小函数强制内联---
if (candidate_is_small) {
return InlineCandidate(candidate, true);
}

// In the general case we remember the candidate for later.
candidates_.insert(candidate);
return NoChange();
}
1
2
3
4
// Returns true if opcode can be inlined.
static bool IsInlineeOpcode(Value value) {
return value == kJSConstruct || value == kJSCall;
}

💡
opcode 常见值如下:

  1. kJSCall:表示一个普通的 JavaScript 函数调用。
  2. kJSConstruct:表示一个构造函数调用,通常是通过 new 关键字调用的函数。
  3. kJSReturn:表示返回语句,结束当前函数并返回值。
  4. kJSLoadProperty:表示加载对象的属性。
  5. kJSStoreProperty:表示将值存储到对象的属性中。
  6. kJSLoadElement:表示加载数组或类数组对象的元素。
  7. kJSStoreElement:表示将值存储到数组或类数组对象的元素中。
  8. kJSThrow:表示抛出异常。
  9. kJSBranch:表示条件分支或跳转。

标记完成后,需要进行优化前检查,排序。到这里的基本都是非小函数【大概是函数体超过2行,小函数字节码限制27字节】。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// file: src/compiler/js-inlining-heuristic.cc
// 排序策略
void JSInliningHeuristic::Finalize() {

...
while (!candidates_.empty()) {
auto i = candidates_.begin();
Candidate candidate = *i;
candidates_.erase(i);

// 已经优化了的--跳过
if (!IrOpcode::IsInlineeOpcode(candidate.node->opcode())) continue;

// 无效死代码,跳过
if (candidate.node->IsDead()) continue;

// 预算检查---
double size_of_candidate =
candidate.total_size * v8_flags.reserve_inline_budget_scale_factor;
int total_size =
total_inlined_bytecode_size_ + static_cast<int>(size_of_candidate);

// 超过内联预算上限---本次暂时不优化
if (total_size > max_inlined_bytecode_size_cumulative_) {
info_->set_could_not_inline_all_candidates();
// Try if any smaller functions are available to inline.
continue;
}

// 恭喜!!进入下一环节~
Reduction const reduction = InlineCandidate(candidate, false);
if (reduction.Changed()) return;
}
}

分析函数历史执行情况!从参数格式结构稳定性、运行结果稳定性、异常错误等方面进行评估,内容来自 feedback_vector。

  • 稳定性检查,出现过不稳定执行的函数中止优化!
  • 函数内逐步进行内联优化!直到总内联优化占用达到上限!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate,
bool small_function) {
...
// 检查调用是否稳定--参数检查!
Node* if_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
Node* if_exceptions[kMaxCallPolymorphism + 1];

// 检查历史执行情况:运行稳定性、异常结果检查
for (int i = 0; i < num_calls; ++i) {
if_successes[i] = graph()->NewNode(common()->IfSuccess(), calls[i]);
if_exceptions[i] =
graph()->NewNode(common()->IfException(), calls[i], calls[i]);
}
// Morph the {if_exception} projection into a join.
...
}

...

// 稳定性检查通过后,逐步进行内联优化!直到总内联优化占用达到上限!
for (int i = 0; i < num_calls && total_inlined_bytecode_size_ <
max_inlined_bytecode_size_absolute_;
++i) {
if (candidate.can_inline_function[i] &&
(small_function || total_inlined_bytecode_size_ <
max_inlined_bytecode_size_cumulative_)) {
Node* call = calls[i];
Reduction const reduction = inliner_.ReduceJSCall(call);
if (reduction.Changed()) {
total_inlined_bytecode_size_ += candidate.bytecode[i]->length();
call->Kill();
}
}
}

return Replace(value);
}

内联优化存在几个针对字节码大小的限制,

💡

  • 小函数内联大小:27字节
  • 单个内联最大字节码:460字节
  • 累计内联上限:920字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// file : src/flags/flag-definitions.h
DEFINE_INT(max_inlined_bytecode_size, 460,
"maximum size of bytecode for a single inlining")
DEFINE_INT(max_inlined_bytecode_size_cumulative, 920,
"maximum cumulative size of bytecode considered for inlining")
DEFINE_INT(max_inlined_bytecode_size_absolute, 4600,
"maximum absolute size of bytecode considered for inlining")
DEFINE_FLOAT(
reserve_inline_budget_scale_factor, 1.2,
"scale factor of bytecode size used to calculate the inlining budget")
DEFINE_INT(max_inlined_bytecode_size_small, 27,
"maximum size of bytecode considered for small function inlining")
DEFINE_INT(max_optimized_bytecode_size, 60 * KB,
"maximum bytecode size to "
"be considered for turbofan optimization; too high values may cause "
"the compiler to hit (release) assertions")
DEFINE_FLOAT(min_inlining_frequency, 0.15, "minimum frequency for inlining")
DEFINE_BOOL(polymorphic_inlining, true, "polymorphic inlining")

其他不会被优化的场景:

💡

  • 动态的 eval
  • with 语句
  • 复杂的 try catch
  • 单个函数编译为字节码内存超过 460 字节
  • 调用评率低:ignition 为 0.15 [单位没看懂]
  • 动态 import
  • 非纯函数,存在不可预测的过程

内联优化是通过对解释执行的字节码进行执行过程中分析转换的,内联优化后产物还是字节码,Hot 代码中内联优化的代码则会一同编译成机器码。Hot 代码编译机器码部分,不再追溯关于内联优化相关内容。

Hot 代码优化

V8 代码在字节码解释执行阶段会附带标记Hot代码(附带信息存于feedback_vector),通过将 Hot 代码从字节码编译成机器码。机器码由CPU直接运行效率远远大于ignition解释器运行字节码。

V8 引擎采用多级优化策略(平衡编译耗时和机器码优化深度),截止24年11月,V8发布源码中采用的是三级编译器。 Maglev 编译器第一版本发布是在 chrome 117 。

先介绍一下Hot代码涉及的优化编译器:

image.png

三级优化模型,V8目的是平衡编译成本和运行速度。编译成本:编译时间 + 内存占用。编译速度Sparkplug最快。以下是编译的速度:

image.png

V8 引擎解释器+编译器优化历史组合跑分情况:

image.png

💡

1. Ignition(解释器)

  • 阶段:Ignition 是 V8 的字节码解释器,它是 JavaScript 执行的第一个阶段。
  • 功能:将 JavaScript 源代码编译为字节码,并逐条解释执行。这种解释执行通常适合初始化阶段或短期、低频执行的代码。
  • 调用条件:代码首次加载时,Ignition 会将源代码转为字节码,然后解释执行。
  • 优点:解释器生成字节码的速度较快、内存开销小,但由于每次都需要解释,性能会比编译的机器码稍慢。

2. Sparkplug(基线编译器)

  • 阶段:在 Ignition 之后,作为快速编译阶段执行。
  • 功能:Sparkplug 是 V8 的基线编译器,负责将字节码快速编译为机器码,来提高代码执行速度。相比解释执行,编译后的代码运行更快,但 Sparkplug 并不会进行复杂优化,因此编译时间也非常短。
  • 调用条件:当代码被标记为“热”代码; 不存在 feedback_vector;
  • 优点:快速生成机器码,提升代码性能的同时仍保持较低内存消耗。

3. Maglev(中层编译器)

  • 阶段:位于 Sparkplug 和 TurboFan 之间。
  • 功能:Maglev 是一种中层即时编译器,用于在代码“热度”增加但尚未进入深度优化阶段时提供进一步的优化。Maglev 生成的机器码质量高于 Sparkplug,但没有 TurboFan 的复杂优化。
  • 调用条件:Sparkplug 优化代码执行优化后,代码类型允许升级到 Maglev;之前没有Maglev 编译失败记录;未启动PGO(Profile Guided Optimization),启动PGO的直奔TurboFan;
  • 优点:提供更高质量的机器码,在提高性能的同时避免过多调用 TurboFan。

4. TurboFan(优化编译器)

  • 阶段:在代码被频繁调用后,为性能的最后提升执行。
  • 功能:TurboFan 是 V8 的优化编译器,对代码进行深度优化,生成高度优化的机器码。它适合执行频率非常高、长时间运行的“热”代码。
  • 调用条件:代码执行频率极高时触发,特别是在 Maglev 生成的代码仍不够高效的情况下,才会进入 TurboFan 的深度优化阶段。
  • 优点:TurboFan 会观察代码的执行模式,通过内联缓存和隐藏类等信息,对代码进行进一步优化,使其达到最佳性能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// file: src/execution/tiering-manager.cc
void TieringManager::OnInterruptTick(DirectHandle<JSFunction> function,
CodeKind code_kind) {
...
// 第一步:先检查是否可使用 Sparkplug 优化:【函数没有feedback_vector】
const bool compile_sparkplug =
CanCompileWithBaseline(isolate_, function->shared()) &&
function->ActiveTierIsIgnition(isolate_) && !maybe_had_optimized_osr_code;

if (compile_sparkplug) {
#ifdef V8_ENABLE_SPARKPLUG
// sparkpug 入口。。。
if (v8_flags.baseline_batch_compilation) {
isolate_->baseline_batch_compiler()->EnqueueFunction(function);
}

//
if (first_time_tiered_up_to_sparkplug) {
if (had_feedback_vector) {
if (function->shared()->cached_tiering_decision() ==
CachedTieringDecision::kPending) {
function->shared()->set_cached_tiering_decision(
CachedTieringDecision::kEarlySparkplug);
}
function->SetInterruptBudget(isolate_);
}
return;
}

// Sparkplug 优化后升级的场景:Maglev、turboFan
MaybeOptimizeFrame(function_obj, code_kind);

...
}

//----------------------------------------------------------

void TieringManager::MaybeOptimizeFrame(Tagged<JSFunction> function,
CodeKind current_code_kind) {

...

if (V8_UNLIKELY(v8_flags.always_osr)) {
TryRequestOsrAtNextOpportunity(isolate_, function);
// Continue below and do a normal optimized compile as well.
}

const bool maglev_osr = maglev::IsMaglevOsrEnabled();
const CodeKinds available_kinds = function->GetAvailableCodeKinds(isolate_);

const bool waiting_for_tierup =
(current_code_kind < CodeKind::TURBOFAN_JS &&
(available_kinds & CodeKindFlag::TURBOFAN_JS)) ||
(maglev_osr && current_code_kind < CodeKind::MAGLEV &&
(available_kinds & CodeKindFlag::MAGLEV));


if (function->IsOptimizationRequested(isolate_) || waiting_for_tierup) {
// 节能模式或电池节省模式 --- 不优化
if (V8_UNLIKELY(maglev_osr && current_code_kind == CodeKind::MAGLEV &&
(!v8_flags.osr_from_maglev ||
isolate_->EfficiencyModeEnabledForTiering() ||
isolate_->BatterySaverModeEnabled()))) {
return;
}

// OSR kicks in only once we've previously decided to tier up, but we are
// still in a lower-tier frame (this implies a long-running loop).
// 需要优化:但是优先级不够的,提供优化:大循环
TryIncrementOsrUrgency(isolate_, function);
return;
}

OptimizationDecision d =
ShouldOptimize(function->feedback_vector(), current_code_kind);

// We might be stuck in a baseline frame that wants to tier up to Maglev, but
// is in a loop, and can't OSR, because Maglev doesn't have OSR. Allow it to
// skip over Maglev by re-checking ShouldOptimize as if we were in Maglev.
if (V8_UNLIKELY(!isolate_->EfficiencyModeEnabledForTiering() && !maglev_osr &&
d.should_optimize() && d.code_kind == CodeKind::MAGLEV)) {
bool is_marked_for_maglev_optimization =
existing_request == CodeKind::MAGLEV ||
(available_kinds & CodeKindFlag::MAGLEV);
// 优化第二级:Maglev 优化检查进入
if (is_marked_for_maglev_optimization) {
d = ShouldOptimize(function->feedback_vector(), CodeKind::MAGLEV);
}
}

// 进入终极优化: turboFan
if (d.should_optimize()) Optimize(function, d);
}

//-------------------------------

OptimizationDecision TieringManager::ShouldOptimize(
Tagged<FeedbackVector> feedback_vector, CodeKind current_code_kind) {
Tagged<SharedFunctionInfo> shared = feedback_vector->shared_function_info();

// 已经优化过的代码,不处理
if (current_code_kind == CodeKind::TURBOFAN_JS) {
return OptimizationDecision::DoNotOptimize();
}

if (TiersUpToMaglev(current_code_kind) &&
shared->PassesFilter(v8_flags.maglev_filter) &&
!shared->maglev_compilation_failed()) {
if (v8_flags.profile_guided_optimization &&
shared->cached_tiering_decision() ==
CachedTieringDecision::kEarlyTurbofan) {
// 进入TurboFan 优化
return OptimizationDecision::TurbofanHotAndStable();
}

// Maglev 优化
return OptimizationDecision::Maglev();
}

if (V8_UNLIKELY(!v8_flags.turbofan ||
!shared->PassesFilter(v8_flags.turbo_filter) ||
(v8_flags.efficiency_mode_disable_turbofan &&
isolate_->EfficiencyModeEnabledForTiering()) ||
isolate_->BatterySaverModeEnabled())) {
return OptimizationDecision::DoNotOptimize();
}

// invocation_count 函数的调用次数!!efficiency_mode_delay_turbofan 初始值15000
if (isolate_->EfficiencyModeEnabledForTiering() &&
v8_flags.efficiency_mode_delay_turbofan &&
feedback_vector->invocation_count() <
v8_flags.efficiency_mode_delay_turbofan) {
return OptimizationDecision::DoNotOptimize();
}

// 优化的字节码超过上限:不优化 max_optimized_bytecode_size 初始值60KB
Tagged<BytecodeArray> bytecode = shared->GetBytecodeArray(isolate_);
if (bytecode->length() > v8_flags.max_optimized_bytecode_size) {
return OptimizationDecision::DoNotOptimize();
}

// 进入 TurboFan 优化
return OptimizationDecision::TurbofanHotAndStable();
}

以上就是Hot代码分层优化的内容,什么时候去优化呢?当内存资源出现压力后、函数变冷(调用不再频繁)等情况优化会被去掉,退回字节码解释执行。

如何写好代码?

思考题~

参考资料