V8 引擎如何執行JS,之前看過 Webkit 技術內幕,也只是走馬觀花。並沒有深入理解,突然看到這篇文章,翻譯之How does the Google V8 engine work?
Google V8
引擎是如何工作的?這是一個非常好的問題,這裡有少許流出的官方文件來講解,到底 V8
內部都做了什麼。我會把我知道的東西分享給你(你需要自己猜,哪部分我給拿掉了),還有很多有用的地址去幫助你明白這些內容。
V8
有兩個編譯器:
- 一個非常簡單並且非常快的編譯器用於將
js
編譯成簡單但是很慢的機械碼,叫做full-codegen
。 - 另一個是非常複雜的實時優化編譯器,編譯高效能的可執行程式碼,叫做
Crankshaft
。
V8
裡面也使用了一些執行緒:
- 主執行緒,做你希望它做的事情:載入你的程式碼,編譯它們,執行他們。
- 有一個獨立的編譯執行緒,當主執行緒執行的時候,它去優化程式碼。
- 一個
profiler
執行緒(不知道還有沒有了,但是會有一個同樣職責的執行緒存在),用於發現執行過程中哪個方法耗費了大量時間,這樣Crankshaft
就可以優化這些程式碼。 - 一些用於
GC
處理的執行緒(譯者注:這裡是一些用於GC
的執行緒,不止一個執行緒用於垃圾回收)。
最開始執行你的程式碼的時候,V8
開始使用 full-codegen
,full-codegen
直接將 JS
程式碼解釋成機械碼,沒有做任何轉化。這可以讓 V8
快速執行機械碼。注意,V8
並不使用中間位元組碼,因此也就不再需要轉譯處理。
當你的程式碼被執行的時候,profiler
執行緒有足夠的資料來找出哪些方法需要被優化。我不確定 V8
如何選擇使用哪個執行緒做的優化,簡單起見,我們就認為它用的主執行緒吧。
主執行緒通過停止正在執行的程式碼(也許就在需要優化的方法這裡),開始使用 Crankshaft
進行優化。JS
程式碼首先會被編譯成一種叫做 Hydrogen
的高階描述,它是控制流圖的靜態單賦值表示。大多數優化都是在這個級別完成的。
首先,對儘可能多的程式碼進行內聯,這使得優化變得更有意義。然後進行型別轉化。這個優化移除了打包和拆包的處理,可以認為是執行了很多指令。這意味著,如果你的程式碼在操作整數,並且沒有做型別轉換,比如轉換成 string
,double
這些,那麼它會跑的很快。內聯快取會在這個階段起到非常重要的作用,提供了型別判斷。就像你猜到的那樣,我們需要小心型別轉換:如果你希望一個變數是一個整數,但是過一會卻被修改成了其它型別,那麼你的假設就失敗了,那麼一次重新編譯就在所難免了。還有其它的優化,比如 loop-invariant code motion
(譯者注:貌似是講將迴圈內不變化的程式碼移到外面,減少每一次迴圈執行的程式碼數量),移除死程式碼(譯者注:不被執行的程式碼也要移除,否則 V8
始終都要要對這些程式碼處理的,帶來了額外負擔)等。
一旦 Hydrogen graph
被優化,Crankshaft
會降低它到一個低階別的描述,叫做 Lithium
。大部分的 Lithium
執行於特定架構。分配暫存器就是在這個級別進行的。
最終,Lithium
被編譯成機械碼。然後一些叫做 OSR
(on-stack replacement
)的事情就發生了。記住,在我們開始編譯和優化執行耗時較長的方法之前,我們喜歡先執行它。我們不要忘記我們剛剛放慢了執行,然後開始執行優化後的程式碼。相反,我們將要轉換所有的上下文,因此我們才能在執行的中間過程中選擇執行優化後的程式碼。我讓你們感到複雜,提醒一下,其它的優化中,我們內聯了一些東西。V8
並不是唯一一個這麼做的虛擬機器,但是我發現一些比較瘋狂的地方。有一些保護機制叫 — 去優化,在做相反的事情,並且會在一些假設的特定情況下反轉一些優化後的程式碼。
還有。就是編譯/執行部分。我忘了提到 GC
,不過這很短,因為我對它不太瞭解。
對於垃圾收集來說,V8
使用了傳統的方法,採用標記計數的方式來進行垃圾收集。標記階段必須停止 JavaScript
執行。為了控制 GC
成本,使執行更加穩定,V8
採用增量標記。這就是說,他們不是在堆中試圖示記每一個可能的物件,而是處理一部分堆,然後恢復正常的執行。下一個 GC
停止執行程式碼的時候處理之前未處理的堆。這允許非常短的暫停。如前所述,掃描階段由單獨的執行緒來處理。
floitsch.blogspot.de/2012/04/opt…
還有這裡原始碼