V8引擎優化機制之隱藏類和內聯緩存

我們知道Javascript作爲一種動態語言,性能方面與c#,Java之類的靜態語言相比存在着一定的差距。而隨着Web技術的發展,對Javascript的執行效率提出越來越高的要求。爲了追求更好的性能,V8引擎借鑑了大量的靜態語言編譯技術來優化引擎的執行效率。比如V8引擎放棄生成中間字節碼,而是直接從AST(抽象語法樹)生成機器語言。與靜態語言不同, javascript的程序在執行期間需要反覆檢查數據類型。因此,V8引擎中存在兩種機制來優化這個過程。

hidden class 隱藏類

對於動態類型語言來說,由於類型的不確定性,在方法調用過程中,語言引擎每次都需要進行動態查詢,這就造成大量的性能消耗,從而降低程序運行的速度。大多數的Javascript 引擎會採用哈希表的方式來存取屬性和尋找方法。而爲了加快對象屬性和方法在內存中的查找速度,V8引擎引入了隱藏類(Hidden Class)的機制,起到給對象分組的作用。在初始化對象的時候,V8引擎會創建一個隱藏類,隨後在程序運行過程中每次增減屬性,就會創建一個新的隱藏類或者查找之前已經創建好的隱藏類。每個隱藏類都會記錄對應屬性在內存中的偏移量,從而在後續再次調用的時候能更快地定位到其位置。

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var xiaoming = new Person("xiaoming", 32);
var lisi = new Person("lisi", 20);

xiaoming.email = "[email protected]";
xiaoming.job = "teacher";

lisi.job = "chef";
lisi.email = "[email protected]";

觀察以上代碼,當初始化Person對象的時候, 最開始會創建一個C0的隱藏類,該類不帶有任何屬性。隨後在調用構造器函數的時候,隨着屬性的增加,引擎會生成C1,C2的過渡隱藏類,隱藏類內部會記錄屬性的偏移量(offset)。之所以存在過渡隱藏類是爲了在多個對象間能夠共享隱藏類。

這裏,注意到xiaominglisi兩個對象使用的是同一個構造函數,所以它們會共享同一個隱藏類C2。隨後雖然xiaominglisi兩個對象都添加了jobemail兩個屬性,但由於初始化順序不同,會生成不同的隱藏類。

不同初始化順序的對象,所生成的隱藏類是不一樣的。因此,在實際開發過程中,應該儘量保證屬性初始化的順序一致,這樣生成的隱藏類可以得到共享。同時,儘量在構造函數裏就初始化所有對象成員,減少隱藏類的產生。

inline caching 內聯緩存

僅擁有隱藏類似乎還不夠,畢竟引擎在執行過程中還需要查找隱藏類。爲了取得更好的性能,V8引擎加入了內聯緩存(Inline Caching)技術來優化運行時查找對象及其屬性的過程。這項技術其實很古老了,最初是應用在Smalltalk虛擬機上。核心原理就是在運行過程中,收集類型信息,從而可以讓引擎在後續運行過程中利用這些類型信息作出預判。

對於動態查詢優化來說,最簡單的方式是利用緩存來保留最常使用的查詢結果。每次調用對象上的方法或屬性的時候先查詢緩存,如果命中則直接使用緩存結果。如果未命中,就查詢隱藏類來獲取結果。內聯緩存也是基於這個思想。但是如果想要進一步優化查詢效率,應該怎麼做呢? 考慮到在程序中類型很少發生改變,內聯緩存技術會直接將查詢結果寫入調用方法中,來避免查詢緩存。但是萬一類型在程序執行中途發生變化了怎麼辦?對於這種情況,內聯緩存會在直接調用之前驗證類型,這些驗證類型的代碼叫做"前導代碼"。

var arr = [1, 2, 3, 4];
arr.forEach((item) => console.log(item.toString());

像上面這段代碼,數字1在第一次toString()方法時會發起一次動態查詢,並記錄查詢結果。當後續再調用toString方法時,引擎就能根據上次的記錄直接獲知調用點,不再進行動態查詢操作。

再來考慮下面這個情況:

var arr = [1, '2', 3, '4'];
arr.forEach((item) => console.log(item.toString());

可以看到,調用toString方法的對象類型經常發生改變,這就會導致緩存失效。爲了防止這種情況發生,V8引擎採用了 polymorphic inline cache (PIC) 技術, 該技術不僅僅只緩存最後一次查詢結果,還會緩存多次的查詢結果(取決於記錄上限)。

參考資料

https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e

https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

https://github.com/sq/JSIL/wiki/Optimizing-dynamic-JavaScript-with-inline-caches

https://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章