V8編譯淺談

一  簡介

本文是一個 V8 編譯原理知識的介紹文章,旨在讓大家感性的瞭解 JavaScript 在 V8 中的解析過程。本文主要的撰寫流程如下:

  • 直譯器和編譯器:計算機編譯原理的基礎知識介紹
  • V8 的編譯原理:基於計算機編譯原理的知識,瞭解 V8 對於 JavaScript 的解析流程
  • V8 的執行時表現:結合 V8 的編譯原理,實踐 V8 在解析流程中的具體執行表現
本文僅代表個人觀點,文中若有錯誤歡迎指正。

二  直譯器和編譯器

大家可能一直疑惑的問題:JavaScript 是一門解釋型語言嗎?要了解這個問題,首先需要初步瞭解什麼是直譯器和編譯器以及它們的特點是什麼。

1  直譯器

直譯器的作用是將某種語言編寫的源程式作為輸入,將該源程式執行的結果作為輸出,例如 Perl、Scheme、APL 等都是使用直譯器進行轉換執行:

2  編譯器

編譯器的設計是一個非常龐大和複雜的軟體系統設計,在真正設計的時候需要解決兩個相對重要的問題:
  • 如何分析不同高階程式語言設計的源程式
  • 如何將源程式的功能等價對映到不同指令系統的目標機器

中間表示(IR)

中間表示(Intermediate Representation,IR)是程式結構的一種表現方式,它會比抽象語法樹(Abstract Syntax Tree,AST)更加接近組合語言或者指令集,同時也會保留源程式中的一些高階資訊,具體作用包括:

  • 易於編譯器的錯誤除錯,容易識別是 IR 之前的前端還是之後的後端出的問題
  • 可以使得編譯器的職責更加分離,源程式的編譯更多關注如何轉換成 IR,而不是去適配不同的指令集
  • IR 更加接近指令集,從而相對於原始碼可以更加節省記憶體空間

最佳化編譯器

IR 本身可以做到多趟迭代從而最佳化源程式,在每一趟迭代的過程中可以研究程式碼並記錄最佳化的細節,方便後續的迭代查詢並利用這些最佳化資訊,最終可以高效輸出更優的目標程式:

最佳化器可以對 IR 進行一趟或者多趟處理,從而生成更快執行速度或者更小體積的目標程式(例如找到迴圈中不變的計算並對其進行最佳化從而減少運算次數),也可能用於產生更少異常或者更低功耗的目標程式。除此之外,前端和後端內部還可以細分為多個處理步驟,具體如下圖所示:

3  兩者的特性比較

直譯器和編譯器的具體特性比較如下所示:
需要注意早期的 Web 前端要求頁面的啟動速度快,因此採用解釋執行的方式,但是頁面在執行的過程中效能相對較低。為了解決這個問題,需要在執行時對 JavaScript 程式碼進行最佳化,因此在 JavaScript 的解析引擎中引入了 JIT 技術。

4  JIT 編譯技術

JIT (Just In Time)編譯器是一種動態編譯技術,相對於傳統編譯器而言,最大的區別在於編譯時和執行時不分離,是一種在執行的過程中對程式碼進行動態編譯的技術。

5  混合動態編譯技術

為了解決 JavaScript 在執行時效能較慢的問題,可以透過引入 JIT 技術,並採用混合動態編譯的方式來提升 JavaScript 的執行效能,具體思路如下所示:

採用上述編譯框架後,可以使得 JavaScript 語言:

  • 啟動速度快:在 JavaScript 啟動的時候採用解釋執行的方式執行,利用瞭解釋器啟動速度快的特性
  • 執行效能高:在 JavaScript 執行的過程中可以對程式碼進行監控,從而使用 JIT 技術對程式碼進行編譯最佳化

三  V8 的編譯原理

V8 是一個開源的 JavaScript 虛擬機器,目前主要用在 Chrome 瀏覽器(包括開源的 Chromium)以及 Node.js 中,核心功能是用於解析和執行 JavaScript 語言。為了解決早期 JavaScript 執行效能差的問題,V8 經歷了多個歷史的編譯框架衍變之後(感興趣的同學可以瞭解一下早期的 V8 編譯框架設計),引入混合動態編譯的技術來解決問題,具體詳細的編譯框架如下所示:

1  Ignition 直譯器

Ignition 的主要作用是將 AST 轉換成 Bytecode(位元組碼,中間表示)。在執行的過程中,還會使用型別反饋(TypeFeedback)技術並計算熱點程式碼(HotSpot,重複被執行的程式碼,可以是方法也可以是迴圈體),最終交給 TurboFan 進行動態執行時的編譯最佳化。Ignition 的解釋執行流程如下所示:
在位元組碼解釋執行的過程中,會將需要進行效能最佳化的執行時資訊指向對應的 Feedback Vector(反饋向量,之前也被稱為 Type Feedback Vector),Feeback Vector 中會包含根據內聯快取(Inline Cache,IC)來儲存的多種型別的插槽(Feedback Vector Slot)資訊,例如 BinaryOp 插槽(二進位制操作結果的資料型別)、Invocation Count(函式的呼叫次數)以及 Optimized Code 資訊等。
這裡不會過多講解每個執行流程的細節問題。

2  TurboFan 最佳化編譯器

TurboFan 利用了 JIT 編譯技術,主要作用是對 JavaScript 程式碼進行執行時編譯最佳化,具體的流程如下所示:
圖片出處 An Introduction to Speculative Optimization in V8
需要注意 Profiling Feedback 部分,這裡主要提供 Ignition 解釋執行過程中生成的執行時反饋向量資訊 Feedback Vector ,Turbofan 會結合位元組碼以及反饋向量資訊生成圖示(資料結構中的圖結構),並將圖傳遞給前端部分,之後會根據反饋向量資訊對程式碼進行最佳化和去最佳化。
這裡的去最佳化是指讓程式碼回退到 Ignition 進行解釋執行,去最佳化本質是因為機器碼已經不能滿足執行訴求,例如一個變數從 string 型別轉變成 number 型別,機器碼編譯的是 string 型別,此時已經無法再滿足執行訴求,因此 V8 會執行去最佳化動作,將程式碼回退到 Ignition 進行解釋執行。

四  V8 的執行時表現

在瞭解 V8 的編譯原理之後,接下來需要使用 V8 的除錯工具來具體檢視 JavaScript 的編譯和執行資訊,從而加深我們對 V8 的編譯過程認知。

1  D8 除錯工具

如果想了解 JavaScript 在 V8 中的編譯時和執行時資訊,可以使用除錯工具 D8。D8 是 V8 引擎的命令列 Shell,可以檢視 AST 生成、中間程式碼 ByteCode、最佳化程式碼、反最佳化程式碼、最佳化編譯器的統計資料、程式碼的 GC 等資訊。D8 的安裝方式有很多,如下所示:

  • 方法一:根據 V8 官方文件 Using d8 以及 Building V8 with GN 進行工具鏈的下載和編譯
  • 方法二:使用別人已經編譯好的 D8 工具,可能版本會有滯後性,例如 Mac 版
  • 方法三:使用 JavaScript 引擎版本管理工具,例如 jsvu,可以下載到最新編譯好的 JavaScript 引擎

本文使用方法三安裝 v8-debug 工具,安裝完成後執行 v8-debug –help 可以檢視有哪些命令:

# 執行 help 命令檢視支援的引數v8-debug --helpSynopsis: 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 snapshotSSE3=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=0The 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: booldefault: --noprint-bytecode # 跟蹤被最佳化的資訊 --trace-opt (trace optimized compilation)type: booldefault: --notrace-opt --trace-opt-verbose (extra verbose optimized compilation tracing)type: booldefault: --notrace-opt-verbose --trace-opt-stats (trace optimized compilation statistics)type: booldefault: --notrace-opt-stats # 跟蹤去最佳化的資訊 --trace-deopt (trace deoptimization)type: booldefault: --notrace-deopt --log-deopt (log deoptimization)type: booldefault: --nolog-deopt --trace-deopt-verbose (extra verbose deoptimization tracing)type: booldefault: --notrace-deopt-verbose --print-deopt-stress (print number of possible deopt points) # 檢視編譯生成的 AST --print-ast (print source AST)type: booldefault: --noprint-ast # 檢視編譯生成的程式碼 --print-code (print generated code)type: booldefault: --noprint-code # 檢視最佳化後的程式碼 --print-opt-code (print optimized code)type: booldefault: --noprint-opt-code # 允許在原始碼中使用 V8 提供的原生 API 語法 --allow-natives-syntax (allow natives syntax)type: booldefault: --noallow-natives-syntax

2  生成 AST

我們編寫一個 index.js 檔案,在檔案中寫入 JavaScript 程式碼,執行一個簡單的 add 函式:
functionadd(x, y) {return x + y}console.log(add(1, 2));
使用 –print-ast 引數可以列印 add 函式的 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 at49. . . . . VAR PROXY unallocated (0x7fb8c080e6f0) (mode = DYNAMIC_GLOBAL, assigned = false) "console". . . . . NAMElog. . . . CALL. . . . . VAR PROXY unallocated (0x7fb8c080e470) (mode = VAR, assigned = true) "add". . . . . LITERAL 1. . . . . LITERAL 2. RETURNat-1. . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"[generating bytecode forfunction: add]--- AST ---FUNC at12. KIND 0. LITERAL ID1. SUSPENDCOUNT0. 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". RETURNat25. . ADDat34. . . VAR PROXY parameter[0] (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x". . . VAR PROXY parameter[1] (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
我們以圖形化的方式來描述生成的 AST 樹:
VAR PROXY 節點在真正的分析階段會連線到對應地址的 VAR 節點。

3  生成位元組碼

AST 會經過 Ignition 直譯器的 BytecodeGenerator 函式生成位元組碼(中間表示),我們可以透過 –print-bytecode 引數來列印位元組碼資訊:

v8-debug--print-bytecode ./index.js[generatedbytecode for function: (0x3ab2082933f5 <SharedFunctionInfo>)]Bytecodelength: 43Parametercount 1Registercount 6Framesize 48OSRnesting level: 0BytecodeAge: 00x3ab2082934be@ 0 : 13 00 LdaConstant [0]0x3ab2082934c0@ 2 : c3 Star1 0x3ab2082934c1@ 3 : 19 fe f8 Mov <closure>, r20x3ab2082934c4@ 6 : 65 52 01 f9 02 CallRuntime [DeclareGlobals], r1-r20x3ab2082934c9@ 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 Constantpool (size = 4)0x3ab208293485: [FixedArray] in OldSpace-map: 0x3ab208002205 <Map>-length: 40: 0x3ab20829343d <FixedArray[2]>1: 0x3ab208202741 <String[7]: #console>2: 0x3ab20820278d <String[3]: #log>3: 0x3ab208003f09 <String[3]: #add>HandlerTable (size = 0)SourcePosition Table (size = 0)[generatedbytecode for function: add (0x3ab20829344d <SharedFunctionInfo add>)]Bytecodelength: 6//接受 3 個引數, 1 個隱式的 this,以及顯式的 x 和 yParametercount 3Registercount 0//不需要區域性變數,因此幀大小為 0 Framesize 0OSRnesting level: 0BytecodeAge: 00x3ab2082935f6@ 0 : 0b 04 Ldar a10x3ab2082935f8@ 2 : 39 03 00 Add a0, [0]0x3ab2082935fb@ 5 : a9 Return Constantpool (size = 0)HandlerTable (size = 0)SourcePosition Table (size = 0)
add 函式主要包含以下 3 個位元組碼序列:
// Load Accumulator Register// 載入暫存器 a1 的值到累加器中Ldar a1// 讀取暫存器 a0 的值並累加到累加器中,相加之後的結果會繼續放在累加器中// [0] 指向 Feedback Vector Slot,Ignition 會收集值的分析資訊,為後續的 TurboFan 最佳化做準備Add a0, [0]// 轉交控制權給呼叫者,並返回累加器中的值Return
這裡 Ignition 的解釋執行這些位元組碼採用的是一地址指令結構的暫存器架構。
關於更多位元組碼的資訊可檢視 Understanding V8’s Bytecode。

4  最佳化和去最佳化

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 會對 JavaScript 像強型別語言那樣對形參 x 和 y 進行推測,這樣就可以在執行的過程中排除一些副作用分支程式碼,同時這裡也會預測程式碼不會丟擲異常,因此可以對程式碼進行最佳化,從而達到最高的執行效能。在 Ignition 中透過位元組碼來收集反饋資訊(Feedback Vector),如下所示:

為了檢視 add 函式的執行時反饋資訊,我們可以透過 V8 提供的 Native API 來列印 add 函式的執行時資訊,具體如下所示:

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);
透過 –allow-natives-syntax 引數可以在 JavaScript 中呼叫 %DebugPrint 底層 Native API(更多 API 可以檢視 V8 的 runtime.h 標頭檔案):
v8-debug --allow-natives-syntax ./index.jsDebugPrint: 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: descriptor0x1d2208004dfd: [String] in ReadOnlySpace: #name: 0x1d22082043ed <AccessorInfo> (const accessor descriptor), location: descriptor0x1d2208003fad: [String] in ReadOnlySpace: #arguments: 0x1d2208204365 <AccessorInfo> (const accessor descriptor), location: descriptor0x1d22080041f1: [String] in ReadOnlySpace: #caller: 0x1d22082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor0x1d22080050b1: [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 直譯器的 trampoline 指標,每當 V8 需要進去去最佳化時,就會使用此指標使程式碼回退到直譯器相應的函式執行位置。
為了使得 add 函式可以像 HotSpot 程式碼一樣被最佳化,在這裡強制做一次函式最佳化:
function add(x, y) {return x + y}add(1, 2);// 強制開啟函式最佳化%OptimizeFunctionOnNextCall(add);%EnsureFeedbackVectorForFunction(add);add(1, 2);// 列印 add 詳細的執行時資訊 %DebugPrint(add);
透過 –trace-opt 引數可以跟蹤 add 函式的編譯最佳化資訊:
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] - functionprototype: - initial_map: - shared_info: 0x3872082934b9 <SharedFunctionInfoadd> - name: 0x387208003f09 <String[3]: #add> - formal_parameter_count: 2 - kind: NormalFunction - context: 0x387208283649 <NativeContext[263]> - code: 0x387200044001 <CodeTURBOFAN> - sourcecode: (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: descriptor0x387208004dfd: [String] in ReadOnlySpace: #name: 0x3872082043ed <AccessorInfo> (const accessor descriptor), location: descriptor0x387208003fad: [String] in ReadOnlySpace: #arguments: 0x387208204365 <AccessorInfo> (const accessor descriptor), location: descriptor0x3872080041f1: [String] in ReadOnlySpace: #caller: 0x3872082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor0x3872080050b1: [String] in ReadOnlySpace: #prototype: 0x387208204475 <AccessorInfo> (const accessor descriptor), location: descriptor } - feedback vector: 0x387208293685: [FeedbackVector] in OldSpace - map: 0x387208002711 <Map> - length: 1 - shared functioninfo: 0x3872082934b9 <SharedFunctionInfoadd> - nooptimizedcode - optimizationmarker: OptimizationMarker::kNone - optimizationtier: OptimizationTier::kNone // 呼叫次數增加了 1 次 - invocationcount: 1 - profilerticks: 0 - closurefeedbackcellarray: 0x3872080032b5: [ClosureFeedbackCellArray] inReadOnlySpace - map: 0x387208002955 <Map> - length: 0 - slot #0 BinaryOpBinaryOp: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 引數跟蹤 add 函式的去最佳化資訊:
ziyi@B-D0UTG8WN-2029 .jsvu % 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 id0, node id58, bytecode offset2, deopt exit1, 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: descriptor0x8f708004dfd: [String] in ReadOnlySpace: #name: 0x08f7082043ed <AccessorInfo> (const accessor descriptor), location: descriptor0x8f708003fad: [String] in ReadOnlySpace: #arguments: 0x08f708204365 <AccessorInfo> (const accessor descriptor), location: descriptor0x8f7080041f1: [String] in ReadOnlySpace: #caller: 0x08f7082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor0x8f7080050b1: [String] in ReadOnlySpace: #prototype: 0x08f708204475 <AccessorInfo> (const accessor descriptor), location: descriptor } - feedback vector: 0x8f708293715: [FeedbackVector] in OldSpace - map: 0x08f708002711 <Map> - length: 1 - sharedfunction 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 - instancesize: 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 heapobject (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0
需要注意的是程式碼在執行去最佳化的過程中會產生效能損耗,因此在日常的開發中,建議使用 TypeScript 對程式碼進行型別宣告,這樣可以一定程度提升程式碼的效能。

五  總結

本文對於 V8 的研究還處在一個感性的認知階段,並沒有深入到 V8 底層的原始碼。透過本文可以對 V8 的編譯原理有一個感性的認知,同時也建議大家可以使用 TypeScript,它確實能在一定程度上對 JavaScript 程式碼的編寫產生更好的指導作用。

E-MapReduce入門訓練營

本課程主要介紹阿里雲開源大資料平臺EMR的基礎知識體系。點選閱讀原文檢視詳情!

相關文章