V8 Design Elements(翻译)
為什么80%的碼農都做不了架構師?>>> ??
最近開始學習V8 Javascript引擎,這篇文章是翻譯官方文檔的,解釋了V8之所以快的主要原因等,原文請參見http://code.google.com/apis/v8/design.html。
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速度提升的三個主要方面為:
- 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。
當執行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而言,會做如下事情:
- 創建一個新的隱藏類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會更新所有指向改對象的指針。
轉載于:https://my.oschina.net/sub/blog/152628
總結
以上是生活随笔為你收集整理的V8 Design Elements(翻译)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 企业过快移向MBaaS很危险
- 下一篇: BZOJ1083: [SCOI2005]