瀏覽器辭典:V8
V8:Chrome的JavaScript引擎,用C++開發,基于ECMA-262第3版標準。作為一個開源項目,任何人都可以參加。項目地址在Google Code:http://code.google.com/p/v8/
Chrome V8設計
自從十九世紀九十年底中,Netscape瀏覽器集成了JavaScript,它使得web開發者更加容易訪問HTML頁面元素如:表單、frames和圖象。JavaScript迅速流行,用于定制控件和增加動畫效果。到19世紀九十年代后,出現大量的用于切換圖片以響應用戶生成的鼠標事件的腳本。
最近,隨著AJAX的出現,JavaScript已經稱為了實現基于web的應用(如:Gmail)的中心技術。JavaScript程序由簡單的幾行成長為幾百k的源碼。然而JavaScript是被設計成實現web應用的非常有效的技術。性能已經成為開發基于web的 JavaScript應用的限制因素。
V8是全新的JavaScript引擎,主要設計目標是快速執行大量JavaScript腳本應用。在幾種benchmark測試中,V8的性能是JScript(IE內的引擎)、SpiderMonkey(Firefox所用)和 JavaScriptCore(safari所用)的許多倍。如果您的web應用受限于JavaScript的執行速度,則使用V8代替您當前的 JavaScript引擎將很可能提高您的應用的性能。性能提升的程度依賴于JavaScript的多少和JavaScript的特點。例如,如果在您的應用中函數傾向于一次一次被執行,則與僅執行一次許多不同函數相比性能將大大地提升。當您閱讀完本文檔時,您將更加清楚性能提升的原因。
V8性能的3個關鍵方面:
◆快速屬性訪問
◆動態生成機器碼
◆高效的垃圾回收
快速屬性訪問
JavaScript是動態的編程語言:對象的屬性可以增加和刪除。這意味著一個對象的屬性可能改變。大多數JavaScript引擎使用類似字典的數據結構存儲對象的屬性,每個屬性的訪問需要動態查找定位屬性在內存的位置。這種典型的訪問屬性方法比在Java和Smalltalk中訪問實例化變量慢得多。在這些語言中,實例化變量通過由編譯器決定的根據對象類型定義的對象固定的布局定義的固定的偏移來定位。加載或存儲訪問非常簡單,通常僅僅需要一條簡單點的指令。
為了減少訪問JavaScript屬性的時間,V8沒有使用動態查找訪問屬性,取而代之的是V8動態創建后臺隱藏的類。這個想法不是最新才有的-是基于原型的編程語言自身的特性(相似地用于映射某些東西)(見An Efficient Implementation of Self, a Dynamiclly-Typed Object-Oriented Language Based on Prototypes)。在V8中,當一個新的屬性增加時,對象改變它的隱藏類。
為了更加清除說明這一個點,想象如下一個的簡單的JavaScript函數:
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:
- 基于C0創建另外一個的隱藏類C1, 然后增加描述有屬性x的信息給C1,這個屬性的值存在Point對象偏移為0的位置。
- 如果一個x屬性添加到C0描述的對象上那么隱藏類C1應該取代C0,同時更新C0以表示前面的過渡。此時Point對象的隱藏類是C1。
執行Point的第二個語句(this.y = y; ),則在Point對象中創建一個新的屬性y,這種情況下V8:
- 基于C1創建另外一個隱藏類C2,然后添加描述屬性y的信息給C2,同時屬性值在Point對象的偏移為1。
- 如果一個y屬性添加到C1描述的對象上那么隱藏類C2應該取代C1,同時更新C1以表示前面的過渡。此時Point對象的隱藏類是C2。
無論何時增加屬性,以上似乎通過創建一個隱藏類不是很高效。然而由于類的過渡,隱藏類可以重用,實際的效率較高。第二次創建一個新的Point時是不需要創建新的隱藏類,相反新的Point對象共享了第一個Point對象的類型。例如,如果創建另外一個Point對象:
- 初始的Point對象沒有屬性,因此最新建的對象引用初始類C0。
- 當增加屬性x,V8遵循隱藏類從C0到C1過渡。并根據C1中x的偏移寫入x的值。
- 當增加屬性y,V8遵循隱藏類從C1到C2過渡。并根據C2中y的偏移寫入y的值。
盡管JavaScript比通常的面向對象的語言更加動態,使用上面方法通常的JavaScript程序的運行時行為將導致高度的結構貢獻。這里列舉使用隱藏類的兩個優點:屬性訪問不需要字典查找,同時使得V8能使用面向對象的優化,內聯緩存。更多的內聯緩存見Efficent Implementation of the Smalltalk-80 System。
動態生成機器指令
首次執行時,V8直接將JavaScript源碼編譯成機器碼。不存在中間過程的字節碼,沒有解釋器。訪問屬性通過處理內聯的緩存代碼,這些代碼可以像V8執行時一樣轉為的其他機器指令。
在首次訪問一個給定對象的屬性時,V8生成了對象當前的隱藏類。V8使用隱藏類內部生成內聯緩存信息并通過預測這個類是否將用于在同一節代碼的所有將來的對象來優化屬性的訪問。如果V8成功預測則屬性的值將用一個簡單的操作讀取或者寫入。如果預測不正確,則V8將刪除被優化的代碼。
例如,JavaScript代碼訪問Point對象的屬性x:
point.x
在V8中,訪問x的機器碼是:
# ebx = the point object cmp [ebx,<hidden class offset>],<cached hidden class> jne <inline cache miss> mov eax,[ebx, <cached x offset>]
如果對象的隱藏類不匹配緩存的隱藏類,則執行跳轉到V8運行時系統處理內嵌緩存缺失同時生成內嵌緩存代碼,通常遇到的情況是匹配,則簡單地返回屬性x的值。
當有許多對象具有相同的隱藏類時,則就像大多數靜態語言一樣這些對象都受益。使用隱藏類訪問屬性和內嵌緩存與機器碼生成優化組合在一起,對于相同類型的對象以相似的方式頻繁創建和訪問,這將大大地提高執行大多數JavaScript代碼的速度。
高效的垃圾回收
V8回收那些在過程中不再需要的對象的內存,這一個過程稱為垃圾回收。為了確保快速的對象分配,垃圾回收時間足夠短暫,沒有內存碎片,V8使用了stop-the-world,分代,精確垃圾回收器。這意味著V8:
- 在執行垃圾回收期間停止程序的執行。
- 大多數的垃圾回收都是在處理一部分對象的堆。則最小化對于停止應用執行的影響。
- 總是精確知道何處的所有對象和指針在內存中。避免了錯誤的指示對象的指針可能帶來的內存泄漏。
在V8中,對象的堆分成兩段:剛創建對象的新空間和在垃圾回收時仍在使用的老對象。如果一個對像被垃圾回收器回收,V8更新所有指向這個對象的指針。