1 摘要
Turbofan 是基于 Sea of Nodes 理论的优化编译,它是 V8 Compiler Pipeline 三个节点中的最后一个节点,此外还有 Ingintion 和 Sparkplug。 Turbofan 使 JavaScript 执行的更快,但也需要更多的编译时间,所以 V8 只对热点函数使用 Turbofan。本文将介绍 Turbofan 的开启方式、工作流程以及重要的数据结构。
2 Turbofan
下面给出测试样例代码:
function addstring(a,b) {
return a+b;
}
addstring("hello ","Turbofan!");
//分隔线..........................
Bytecode Age: 0
00000043637A1DDE @ 0 : 13 00 LdaConstant [0]
00000043637A1DE0 @ 2 : c2 Star1
00000043637A1DE1 @ 3 : 19 fe f8 Mov <closure>, r2
00000043637A1DE4 @ 6 : 64 51 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
00000043637A1DE9 @ 11 : 21 01 00 LdaGlobal [1], [0]
00000043637A1DEC @ 14 : c2 Star1
00000043637A1DED @ 15 : 13 02 LdaConstant [2]
00000043637A1DEF @ 17 : c1 Star2
00000043637A1DF0 @ 18 : 13 03 LdaConstant [3]
00000043637A1DF2 @ 20 : c0 Star3
00000043637A1DF3 @ 21 : 62 f9 f8 f7 02 CallUndefinedReceiver2 r1, r2, r3, [2]
00000043637A1DF8 @ 26 : c3 Star0
00000043637A1DF9 @ 27 : a8 Return
Constant pool (size = 4)
00000043637A1D79: [FixedArray] in OldSpace
- map: 0x01d0222812c1 <Map>
- length: 4
0: 0x0043637a1d01 <FixedArray[2]>
1: 0x0043637a1c09 <String[9]: #addstring>
2: 0x0043637a1c29 <String[6]: #hello >
3: 0x0043637a1c41 <String[9]: #Turbofan!>
上述代码中 LdaGlobal [1], [0]、LdaConstant [2] 和 LdaConstant [3] 三条指令分别获取 addstring() 函数和两个参数;CallUndefinedReceiver2 调用 addstring() 函数完成字符串相加。
只有热点函数才会启动 Turbofan,为了方便学习,需要使用 %OptimizeFunctionOnNextCall() 和 —allow-natives-syntax 指令主动触发 Turbofan。
(1)%OptimizeFunctionOnNextCall() 用于在下次调用时启动优化编译;
(2)—allow-natives-syntax 允许 d8 接受 natives 指令。图 1 给出了使用方法。
(3)%DebugPrint 用于打印调试信息。
对测试代码进行一些改动,源码如下:
1. function addstring(a,b) {
2. return a+b;
3. }
4. console.log(addstring("hello ","Turbofan!"));
5. %DebugPrint(addstring);
6. %OptimizeFunctionOnNextCall(addstring);
7. addstring("Speculative ", "Optimization");
8. %DebugPrint(addstring);
上述第 4 行代码执行后会产生 Feedback;第 5 行代码使用 Turbofan 优化 addstring;Turbofan 优化机制的核心思想是投机,即会省去很多不必要的操作来提升性能,简而言之 Turbofan 认为 addstring 只用于字符串的加法操作,期望下次调用 addstring 时传入的两个参数仍然是字符串,这样就可以省去很多不必要的操作。执行第 5 行代码后打印的信息如下:
1. hello Turbofan!
2. DebugPrint: 000000F3196E1FA1: [Function] in OldSpace
3. - map: 0x00ae91d413a1 <Map(HOLEY_ELEMENTS)> [FastProperties]
4. - prototype: 0x00f3196c3ee9 <JSFunction (sfi = 000001CFDCE91269)>
5. - elements: 0x03e492c81309 <FixedArray[0]> [HOLEY_ELEMENTS]
6. - function prototype:
7. - initial_map:
8. - shared_info: 0x00f3196e1db1 <SharedFunctionInfo addstring>
9. - name: 0x00f3196e1c09 <String[9]: #addstring>
10. - builtin: InterpreterEntryTrampoline
11. - formal_parameter_count: 2
12. - kind: NormalFunction
13. - context: 0x00f3196c34a9 <NativeContext[262]>
14. - code: 0x021668546441 <Code BUILTIN InterpreterEntryTrampoline>
15. - interpreted
16. - bytecode: 0x00f3196e20c1 <BytecodeArray[6]>
17. - source code: (a,b) {
18. return a+b;
19. }
20. //..........省略........................
21. - feedback vector: No feedback vector, but we have a closure feedback cell array
22. 000003E492C82FB9: [ClosureFeedbackCellArray] in ReadOnlySpace
23. - map: 0x03e492c81f19 <Map>
24. - length: 0
25. 000000AE91D413A1: [Map]
26. - type: JS_FUNCTION_TYPE
27. - instance size: 64
28. - inobject properties: 0
29. - elements kind: HOLEY_ELEMENTS
30. - unused property fields: 0
31. - enum length: invalid
32. - stable_map
33. - callable
34. - constructor
35. - has_prototype_slot
36. - back pointer: 0x03e492c81599 <undefined>
37. - prototype_validity cell: 0x01cfdce84a09 <Cell value= 1>
38. - instance descriptors (own) #5: 0x00f3196c4041 <DescriptorArray[5]>
39. - prototype: 0x00f3196c3ee9 <JSFunction (sfi = 000001CFDCE91269)>
40. - constructor: 0x00f3196c3fd9 <JSFunction Function (sfi = 000001CFDCE91409)>
41. - dependent code: 0x03e492c81239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
42. - construction counter: 0
上述信息是执行 Turbofan 之前的 debug 输出,第 1 行是运行结果;第 8 行是 SharedFunctionInfo;第 10 行说明启动方式为 InterpreterEntryTrampoline,也就是解释执行;第 14 行是 InterpreterEntryTrampoline 的入口地址;第 23-42 行给出了 addstring 的 map 信息。
下面给出执行 Turbofan 之后的 debug 信息:
1. Speculative Optimization
2. DebugPrint: 000000F3196E1FA1: [Function] in OldSpace
3. - map: 0x00ae91d413a1 <Map(HOLEY_ELEMENTS)> [FastProperties]
4. - prototype: 0x00f3196c3ee9 <JSFunction (sfi = 000001CFDCE91269)>
5. - elements: 0x03e492c81309 <FixedArray[0]> [HOLEY_ELEMENTS]
6. - function prototype:
7. - initial_map:
8. - shared_info: 0x00f3196e1db1 <SharedFunctionInfo addstring>
9. - name: 0x00f3196e1c09 <String[9]: #addstring>
10. - formal_parameter_count: 2
11. - kind: NormalFunction
12. - context: 0x00f3196c34a9 <NativeContext[262]>
13. - code: 0x021668583001 <Code TURBOFAN>
14. - interpreted
15. - bytecode: 0x00f3196e20c1 <BytecodeArray[6]>
16. - source code: (a,b) {
17. return a+b;
18. }
19. - properties: 0x03e492c81309 <FixedArray[0]>
20. - feedback vector: 000000F3196E2119: [FeedbackVector] in OldSpace
21. - map: 0x03e492c81b69 <Map>
22. - length: 1
23. - shared function info: 0x00f3196e1db1 <SharedFunctionInfo addstring>
24. - no optimized code
25. - optimization marker: OptimizationMarker::kNone
26. - optimization tier: OptimizationTier::kNone
27. - invocation count: 0
28. - profiler ticks: 0
29. - closure feedback cell array: 000003E492C82FB9: [ClosureFeedbackCellArray] in ReadOnlySpace
30. - map: 0x03e492c81f19 <Map>
31. - length: 0
32. - slot #0 BinaryOp BinaryOp:String {
33. [0]: 16
34. }
35. 000000AE91D413A1: [Map]
36. - type: JS_FUNCTION_TYPE
37. - instance size: 64
38. - inobject properties: 0
39. - elements kind: HOLEY_ELEMENTS
40. - unused property fields: 0
41. - enum length: invalid
42. - stable_map
43. - callable
44. - constructor
45. - has_prototype_slot
46. - back pointer: 0x03e492c81599 <undefined>
47. - prototype_validity cell: 0x01cfdce84a09 <Cell value= 1>
48. - instance descriptors (own) #5: 0x00f3196c4041 <DescriptorArray[5]>
49. - prototype: 0x00f3196c3ee9 <JSFunction (sfi = 000001CFDCE91269)>
50. - constructor: 0x00f3196c3fd9 <JSFunction Function (sfi = 000001CFDCE91409)>
51. - dependent code: 0x03e492c81239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
52. - construction counter: 0
上述信息是执行 Turbofan 后的 debug 输出。第 1 行是输出结果;与执行 Turbofan 前的 debug 输出的区别是:
(1) 第 13 行信息说明运行方式为 Code TURBOFAN ,而不是 InterpreterEntryTrampoline。
(2) 第 32 行信息说明当前 feedback vector slot 是对字符串的操作,也就是为字符串操作做优化。
feedback 是一种收集运行时信息的机制,之所以叫 vector,是因为他是用 C++ 的 vector 实现的。feedback 可以用于指导 inline cache 存储相关信息,也可以用于指导 Turbofan 工作。根据 JavaScript 的数据类型,我们可能粗略推算出 feedback 的状态如图 2所示。
V8 开始执行时 feedback 是 None,因为此时我们什么也没有,也看不到任何信息。由于 addstring 常用于字符串相加,所以运行一段时间后 feedback 变成了 String,这时就可以使用 Turbofan 对字符串加法做优化。随着程序的运行 feedback 又变成了 Any,这时意味着 addstring 常被用于任何数据的相加。注意 feedback 的状态一般是自上而下转换的,如果逆向转换就可能发生去优化。
本文使用 %OptimizeFunctionOnNextCall() 主动唤起 Turbofan,源码如下:
1. RUNTIME_FUNCTION(Runtime_OptimizeFunctionOnNextCall) {
2. HandleScope scope(isolate);
3. return OptimizeFunctionOnNextCall(args, isolate, TierupKind::kTierupBytecode);
4. }
5. //................分隔线..............
6. bool Compiler::CompileOptimized(Isolate* isolate, Handle<JSFunction> function,
7. ConcurrencyMode mode, CodeKind code_kind) {
8. DCHECK(CodeKindIsOptimizedJSFunction(code_kind));
9. DCHECK(AllowCompilation::IsAllowed(isolate));
10. if (FLAG_stress_concurrent_inlining &&
11. isolate->concurrent_recompilation_enabled() &&
12. mode == ConcurrencyMode::kNotConcurrent &&
13. isolate->node_observer() == nullptr) {
14. SpawnDuplicateConcurrentJobForStressTesting(isolate, function, mode,
15. code_kind);
16. }
17. Handle<Code> code;
18. if (!GetOptimizedCode(isolate, function, mode, code_kind).ToHandle(&code)) {
19. // Optimization failed, get the existing code. We could have optimized code
20. // from a lower tier here. Unoptimized code must exist already if we are
21. // optimizing.
22. DCHECK(!isolate->has_pending_exception());
23. DCHECK(function->shared().is_compiled());
24. DCHECK(function->shared().IsInterpreted());
25. code = ContinuationForConcurrentOptimization(isolate, function);
26. }
27. function->set_code(*code, kReleaseStore);
28. // Check postconditions on success.
29. return true;
30. }
上述是两部分代码:Runtime_OptimizeFunctionOnNextCall 设置 Turbofan 的使能标记为真,设置使能标记后会调用 Runtime_CompileOptimized_NotConcurrent 方法,在该方法中调用上述代码中的 CompileOptimized。图 3 给出了 CompileOptimized 方法的调用堆栈。
技术总结
(1) 只有使用 —allow-natives-syntax 选项时,才能在 JavaScript 中使用 native 指令;
(2) feedback 的状态发生逆向转换时可能导致去优化。
好了,今天到这里,下次见。
个人能力有限,有不足与纰漏,欢迎批评指正
微信:qq9123013 备注:v8交流 邮箱:v8blink@outlook.com