V8 Design Elements(翻譯)

最近開始學習V8 Javascript引擎,這篇文章是翻譯官方文檔的,解釋了V8之因此快的主要緣由等,原文請參見http://code.google.com/apis/v8/design.htmlhtml

V8是一個新的爲了提升Javascript程序速度的Javascript引擎,在多項測試中,V8的速度比JScript (in Internet Explorer), SpiderMonkey (in Firefox), and JavaScriptCore (in Safari)都要快不少倍,若是你的JS網絡應用程序正受制於JS引擎的速度,那麼使用V8而不是你如今使用的JS引擎將能夠大幅改善你的應用程序的表現。至於提高的幅度則由JS所佔的比例以及JS的結構(nature of JS)等多方面因素決定,例如,若是一個函數在你的應用中將會被一次次重複運行,那麼提高的幅度將會比不少函數都在你的應用中只運行一次要大不少,至於會這樣的緣由將會在後面的文章中解釋。 編程

V8速度提高的三個主要方面爲: api

  • Fast Property Access (快速的屬性訪問)
  • Dynamic Machine Code Generation (動態生成機器碼)
  • Efficient Garbage Collection (高效的垃圾回收)


快速的屬性訪問

JS是一種動態語言,對象的屬性(property)能夠在運行時被動態的添加和刪除,這意味着對象的屬性頗有可能被改變,絕大多數JS引擎採用一個字典型的數據結構來存儲對象的屬性,每一次屬性的訪問都須要一次動態的查詢以得到屬性在內存中的位置,這樣的訪問方法使得JS中的屬性訪問要比普通編程語言例如Java和Smalltalk中的對象訪問要慢不少,在這些(普通的)編程語言中,對象的屬性值根據類的固定結構,被編譯器放在離對象指針有固定的偏移值的內存位置上,屬性的訪問只是一次簡單的內存讀取和寫入,一般只須要一個(彙編)指令。 緩存

爲了減小訪問JS屬性的時間,V8沒有采用動態查詢的方式,V8會在背後動態的建立隱藏類,在V8中,當對象的屬性改變時,對象會更改隱藏類的指向。這種方式的基本思想並非被創新出來的,在prototype-based的編程語言Self中也作了相似的事情,詳見An Efficient Implementation of Self, a Dynamically-Typed Object-Oriented Language Based on Prototypes網絡

咱們經過舉例來更加清楚的瞭解整個過程。請看下面這個很簡單的JS函數。 數據結構

function Point(x, y) {
    this.x = x;
    this.y = y; 
}

當new Point(x, y)被執行的時候,一個新的Point對象會被建立,當V8第一次執行這個函數的時候,V8會爲Point建立一個初始的隱藏類,爲方便,咱們假設這個類叫C0,顯然這個類裏面什麼都沒有,這個時候,Point這個對象的隱藏類是C0。 app

當執行Point中的第一個語句(this.x = x;),會在Point對象中建立一個新的屬性x,對於V8而言,會作以下事情: 編程語言

  • 建立另一個隱藏類C1,繼承自C0,同時在C1中加入屬性x,x的值會被存在離Point對象偏移0的地方。
  • 更新C0的描述,添加一個類轉移,之後,若是一個隱藏類指向C0的對象中加入了x屬性,那麼這個對象的隱藏類將會指向C1(這樣能夠避免每次加入新屬性的時候都須要從新建立一個新的隱藏類,若是咱們第二次調用Point函數,就不會再次建立C1和C0類,而會直接使用此次建立的,這個也說明了爲何若是一個函數在應用中被重複運行的話,那麼速度的提高就會很大)。在這個時候,Point對象的隱藏類指向C1


當執行Point中的第二個語句(this.y = y;)的時候,將會在Point對象中建立一個新的屬性y,對於V8而言,會作以下事情: ide

  • 建立一個新的隱藏類C2,繼承自C1,同時對C2加入一個屬性y,存儲在離Point對象便宜1的內存位置上。
  • 更新C1的描述,添加一個類轉移,之後,若是一個隱藏類指向C1的對象中加入了y屬性,那麼這個對象的隱藏類將會指向C2.

每次有一個屬性被添加的時候就要從新建立一個隱藏類的作法看起來好像很低效,可是,由於類轉移的存在,隱藏類能夠被複用(如我上面所解釋)。儘管JS比其餘面向對象的編程語言都要更加動態,可是採用上面的方法,經過觀察不少JS程序的運行,它們在很大程度上都重用了以前的結構(the runtime behavior of most JS programs results in a high degree of structure-sharing using the above approach). 利用隱藏的方法有兩個好處:屬性的訪問再也不須要一個字典查詢,同時可讓V8使用一些在以類爲基礎的普通編程語言中能夠用的優化方法,例如inline caching(參考 Efficient Implementation of the Smalltalk-80 System)。 函數

動態生成機器碼

V8會JS代碼第一次運行的時候將其直接編譯爲機器碼,在V8中沒有中間字節碼,也就沒有解釋器,屬性的訪問由inline cache來完成,但這些代碼可能會在V8執行期間被更改成別的機器指令。

當第一次執行訪問某個對象的某個屬性的代碼的時候,V8決定這個對象如今的隱藏類,同時會進行以下優化,V8假設當前代碼塊中的對這個對象的全部的屬性訪問都會使用這個隱藏類,並根據這個假設修改inline cache的代碼,直接使用這個隱藏類(跳過查詢隱藏類的步驟),若是V8的假設是正確的,那麼屬性值的讀取和賦值只需一個指令便可完成,若是假設錯誤,那麼V8再次修改代碼,移除這一優化。

舉例,JS中訪問一個Point對象的x屬性的代碼爲

point.x

在V8中,相對應的機器碼爲

# ebx = the point object
cmp [ebx,<hidden class offset>],<cached hidden class>
jne <inline cache miss>
mov eax,[ebx, <cached x offset>]

若是這個對象的隱藏類不符合緩存代碼中的隱藏類,執行將會跳轉到V8運行系統中處理inline cache失敗的地方並修改inline cache的代碼,若是找到了這個屬性,那麼x的值就會直接被獲得。

當有許多對象共享同一個隱藏類的時候,這樣的方法可以使得JS的屬性訪問速度和大部分靜態語言的訪問速度相仿,使用隱藏類並經過inline cache代碼來訪問屬性,同時優化機器碼的生成,V8可以大幅優化大部分JS代碼的執行效率。

高效的垃圾回收

V8會將再也不被引用的內存進行回收,這個過程一般被稱之爲垃圾回收,爲了保證快速的對象生成,縮短垃圾回收所形成的暫停,而且防止內存碎片的產生,V8的垃圾回收器使用了以下原則:stop-the-world, generational, accurate。具體來講:

  • 當處在垃圾回收循環中時,中止程序的執行
  • 在大多數垃圾回收循環中僅僅處理一部分的堆內存,這樣作最小化了程序中止所帶來的影響
  • 永遠知道全部對象和指針在內存中的位置,這個避免了不正確的垃圾回收器中廣泛存在的內存泄露的問題。

在V8中,對象堆內存被分爲兩部分,用於新對象建立的新的內存空間,用於存放在垃圾回收週期中存留下來的對象,若是一個對象在垃圾回收中被移動了,V8會更新全部指向改對象的指針。

相關文章
相關標籤/搜索