從V8 JS引擎學到的優化經驗

Daetalus發表於2015-02-12

V8是谷歌開源的一個高效能JavaScript引擎,用 C++ 實現,並用在谷歌的開源瀏覽器Chrome裡。

為什麼V8非常快,哪種方案讓V8達到這種速度?發現其中祕密是一件有趣的事情。

物件導向、設計模式與效能

有些C/C++開發者有些奇怪的觀念。他們認為使用物件導向和設計模式會降低程式的效能。但V8證明了這種觀念是錯誤的。V8的實現使用了許多設計模式,但依然非常高效。

下面列出V8中使用的兩個模式:

工廠模式

當Javascript引擎執行一個指令碼時,引擎為遇到的每個變數、函式或陣列都建立一個例項。JSObject是所有這些物件的父物件。

下面列出了所有繼承自JSObject的類:

V8實現了一個工廠類來建立這些物件,該類中的Factory::NewJsObject就是用來建立這些物件的。

下面列出了所有使用該類/方法的方法。

V8引擎中的類並沒有直接使用這個工廠類,而是新增了另一層封裝,通過Heap類呼叫該工廠類。

訪問者模式:

維基百科上這樣解釋觀察者模式:

觀察者設計模式是將演算法和演算法處理的物件分開的一種方式。這種分離可以在不修改結構本身的情況下,將新的操作新增到已有的物件結構上。這是一條遵循open/closed準則的方式。

與工廠模式相似,訪問者模式也為實現新增了封裝層。這樣讓其程式碼更加可讀且可維護。

V8原始碼中許多類都實現了訪問者模式。

即使V8開發者必須優化執行效率,他們也不在乎新增到程式碼中的封裝層。使用設計模式和新增一些C++的機制會增加一些封裝,所以的確會對效率有影響。但這對效率的影響僅佔一小部分,更多的影響來自該應用使用的設計決策。

V8中針對執行效率方面的設計決策

1. 隱藏類和快速屬性訪問。

JavaScript是一種動態程式語言:可以在物件執行時為物件新增或刪除熟悉。這意味著很容易改變物件的屬性。

JSFunction和JSValue的父類都是JSObject,JSFunction用來表示一個javascript函式,JSValue用來表示一個javascript值。但沒有繼承自JSObject的類,用以表示Function或Value這樣的Class。許多JavaScript引擎使用詞典型別的資料結構來儲存這些物件的熟悉,訪問每個屬性都需要動態查詢並解析屬性在記憶體中的位置。

這種方式導致JavaScript在訪問物件變數的屬性時,比在Java或Smalltalk中要慢。在這些語言中,實列變數分配的位置是固定的,即由編譯器根據物件的類定義中的佈局,在該物件在記憶體中的位置加上固定的偏移位置。因此訪問這些屬性僅僅是記憶體上的讀取或儲存,而這種操作通常只需一條指令。

V8使用隱藏類概念來降低訪問JavaScript屬性所消耗的時間。V8不使用動態查詢來訪問屬性,而是在幕後建立隱藏類。

2. 動態生產機器碼

在首次執行時,V8就將JavaScript原始碼直接編譯成機器碼,沒有中間位元組碼,沒有直譯器。屬性訪問由內聯的快取程式碼處理,V8執行時可能會有其他機器指令修改這些快取程式碼。

3. 高效的垃圾收集器

在執行過程中,V8會重新獲得廢棄物件的記憶體,即垃圾回收。為了保證擁有較快的物件分類、較短的垃圾回收停頓,以及沒有記憶體碎片。V8使用了停頓、分代、精確垃圾回收器。這意味著V8使用了:

  • 在垃圾回收迴圈期間停止程式的執行。
  • 在大多數垃圾迴圈中,只處理物件堆的一部分。這最大化降低了停頓對應用的影響。
  • 記錄所有物件和指標在記憶體中的位置,避免了將物件作為指標識別而導致的記憶體洩漏。

結論:

出於效率因素而不使用物件導向或設計模式,這是一個錯誤的觀念。這樣只會獲得數毫秒的優化,卻失去了程式碼的可讀性和可維護性。

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

從V8 JS引擎學到的優化經驗 從V8 JS引擎學到的優化經驗

相關文章