最近開(kāi)始學(xué)習(xí)V8 Javascript引擎,這篇文章是翻譯官方文檔的,解釋了V8之所以快的主要原因等,原文請(qǐng)參見(jiàn)http://code.google.com/apis/v8/design.html。
V8是一個(gè)新的為了提高Javascript程序速度的Javascript引擎,在多項(xiàng)測(cè)試中,V8的速度比JScript (in Internet Explorer), SpiderMonkey (in Firefox), and JavaScriptCore (in Safari)都要快很多倍,如果你的JS網(wǎng)絡(luò)應(yīng)用程序正受制于JS引擎的速度,那么使用V8而不是你現(xiàn)在使用的JS引擎將可以大幅改善你的應(yīng)用程序的表現(xiàn)。至于提升的幅度則由JS所占的比例以及JS的結(jié)構(gòu)(nature of JS)等多方面因素決定,例如,如果一個(gè)函數(shù)在你的應(yīng)用中將會(huì)被一次次重復(fù)運(yùn)行,那么提升的幅度將會(huì)比很多函數(shù)都在你的應(yīng)用中只運(yùn)行一次要大很多,至于會(huì)這樣的原因?qū)?huì)在后面的文章中解釋。
V8速度提升的三個(gè)主要方面為:
JS是一種動(dòng)態(tài)語(yǔ)言,對(duì)象的屬性(property)可以在運(yùn)行時(shí)被動(dòng)態(tài)的添加和刪除,這意味著對(duì)象的屬性很有可能被改變,絕大多數(shù)JS引擎采用一個(gè)字典型的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)對(duì)象的屬性,每一次屬性的訪問(wèn)都需要一次動(dòng)態(tài)的查詢(xún)以獲得屬性在內(nèi)存中的位置,這樣的訪問(wèn)方法使得JS中的屬性訪問(wèn)要比普通編程語(yǔ)言例如Java和Smalltalk中的對(duì)象訪問(wèn)要慢很多,在這些(普通的)編程語(yǔ)言中,對(duì)象的屬性值根據(jù)類(lèi)的固定結(jié)構(gòu),被編譯器放在離對(duì)象指針有固定的偏移值的內(nèi)存位置上,屬性的訪問(wèn)只是一次簡(jiǎn)單的內(nèi)存讀取和寫(xiě)入,通常只需要一個(gè)(匯編)指令。
為了減少訪問(wèn)JS屬性的時(shí)間,V8沒(méi)有采用動(dòng)態(tài)查詢(xún)的方式,V8會(huì)在背后動(dòng)態(tài)的創(chuàng)建隱藏類(lèi),在V8中,當(dāng)對(duì)象的屬性改變時(shí),對(duì)象會(huì)更改隱藏類(lèi)的指向。這種方式的基本思想并不是被創(chuàng)新出來(lái)的,在prototype-based的編程語(yǔ)言Self中也做了類(lèi)似的事情,詳見(jiàn)An Efficient Implementation of Self, a Dynamically-Typed Object-Oriented Language Based on Prototypes。
我們通過(guò)舉例來(lái)更加清楚的了解整個(gè)過(guò)程。請(qǐng)看下面這個(gè)很簡(jiǎn)單的JS函數(shù)。
1 | function Point(x, y) { |
2 | this .x = x; |
3 | this .y = y; |
4 | } |
當(dāng)new Point(x, y)被執(zhí)行的時(shí)候,一個(gè)新的Point對(duì)象會(huì)被創(chuàng)建,當(dāng)V8第一次執(zhí)行這個(gè)函數(shù)的時(shí)候,V8會(huì)為Point創(chuàng)建一個(gè)初始的隱藏類(lèi),為方便,我們假設(shè)這個(gè)類(lèi)叫C0,顯然這個(gè)類(lèi)里面什么都沒(méi)有,這個(gè)時(shí)候,Point這個(gè)對(duì)象的隱藏類(lèi)是C0。
當(dāng)執(zhí)行Point中的第一個(gè)語(yǔ)句(this.x = x;),會(huì)在Point對(duì)象中創(chuàng)建一個(gè)新的屬性x,對(duì)于V8而言,會(huì)做如下事情:
當(dāng)執(zhí)行Point中的第二個(gè)語(yǔ)句(this.y = y;)的時(shí)候,將會(huì)在Point對(duì)象中創(chuàng)建一個(gè)新的屬性y,對(duì)于V8而言,會(huì)做如下事情:
每次有一個(gè)屬性被添加的時(shí)候就要重新創(chuàng)建一個(gè)隱藏類(lèi)的做法看起來(lái)好像很低效,但是,因?yàn)轭?lèi)轉(zhuǎn)移的存在,隱藏類(lèi)可以被復(fù)用(如我上面所解釋?zhuān)?。盡管JS比其他面向?qū)ο蟮木幊陶Z(yǔ)言都要更加動(dòng)態(tài),但是采用上面的方法,通過(guò)觀察很多JS程序的運(yùn)行,它們?cè)诤艽蟪潭壬隙贾赜昧酥暗慕Y(jié)構(gòu)(the runtime behavior of most JS programs results in a high degree of structure-sharing using the above approach). 利用隱藏的方法有兩個(gè)好處:屬性的訪問(wèn)不再需要一個(gè)字典查詢(xún),同時(shí)可以讓V8使用一些在以類(lèi)為基礎(chǔ)的普通編程語(yǔ)言中可以用的優(yōu)化方法,例如inline caching(參考 Efficient Implementation of the Smalltalk-80 System)。
V8會(huì)JS代碼第一次運(yùn)行的時(shí)候?qū)⑵渲苯泳幾g為機(jī)器碼,在V8中沒(méi)有中間字節(jié)碼,也就沒(méi)有解釋器,屬性的訪問(wèn)由inline cache來(lái)完成,但這些代碼可能會(huì)在V8執(zhí)行期間被更改為別的機(jī)器指令。
當(dāng)?shù)谝淮螆?zhí)行訪問(wèn)某個(gè)對(duì)象的某個(gè)屬性的代碼的時(shí)候,V8決定這個(gè)對(duì)象現(xiàn)在的隱藏類(lèi),同時(shí)會(huì)進(jìn)行如下優(yōu)化,V8假設(shè)當(dāng)前代碼塊中的對(duì)這個(gè)對(duì)象的所有的屬性訪問(wèn)都會(huì)使用這個(gè)隱藏類(lèi),并根據(jù)這個(gè)假設(shè)修改inline cache的代碼,直接使用這個(gè)隱藏類(lèi)(跳過(guò)查詢(xún)隱藏類(lèi)的步驟),如果V8的假設(shè)是正確的,那么屬性值的讀取和賦值只需一個(gè)指令即可完成,如果假設(shè)錯(cuò)誤,那么V8再次修改代碼,移除這一優(yōu)化。
舉例,JS中訪問(wèn)一個(gè)Point對(duì)象的x屬性的代碼為
point.x
在V8中,相對(duì)應(yīng)的機(jī)器碼為
# ebx = the point objectcmp [ebx,<hidden class offset>],<cached hidden class>jne <inline cache miss>mov eax,[ebx, <cached x offset>]
如果這個(gè)對(duì)象的隱藏類(lèi)不符合緩存代碼中的隱藏類(lèi),執(zhí)行將會(huì)跳轉(zhuǎn)到V8運(yùn)行系統(tǒng)中處理inline cache失敗的地方并修改inline cache的代碼,如果找到了這個(gè)屬性,那么x的值就會(huì)直接被得到。
當(dāng)有許多對(duì)象共享同一個(gè)隱藏類(lèi)的時(shí)候,這樣的方法能夠使得JS的屬性訪問(wèn)速度和大部分靜態(tài)語(yǔ)言的訪問(wèn)速度相仿,使用隱藏類(lèi)并通過(guò)inline cache代碼來(lái)訪問(wèn)屬性,同時(shí)優(yōu)化機(jī)器碼的生成,V8能夠大幅優(yōu)化大部分JS代碼的執(zhí)行效率。
V8會(huì)將不再被引用的內(nèi)存進(jìn)行回收,這個(gè)過(guò)程通常被稱(chēng)之為垃圾回收,為了保證快速的對(duì)象生成,縮短垃圾回收所造成的暫停,并且防止內(nèi)存碎片的產(chǎn)生,V8的垃圾回收器使用了如下原則:stop-the-world, generational, accurate。具體來(lái)說(shuō):
在V8中,對(duì)象堆內(nèi)存被分為兩部分,用于新對(duì)象創(chuàng)建的新的內(nèi)存空間,用于存放在垃圾回收周期中存留下來(lái)的對(duì)象,如果一個(gè)對(duì)象在垃圾回收中被移動(dòng)了,V8會(huì)更新所有指向改對(duì)象的指針。
聯(lián)系客服