V8 引擎是如何工做的?

V8 引擎是如何工做的?

本文翻譯自:How the V8 engine works?javascript

​ V8是谷歌德國開發中心構建的一個JavaScript引擎。它是由C++編寫的開源項目,同時被客戶端(谷歌瀏覽器)和服務器端(Node.js)應用使用。java

​ V8最初是爲了提升web瀏覽器中的JavaScript運行性能設計的。爲了提高性能,V8將JavaScript代碼翻譯爲更高效的機器語言,而不是使用解釋程序。它經過實現一個JIT(Just-In-Time,即時)編譯器來將JavaScript代碼編譯爲機器語言,就像不少現代JavaScript引擎如SpiderMonkey或Rhino(Mozilla)作的那樣。V8和它們主要的區別是它不會生成字節碼或其餘中間代碼。git

​ 本篇文章主要目的是展現和理解V8是如何爲了生成優化代碼工做的(爲了客戶端或服務器端應用)。若是你有過"我應該在意JavaScript的性能麼?"這樣的疑惑,我會引用Daniel Clifford (V8團隊的研發組長與經理)的這句話來回答你:github

它不只僅是爲了讓你如今的應用運行得更快,它是爲了將不可能變爲可能。web

v8引擎

Hidden class

​ JavaScript是一個基於原型的語言:在使用克隆進程的時候,並不會產生類和對象。JavaScript仍是動態類型的:類型和類型信息並不明確,對象的屬性也能夠動態的添加或刪除。如何高效地訪問類型和屬性是V8的第一個大的挑戰。並不像大多數JavaScript引擎作的那樣使用類字典數據結構存儲對象屬性並動態查找屬性位置,V8在運行時建立hidden classes(隱藏類)來生成一個內部的類型系統表示和提升屬性訪問速度。數組

舉個例子,咱們建立一個Point構造函數和兩個Point對象:瀏覽器

hidden class

若是佈局相同,以這個例子爲例,pq 屬於相同的V8建立的hidden class。這還顯示出了使用hidden classes的另外一個優勢:它讓V8能夠將屬性相同的對象劃爲一組。在這裏 pq 使用相同的優化代碼緩存

如今假設咱們想要在聲明以後再給 q 對象添加一個 z 屬性(這對動態類型語言來講很正常)。服務器

V8會如何處理這種狀況?實際上,V8在每一次構建函數聲明新的屬性是都會建立一個新的hidden class,並持續跟蹤hidden class 的變化。爲何?由於若是建立了兩個對象(pq),在建立後第二個對象q又被添加了一個屬性,V8須要在保持上一個建立的hidden class(爲第一個對象 p 建立) 的同時,爲新的動態添加的屬性建立一個新的hidden class(爲第二個對象 q 建立)。數據結構

transition

每次建立一個新的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?
相關文章
相關標籤/搜索