V8 引擎是如何工作的?
V8 引擎是如何工作的?
本文翻譯自:How the V8 engine works?
? V8是谷歌德國開發中心構建的一個JavaScript引擎。它是由C++編寫的開源項目,同時被客戶端(谷歌瀏覽器)和服務器端(Node.js)應用使用。
? V8最初是為了提高web瀏覽器中的JavaScript運行性能設計的。為了提升性能,V8將JavaScript代碼翻譯為更高效的機器語言,而不是使用解釋程序。它通過實現一個JIT(Just-In-Time,即時)編譯器來將JavaScript代碼編譯為機器語言,就像很多現代JavaScript引擎如SpiderMonkey或Rhino(Mozilla)做的那樣。V8和它們主要的區別是它不會生成字節碼或其他中間代碼。
? 本篇文章主要目的是展示和理解V8是如何為了生成優化代碼工作的(為了客戶端或服務器端應用)。如果你有過"我應該在乎JavaScript的性能么?"這樣的疑惑,我會引用Daniel Clifford (V8團隊的研發組長與經理)的這句話來回答你:
它不僅僅是為了讓你現在的應用運行得更快,它是為了將不可能變為可能。
Hidden class
? JavaScript是一個基于原型的語言:在使用克隆進程的時候,并不會產生類和對象。JavaScript還是動態類型的:類型和類型信息并不明確,對象的屬性也可以動態的添加或刪除。如何高效地訪問類型和屬性是V8的第一個大的挑戰。并不像大多數JavaScript引擎做的那樣使用類字典數據結構存儲對象屬性并動態查找屬性位置,V8在運行時創建hidden classes(隱藏類)來生成一個內部的類型系統表示和提高屬性訪問速度。
舉個例子,我們創建一個Point構造函數和兩個Point對象:
如果布局相同,以這個例子為例,p 和 q 屬于相同的V8創建的hidden class。這還顯示出了使用hidden classes的另一個優點:它讓V8可以將屬性相同的對象劃為一組。在這里 p 和 q 使用相同的優化代碼。
現在假設我們想要在聲明之后再給 q 對象添加一個 z 屬性(這對動態類型語言來說很正常)。
V8會如何處理這種情況?實際上,V8在每一次構建函數聲明新的屬性是都會創建一個新的hidden class,并持續跟蹤hidden class 的變化。為什么?因為如果創建了兩個對象(p 和 q),在創建后第二個對象q又被添加了一個屬性,V8需要在保持上一個創建的hidden class(為第一個對象 p 創建) 的同時,為新的動態添加的屬性創建一個新的hidden class(為第二個對象 q 創建)。
每次創建一個新的hidden class時,前一個hidden class都進行一次類轉換更新,指示要使用哪個hidden class而不是它。
代碼優化
因為V8為每一個屬性創建一個新的hidden class,hidden class的創建應該盡量少。因此,我們需要盡量避免在對象創建后添加屬性,同時以相同的順序初始化對象成員(來減少hidden classes的不同樹的創建)。
單態操作(Monomorphic operations)指只在對象上的使用相同hidden class的操作。V8在我們調用函數時會新建一個hidden class。如果我們使用不同的參數類型再次調用此函數,V8需要創建另一個hidden class:因此盡量編寫單態代碼而不是多態代碼。
V8優化JavaScript代碼的更多例子
切換值
為了更高效地描述數和JavaScript對象,在V8中,兩者均使用32位值表示。其中1位表示這個值是對象(flag = 1)還是數(flag = 0)。如果一個數比31位大,V8會把它轉換為double存儲在新建的一個對象中。
代碼優化:如果可能的話,盡量使用31位有符號數,來減少上述的高代價的操作。
數組
V8使用兩種不同的方法來操作數組:
快速元素(Fast elements):為無間隙的密集數組設計。它們使用線性存儲緩存,使得訪問非常高效。([1,2,4,5,8])
字典元素(Dictionary elements):為有間隙的稀疏數組設計。使用哈希表緩存,較之快速元素訪問代價更高。([1,2,,5,8])
代碼優化:盡量使用 V8 會使用快速元素方法來操作的數組。即減少使用鍵不是遞增數的數組的使用。同時,盡量避免預分配大數組。在使用中讓它自己慢慢增加會更好。同時,不要刪除數組中的元素:它會使得數組稀疏。
V8如何編譯JavaScript代碼?
V8有兩個編譯器:
一個是"完整"編譯器,可以編譯任何的JavaScript代碼:編譯結果為好的代碼但不是好的JIT代碼。這個編譯器的目的就是快速生成代碼。為了實現這個目的,它不會進行任何的類型分析,因此它對類型一無所知。相反的,它使用內聯緩存(Inline Caches)策略來在程序運行中精煉類型信息。內聯緩存非常高效,帶來了20倍的速度提升。
另一個是優化編譯器,可以編譯大多數JavaScript代碼,生成更好的代碼。它出現的更晚,并對熱函數進行了重編譯。優化編譯器從內聯緩存中獲取類型并決定如何對代碼進行優化。然而,有些語言特性并沒有被支持或可能會拋出錯誤。(應對方法是使用try catch)
代碼優化:V8同樣支持去優化:優化編譯器根據從內聯緩存中獲取的類型信息進行優化,當此優化后有問題時會去優化。例如,如果生成的hidden class不是期望的那樣,V8會拋棄優化代碼,返回到完整編譯器生成的代碼,并從內聯緩存中重新獲取類型。這個過程很慢,因此應盡量在函數被優化后不去修改它。
參考資料
Google I/O 2012 “Breaking the JavaScript Speed Limit with V8” with Daniel Clifford, tech lead and manager of the V8 team: video and slides.
V8: an open source JavaScript engine: video of Lars Bak, V8 core engineer.
Nikkei Electronics Asia blog post: Why Is the New Google V8 Engine So Fast?
總結
以上是生活随笔為你收集整理的V8 引擎是如何工作的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 乡镇户口无房可以申请修房子吗?
- 下一篇: 坦克争锋购买几级坦克后会有兵的礼包出现