本文翻譯自: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
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:所以儘可能編寫單態代碼而不是多態代碼。
爲了更高效地描述數和JavaScript對象,在V8中,二者均使用32位值表示。其中1位表示這個值是對象(flag = 1
)仍是數(flag = 0
)。若是一個數比31位大,V8會把它轉換爲double存儲在新建的一個對象中。
代碼優化:若是可能的話,儘可能使用31位有符號數,來減小上述的高代價的操做。
V8使用兩種不一樣的方法來操做數組:
[1,2,4,5,8]
)[1,2,,5,8]
)代碼優化:儘可能使用 V8 會使用快速元素方法來操做的數組。即減小使用鍵不是遞增數的數組的使用。同時,儘可能避免預分配大數組。在使用中讓它本身慢慢增長會更好。同時,不要刪除數組中的元素:它會使得數組稀疏。
V8有兩個編譯器:
代碼優化:V8一樣支持去優化:優化編譯器根據從內聯緩存中獲取的類型信息進行優化,當此優化後有問題時會去優化。例如,若是生成的hidden class不是指望的那樣,V8會拋棄優化代碼,返回到完整編譯器生成的代碼,並從內聯緩存中從新獲取類型。這個過程很慢,所以應儘可能在函數被優化後不去修改它。