Google V8 引擎淺析

前端開發人員都會遇到一個流行詞:V8。它的流行程度很大一部分是因爲它將 JavaScript 的性能提升到了一個新的水平。是的,V8 很快。但它是如何發揮它的魔力?

前言


源代碼:https://source.chromium.org/chromium/chromium/src/+/master:v8/ [1]
    在介紹 V8 引擎之前,我們可以先分析下爲什麼需要 V8 引擎?衆所周知,前端最火的開發語言非 javascript 莫屬,那 javascript 與 V8 是什麼樣的關係呢?
我們知道,計算機只能識別二進制的機器語言,無法識別更高級的語言,用高級的語言開發,需要先將這些語言翻譯成機器語言,而語言種類大體可以分爲解釋型語言和編譯型語言:

Gp4155

    JavaScript 就是一種解釋型語言,支持動態類型、弱類型、基於原型的語言,內置支持類型。一般 JavaScript 都是在前端側執行,需要能快速響應用戶,所以這就要求語言本身可以被快速地解析和執行,javascript 引擎就爲此目的而生。

JS 引擎歷史那些事

    1993 年網景瀏覽器誕生,成爲瀏覽器鼻祖。

    1995 年微軟推出了 IE 瀏覽器,拉開了第一次瀏覽器大戰的序幕。IE 受益於 windows 系統風靡世界,逐漸佔有了大部分市場。

    1998 年處於低谷的網景公司成立了 Mozilla 基金會,在該基金會推動下,開發了著名的開源項目 Firefox 並在 2004 年發佈 1.0 版本,拉開了第二次瀏覽器大戰的序幕,IE 發展更新較緩慢,Firefox 一推出就深受大家的喜愛,市場份額一直上升。

    2003 年,蘋果發佈了 Safari 瀏覽器,並在 2005 年釋放了瀏覽器中一種非常重要部件的源代碼,發起了一個新的開源項目 WebKit。

    2008 年,Google 以蘋果開源項目 WebKit 作爲內核,創建了一個新的項目 Chromium,在 Chromium 的基礎上,Google 發佈了 Chrome 瀏覽器。Google 工程師起初曾考慮過使用 Firefox 的 Gecko 內核,然而他們最終被 Android 團隊說服採用了 WebKit,他們勸說道:WebKit 輕快、易擴展、代碼結構清晰。而且在蘋果公司內部不停的速度壓榨的情況下,最終才發佈了 WebKit 並把它開源。https://www.google.com/googlebooks/chrome/med_14.html [2]

    站在當下回頭來看,當時的選擇無比的明智,現在 WebKit 更是出現在幾乎每一個移動平臺——iOS、Android、BlackBerry 等等。可惜的是,Google 加入了 WebKit 之列併成爲開發的主力後,獨立 fork 出了 Blink,自己的內核,又與 WebKit 分道揚鑣了。

        在 Blink 基礎之上,爲了追求 javascript 的極致速度和性能,Google 工程師又創造出來了 V8 引擎,而 Node 的作者認爲這麼優秀的引擎只在瀏覽器中跑可惜了,不如拿出來寫一些配套的模塊,就可以開發服務器端應用了,這就有了 Node.js,至此 javascript 語言從單純的前端語言,蛻變成了一門全端語言,從而有了全棧工程師的發展方向。

    微軟依然維護着自己的 EdgeHTML 引擎,作爲老的 Trident 引擎的替代方案。新的 Edge 的瀏覽器已經開始使用 Chromium 的 Blink 引擎了,當 Edge 加入 Blink 的陣營後,以 Webkit 和其衍生產品已經牢牢的佔有市場的 6 成以上。

什麼是 V8 引擎

    V8 引擎是一個 JavaScript 引擎實現,最初由一些語言方面專家設計,後被谷歌收購,隨後谷歌對其進行了開源。V8 使用 C++ 開發,在運行 JavaScript 之前,相比其它的 JavaScript 的引擎轉換成字節碼或解釋執行,V8 將其編譯成原生機器碼(IA-32, x86-64, ARM, or MIPS CPUs),並且使用瞭如內聯緩存(inline caching)等方法來提高性能。有了這些功能,JavaScript 程序在 V8 引擎下的運行速度媲美二進制程序。V8 支持衆多操作系統,如 windows、linux、android 等,也支持其他硬件架構,如 IA32,X64,ARM 等,具有很好的可移植和跨平臺特性。

V8 如何運行 Javascript

Loading

    js 文件加載的過程並不是由 V8 負責的,它可能來自於網絡請求、本地的 cache 或者是也可以是來自 service worker,瀏覽器的 js 加載的整個過程,就是 V8 引擎運行 js 的前置步驟。
3 種加載方式 & V8 的優化

    延伸閱讀:V8 6.6 進一步改進緩存性能 [3] 代碼緩存策略優化,簡單講就是從緩存代碼依賴編譯過程的模式,改變成兩個過程解耦,並增加了可緩存的代碼量,從而提升瞭解析和編譯的時間
【舊模式】

 【新模式】

Parsing

    Parsing(分析過程)是將 js 腳本轉換成 AST(抽象語法樹:Abstract Syntax Tree)的過程

詞法分析

Token

    從左往右逐個字符地掃描源代碼,通過分析,產生一個不同的標記,這裏的標記稱爲 token,代表着源代碼的最小單位,通俗講就是將一段代碼拆分成最小的不可再拆分的單元,這個過程稱爲詞法標記。詞法分析器常用的 token 標記種類有幾類:

TOKEN-TYPE TOKEN-VALUE\
-----------------------------------------------\
T_IF                 if\
T_WHILE              while\
T_ASSIGN             =\
T_GREATTHAN          >\
T_GREATEQUAL         >=\
T_IDENTIFIER name    / numTickets / ...\
T_INTEGERCONSTANT    100 / 1 / 12 / ....\
T_STRINGCONSTANT     "This is a string" / "hello" / ...
流式處理

    詞法處理過程中,輸入是字節流,輸出是 Token 流:

    定義 100 行單詞處理時間 1t,佔用內存 1m,來對比看下流式和非流式兩種方式的區別:

    可以發現流式處理能很大程度上提升處理效率和節省內存空間

詞法分析器結構


    掃描緩存區大小一般是固定的大小,但也無法確保能不影響單詞的邊界,同時會使用兩個指示器(指針),一個指向正在識別的單詞頭部,一個向前搜索單詞的終點。

    這裏有個概念是:超前搜索,用戶可能把關鍵字等特殊的語句重新定義,所以掃描器需要提前掃描到這些字後面的代碼格式,才能確定其最終代表的詞性。有這個問題存在會影響到性能,現在的大多數語言會把基本字都設置成保留字,用戶不能修改其含義。

有限狀態機

    掃描器依託有限狀態機來實現正則匹配不同的 token 類型,下面以識別整數和浮點數的狀態機爲例,簡單講下過程:
單圓代表着 “結點”,代表着掃描過程中可能出現的 “狀態”,也就是上面提到的兩個指示器中間對應的所有字符;
箭頭指向可稱爲 “邊”,從一個狀態指向另外一個狀態,邊的標號代表一個或者多個符號,如果能匹配一條邊,向前指針就會前移,指向下個狀態
Start 代表着開始狀態
雙圓環代表着接受狀態或者最終狀態,代表着已經找到了準確的狀態,向語法分析器返回一個 token 和相關的屬性值
“*” 代表着可能會識別到並不包含接受狀態的符號,可能指針需要回退一步或者多步

| 詞法單元 | 模式 | | --- | --- | | digit | [0-9] | | digits | digit+ | | number | digits(.digits)?(E[+-]?digits)? |

    23 狀態對應的是識別爲整數,24 狀態代表爲非科學計數法的浮點數,22 匹配的是科學計數法的浮點數(包含整數和小數部分),同時也有隻有整數部分的。狀態機會通過一個 state 參數來保存識別出來的編號(例如:0-24),然後通過 switch 來判斷 state 的值,實現不同狀態對應的執行動作。

狀態機是一個很有用的設計思想,在很多場景都有使用,大家可以下來好好學習下,React 的 state,Redux 的狀態管理等等。

在線 demo

Esprima: Parser[4] 在線 demo,僅做參考,v8 的解析的比這個複雜:https://v8.dev/blog/scanner [5]

語法分析

    語法分析是指根據某種給定的形式文法對由單詞序列構成的輸入文本,例如上個階段的詞法分析產物 - tokens stream,進行分析並確定其語法結構的過程。
**    抽象語法樹(Abstract Syntax Tree)** 是源代碼結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每個節點都表示源代碼中的一種結構。
    推薦這 2 個網站可以用來分析代碼生成的 ast 結構:Esprima: Parser[6]
https://resources.jointjs.com/demos/javascript-ast [7]
以下面代碼爲例,簡單分析下對應生成的 AST 樹形結構:

function f(a, b) {
let result = 0;
if(a > 0) {
result = a + b;
} else {
result = a - b;
}
return result
}



V8 會將語法分析的過程分爲兩個階段來執行:

Pre-parser
Full-parser

默認函數聲明後,沒有調用,對應的 pre-parser:

➜ Desktop d8 test.js --print-ast
[generating bytecode for function]
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . FUNCTION "f" = function f

在文件末尾增加 f(1, 2) 調用函數方法,就可以觸發 Full-parser 過程:

[generating bytecode for function: f]
--- AST ---
FUNC at 10
. KIND 0
. LITERAL ID 1
. SUSPEND COUNT 0
. NAME "f"
. PARAMS
. . VAR (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
. . VAR (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
. DECLS
. . VARIABLE (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
. . VARIABLE (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
. . VARIABLE (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
. BLOCK NOCOMPLETIONS at -1
. . EXPRESSION STATEMENT at 25
. . . INIT at 25
. . . . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
. . . . LITERAL undefined
. IF at 35
. . CONDITION at 40
. . . GT at 40
. . . . VAR PROXY parameter[0] (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
. . . . LITERAL 0
. . THEN at -1
. . . BLOCK at -1
. . . . EXPRESSION STATEMENT at 51
. . . . . ASSIGN at 58
. . . . . . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
. . . . . . ADD at 63
. . . . . . . VAR PROXY parameter[0] (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
. . . . . . . VAR PROXY parameter[1] (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
. . ELSE at -1
. . . BLOCK at -1
. . . . EXPRESSION STATEMENT at 83
. . . . . ASSIGN at 90
. . . . . . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
. . . . . . SUB at 94
. . . . . . . VAR PROXY parameter[0] (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
. . . . . . . VAR PROXY parameter[1] (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
. RETURN at 105
. . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
爲什麼要做兩次解析?

    如果僅有一次,那必須是 Full-parser,但這樣的話,大量未使用的代碼會消耗非常多的解析時間,結合具體的項目來看下:通過 Devtools-Coverage 錄製的方式可以分析頁面哪些代碼沒有用到:

    兩次解析的負面影響:如果部分代碼片段已經被 pre-parser 過了,那麼在執行的過程中還會經過一次 Full-parser,那總體耗時就是 0.5*parser + 1 * parser = 1.5parser。
下面羅列了不同種情況的代碼聲明對應的解析方法:

let a = 0; *// Top-Level 頂層的代碼都是 Full-Parsing*
*// 立即執行函數表達式 IIFE = Immediately Invoked Function Expression*
(function eager() {...})(); *// 函數體是 Full-Parsing*
*// 頂層的函數非IIFE*
function lazy() {...} *// 函數體是 Pre-Parsing*
lazy(); *// -> Full-Parsing 開始解析和編譯!*
*// 強制觸發Full-Parsing解析*
!function eager2() {...}function eager3() {...} *// full 解析*
let f1 = function lazy() { ... }; *// 函數體是 Pre-Parsing*
let f2 = function lazy() {...}(); *// 先觸發了pre 解析, 然後又full解析*

Bad case: 深度內嵌定義方法

function lazy_outer() { *// pre-parser*
function inner() {
function inner2() {
*// ...*
}
}
}
lazy_outer(); *// pre-parsing inner & inner2*
inner(); *// pre-parsing inner & inner2 (3rd time!)*
Parser 性能優化 Tips
  1. 使用 Devtools-Coverage 工具分析網頁無用代碼,並進行裁剪

  2. V8 會將 Parser 過程的結果緩存 72 個小時,當用到的腳本文件中有任何代碼修改了,那麼 parser 緩存的結果就失效了,所以好的實踐是將經常變動的代碼打包一起,非經常變動的代碼打包一起,這就有了第三方庫 dll 的方案。

  3. 可以考慮懶加載方案,當有一些代碼無需業務代碼執行的最開始需要的話,可以用異步加載的方法後續再加載

Interpret(解釋)


    解釋階段會將之前生成的 AST 轉換成字節碼,代碼會被編譯器編譯成從未被優化過的機器碼,在運行的過程中,會將需要優化的代碼進行熱點標記,再通過更高級的編譯器進行優化後再編譯。
    增加字節碼的好處是,並不是將 AST 直接翻譯成機器碼,因爲對應的 cpu 系統會不一致,翻譯成機器碼時要結合每種 cpu 底層的指令集,這樣實現起來代碼複雜度會非常高。

內存佔用

    最開始 V8 的設計中,運行 js 代碼後會將 AST 直接轉換成二進制的機器碼,由於執行機器碼的效率是非常高效的,所以這種方式在發佈後的一段時間內運行效果是非常好。

    但機器碼會存儲在內存中,退出進程後會存儲在磁盤上,但如果 js 源碼可能只有 1M,但轉換後的機器碼可能會多達幾十 M,過度佔用會導致性能大大降低。當手機越來變得普及後,內存問題就更加突出了。

Ignition 解釋器

    V8 團隊爲了解決這類性能問題,自己做出了 Ignition[8] 解釋器,在中間過程中增加了字節碼
    Ignition 解釋器轉換成的字節碼,比傳統的直接翻譯成機器碼節省了 25%-50% 的內存空間,同時爲了進一步節省,當字節碼生成後,AST 的數據就直接被廢棄掉了。在字節碼上又加上了一些元數據,例如記錄源代碼的位置和用於執行字節碼的處理方法等。

還是以上面的代碼爲例,看下 d8 下的字節碼

function add(x, y) {
var z = x + y;
return z;
}
add(1,2);
➜ Desktop d8 test.js --print-bytecode
[generated bytecode for function: (0x3ae908292f71 <SharedFunctionInfo>)]
Bytecode length: 28
Parameter count 1
Register count 4
Frame size 32
OSR nesting level: 0
Bytecode Age: 0
0x3ae908293032 @ 0 : 13 00 LdaConstant [0]
0x3ae908293034 @ 2 : c2 Star1
0x3ae908293035 @ 3 : 19 fe f8 Mov <closure>, r2
0x3ae908293038 @ 6 : 64 4f 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
0x3ae90829303d @ 11 : 21 01 00 LdaGlobal [1], [0]
0x3ae908293040 @ 14 : c2 Star1
0x3ae908293041 @ 15 : 0d 03 LdaSmi [3]
0x3ae908293043 @ 17 : c1 Star2
0x3ae908293044 @ 18 : 0d 04 LdaSmi [4]
0x3ae908293046 @ 20 : c0 Star3
0x3ae908293047 @ 21 : 62 f9 f8 f7 02 CallUndefinedReceiver2 r1, r2, r3, [2]
0x3ae90829304c @ 26 : c3 Star0
0x3ae90829304d @ 27 : a8 Return
Constant pool (size = 2)
0x3ae908293001: [FixedArray] in OldSpace
- map: 0x3ae908002205 <Map>
- length: 2
0: 0x3ae908292fb9 <FixedArray[2]>
1: 0x3ae908003f85 <String[3]: #add>
Handler Table (size = 0)
Source Position Table (size = 0)
[generated bytecode for function: add (0x3ae908292fc9 <SharedFunctionInfo add>)]
Bytecode length: 7
Parameter count 3
Register count 1
Frame size 8
OSR nesting level: 0
Bytecode Age: 0
0x3ae908293156 @ 0 : 0b 04 Ldar a1
0x3ae908293158 @ 2 : 38 03 00 Add a0, [0]
0x3ae90829315b @ 5 : c3 Star0
0x3ae90829315c @ 6 : a8 Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 0)

    V8 在執行字節碼的過程中,使用到了通用寄存器累加寄存器,函數參數和局部變量保存在通用寄存器裏面,累加器中保存中間計算結果,在執行指令的過程中,如果直接由 cpu 從內存中讀取數據的話,比較影響程序執行的性能,使用寄存器存儲中間數據的設計,可以大大提升 cpu 執行的速度。
    這裏麪包含了很多編譯原理裏面涉及到的指令集。
    字節碼更多指令可以看下 V8-Ignition 的源碼 [9]

參考文檔:Google Ignition ppt[10]

編譯器

    這個過程主要指是 V8 的 TurboFan 編譯器將字節碼翻譯成機器碼的過程。
字節碼配合解釋器和編譯器這一技術設計,可以稱爲 JIT,即時編譯技術,java 虛擬機也是類似的技術,解釋器在解釋執行字節碼時,會收集代碼信息,標記一些熱點代碼熱點代碼 (hotspot) 就是一段代碼被重複執行多次,TurboFan 會將熱點代碼直接編譯成機器碼,緩存起來,下次調用直接運行對應的二進制的機器碼,加速執行速度。****

**TurboFan** 的整體優化過程,可參見下圖,這裏的優化分爲了 3 層,更偏向於系統底層

在** TurboFan** 將字節碼編譯成機器碼的過程中,還進行了簡化處:常量合併、強制折減、代數重新組合。

類型推斷(Speculative Optimization)是 TurboFan 的一大核心能力,

Execution(執行)

    在 Javascript 的執行過程中,經常遇到的就是對象屬性的訪問。作爲一種動態的語言,在 js 中,一行簡單的屬性訪問可能包含着複雜的語義: Object.xxx 的形式,可能是屬性的直接訪問,也可能調用的對象的 Getter 方法,還有可能是要通過原型鏈往上層對象中查找。
    這種不確定性而且動態判斷的情況,會浪費很多查找時間,降低運行的速度,V8 中會把第一次分析的結果放在緩存中,當再次訪問相同的屬性時,會優先從緩存中去取,調用 GetProperty(Object, "xxx", feedback_cache) 的方法獲取緩存,如果有緩存結果,就會跳過查找過程。

Object Shapes

    在靜態語言中,代碼執行前要先進行編譯,編譯的時候,每個對象的屬性都是固定的, 直接可以通過記錄某個屬性相對該對象的地址的偏移量,就可直接讀取到屬性值, 而在動態語言中,對象的屬性是會被實時改動的,能否可以借鑑靜態語言的這種特點來設計呢?
    V8 加入了 Object Shapes 或者叫做 Hidden Class(隱藏類) 的概念。V8 會給每個對象創建一個隱藏類,裏面記錄了對象的一些基本信息:

    有了屬性名和地址的偏移量,當訪問對象的某個屬性時,就可以直接從內存中讀取到,不需要再經過一系列的查找,大大提升了 V8 訪問對象時的效率。以代碼爲例:

let demoObj = { a:1, b:2 }; %DebugPrint(demoObj); // d8內部api

執行 d8 的調試命令查看對應的隱藏類結構:

➜ Desktop d8 --allow-natives-syntax test2.js
DebugPrint: 0x2ef2081094b5: [JS_OBJECT_TYPE]
- map: 0x2ef2082c78c1 <Map(HOLEY_ELEMENTS)[FastProperties] //隱藏類地址
- prototype: 0x2ef208284205 <Object map = 0x2ef2082c21b9> // 原型鏈
- elements: 0x2ef20800222d <FixedArray[0][HOLEY_ELEMENTS] // elements 和 properties 快慢屬性相關
- properties: 0x2ef20800222d <FixedArray[0]>
- All own properties (excluding elements){
0x2ef20808ecf9: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
0x2ef20808ed95: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object
}
0x2ef2082c78c1: [Map] // 對應的隱藏類
- type: JS_OBJECT_TYPE
- instance size: 20
- inobject properties: 2
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x2ef2082c7899 <Map(HOLEY_ELEMENTS)>
- prototype_validity cell: 0x2ef208202405 <Cell value= 1>
- instance descriptors (own) #2: 0x2ef2081094e5 <DescriptorArray[2]>
- prototype: 0x2ef208284205 <Object map = 0x2ef2082c21b9>
- constructor: 0x2ef208283e3d <JSFunction Object (sfi = 0x2ef208209071)>
- dependent code: 0x2ef2080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

隱藏類的複用

    現在我們清楚每個對象都有一個 map 屬性,指向的是一個隱藏類,如果兩個相同形狀的對象,在 V8 中會複用同一個隱藏類,這樣會減少創建隱藏類的次數,加快 V8 的執行速度,同時也會減少隱藏類佔用的內存空間,相同形狀的定義:

let demoObj = { a:1, b:2 }; 
let demoObj2 = { a:100, b:200 }; 
%DebugPrint(demoObj); 
%DebugPrint(demoObj2);


    當對象的隱藏類創建完成後,一旦對象發生形狀上的改變:增加新的屬性或者是刪除舊的屬性時,隱藏類就會重新被創建,這個動作是 V8 執行過程中的一筆開銷。所以這裏的代碼優化建議

  1. 儘量創建形狀一致的對象,屬性的順序、屬性的 key 值、key 值的個數儘量保持一致

  2. 儘量不要後面再加入臨時屬性,聲明對象時屬性完整,因爲每次加入屬性,破壞了原有的對象形狀,隱藏類要重新創建

  3. 儘量不要使用 delete 刪除對象屬性,同理於上條原理,會破壞對象的形狀

總結

    V8 引擎經歷了多年的不斷完善和新技術的演進,裏面涵蓋了太多的技術點和設計思想,本次分享主要是從 js 腳本內容下載到最終在 V8 引擎執行的過程進行分析,來體會一下 V8 的獨到之處,和它不斷追求極致性能的思想,加深對 V8 的理解,也能更好地寫出高效的代碼,而且很多優化思想也可以在其他的語言或框架中得到印證,還有很多比較深入的知識點,例如:內聯屬性策略、反饋向量機制、快慢屬性等,後續的分享文章中會進一步解析,歡迎大家不吝指正和探討,一起探究 v8 引擎背後絕妙的設計思想。

參考文檔

瀏覽器工作原理 - webkit 內核研究 [11]
Using d8 · V8[12]
V8 JavaScript engine[13] 官網
認識 V8 引擎 [14]
[譯] Blink 內核是如何工作的?[15]
詞法分析 -- 手動詞法單元的識別 (狀態轉換圖、KMP 算法)[16]
v8 parser JS[17]
利用 V8 深入理解 JavaScript 設計 [18]
[譯] V8 引擎中基於推測的優化介紹 [19]
JS 引擎工作原理詳解 [20]

❤️ 謝謝支持


以上便是本次分享的全部內容,希望對你有所幫助 ^_^

喜歡的話別忘了 分享、點贊、收藏 三連哦~。

歡迎關注公衆號 ELab 團隊 收貨大廠一手好文章~

我們來自字節跳動,是旗下大力教育前端部門,負責字節跳動教育全線產品前端開發工作。

我們圍繞產品品質提升、開發效率、創意與前沿技術等方向沉澱與傳播專業知識及案例,爲業界貢獻經驗價值。包括但不限於性能監控、組件庫、多端技術、Serverless、可視化搭建、音視頻、人工智能、產品設計與營銷等內容。

字節跳動校 / 社招內推碼: 86NHHY1

投遞鏈接: https://jobs.toutiao.com/s/dQ2dogm

參考資料

[1]

https://source.chromium.org/chromium/chromium/src/+/master:v8/ : https://source.chromium.org/chromium/chromium/src/+/master:v8/

[2]

https://www.google.com/googlebooks/chrome/med_14.html : https://www.google.com/googlebooks/chrome/med_14.html

[3]

V8 6.6 進一步改進緩存性能: https://zhuanlan.zhihu.com/p/36183010

[4]

Esprima: Parser: https://esprima.org/demo/parse.html#

[5]

https://v8.dev/blog/scanner : https://v8.dev/blog/scanner

[6]

Esprima: Parser: https://esprima.org/demo/parse.html#

[7]

https://resources.jointjs.com/demos/javascript-ast : https://resources.jointjs.com/demos/javascript-ast

[8]

Ignition: https://v8.dev/docs/ignition

[9]

源碼: https://source.chromium.org/chromium/chromium/src/+/master:v8/src/interpreter/interpreter-generator.cc

[10]

Google Ignition ppt: https://docs.google.com/presentation/d/1HgDDXBYqCJNasBKBDf9szap1j4q4wnSHhOYpaNy5mHU/edit#slide=id.g1357e6d1a4_0_58

[11]

瀏覽器工作原理 - webkit 內核研究: https://juejin.cn/post/6844903569200513037

[12]

Using d8 · V8: https://v8.dev/docs/d8

[13]

V8 JavaScript engine: https://v8.dev/

[14]

認識 V8 引擎: https://zhuanlan.zhihu.com/p/27628685

[15]

[譯] Blink 內核是如何工作的?: https://juejin.cn/post/6844904143279095822

[16]

詞法分析 -- 手動詞法單元的識別 (狀態轉換圖、KMP 算法): https://wangwangok.github.io/2019/10/28/compiler_stateGraph_kmp/

[17]

v8 parser JS: https://mlib.wang/2020/02/08/v8-parser-compiler-javascript/

[18]

利用 V8 深入理解 JavaScript 設計: https://segmentfault.com/a/1190000040064998

[19]

[譯] V8 引擎中基於推測的優化介紹: https://zhuanlan.zhihu.com/p/51047561

[20]

JS 引擎工作原理詳解: https://juejin.cn/post/6988458924630835231

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4nSZprltPcVV3yEl5Hawiw