如何分析JS引擎中的Inline Cache技術,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
創(chuàng)新互聯(lián)公司2013年至今,先為荊門等服務建站,荊門等地企業(yè),進行企業(yè)商務咨詢服務。為荊門企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務解決您的所有建站問題。
function Point(x,y) {
this.x = x;
this.y = y;
}
var p = new Point(0, 1);
var q = new Point(2,3);
var r = new Point(4,5);
為了避免API調用不穩(wěn)定因素的影響,通過修改V8源碼,在內部插入時間戳的方式。在3.2G 8核機器上,分別測試三次調用new Point(x,y)時執(zhí)行this.x=x這個語句耗時,結果如下表所示。
執(zhí)行代碼 | this.x=x耗時統(tǒng)計 |
var p = new Point(0,1); | 4.11ns |
var q = new Point(2,3); | 6.63ns |
var r = new Point(4,5); | 0.65ns |
從表中的結果可以看出,事實并非想象中的從第二次執(zhí)行開始,速度就會變快,相反,第二次比第一次還要慢,到第三次的時候速度才會變快。本文后面會通過分析V8 IC機制來解釋為什么第二次速度最慢,第三次執(zhí)行速度又變快的原因。
1. 對象的隱藏類(Hidden Class)
由于JavaScript對象沒有類型信息,幾乎所有JS引擎都采用隱藏類(Hidden Class/Shape/Map等)來描述對象的布局信息,用以在虛擬機內部區(qū)分不同對象的類型,從而完成一些基于類型的優(yōu)化。
V8對JavaScript對象都使用HeapObject來描述和存儲,每一種JavaScript對象都是HeapObject的子類,而每個HeapObject都用Map來描述對象的布局。對象的Map描述了對象的類型,即成員數(shù)目、成員名稱、成員在內存中的位置信息等。
上述源碼中對象p、q、r都是由同一個構造函數(shù)Point生成,因此他們具有同樣的內存布局,可以采用同一個Map來描述。
2. 隱藏類變遷(Map Transition)
因為JavaScript是高度動態(tài)的程序設計語言,對象的成員可以被隨意動態(tài)地添加、刪除甚至修改類型。因此,對象的隱藏類在程序的運行過程中可能會發(fā)生變化,V8內部把這種變化叫隱藏類變遷(Map Transition)。
Map Transition示意圖
前面已經提到IC機制的原理是:對于某代碼語句比如this.x=x,比較上次執(zhí)行到該語句時緩存的Map和對象當前的Map是否相同,如果相同則執(zhí)行對應的IC-Hit代碼,反之執(zhí)行IC-Miss代碼。那么V8是如何組織被緩存的Map和IC-Hit代碼?以上文代碼為例,V8會在Point函數(shù)對象上添加一個名為type_feedback_vector的數(shù)組成員,對于該函數(shù)中的每處可能產生IC的代碼,Point對象中的type_feedback_vector會緩存上一次執(zhí)行至該語句時對象的Map和對應的IC-Hit代碼(在V8內部稱為IC-Hit Handler)。上文中的Point函數(shù)中有兩處可能產生IC的語句,this.x=x和this.y=y。假設某次執(zhí)行至this.x=x時,對象this的Map是map0,執(zhí)行至this.y=y時this的Map是map1,那么Point對象的type_feedback_vector數(shù)據(jù)內容如下所示:
數(shù)組下標 | IC對應的源碼 | 緩存的Map和對應的IC-Hit Handler |
0 | this.x=x | |
1 | this.y=y |
簡單來說,type_feedback_vector緩存了Map和與之對應的IC-Hit handler,這樣IC相關的邏輯簡化為只需要通過訪問type_eedback_vector就可以判斷是否IC Hit并執(zhí)行對應的IC-Hit Handler。
為了描述V8中IC狀態(tài)的變化情況,本節(jié)將以狀態(tài)機的形式描述V8中最常見IC種類的狀態(tài)變化情況。V8中最常用 的IC分為五個狀態(tài),如圖二所示。初始為uninitialized狀態(tài),當發(fā)生一次IC-Miss時會變?yōu)閜re-monomorphic態(tài),再次IC-Miss會進入monomorphic態(tài),如果繼續(xù)IC-Miss,則會進入polymorphic狀態(tài)。進入polymorphic之后如果繼續(xù)IC-Miss 3次,則會進入megamorphic態(tài),并最終穩(wěn)定在megamophic態(tài)。
IC狀態(tài)機
引例中代碼會涉及到IC狀態(tài)機的前三種狀態(tài)。
以Point函數(shù)走紅this.x=x語句為例,第一次執(zhí)行時,由于Point.type_feedback_vetor為空,因此此時會發(fā)生IC-Miss,并將該處IC狀態(tài)從uninitialized設置為pre-monomorphic,IC-Miss Handler會分析出此時this對象的Map中不包含屬性x,因此會添加成員x,接著會發(fā)生Map Transition,即前文提到的this對象的隱藏類從map0變?yōu)閙ap1。由于考慮到大部分函數(shù)可能只會被調用一次,因此V8的策略是發(fā)生第一次IC-Miss時,并不會緩存此時的map,也不會產生IC-Hit handler;
第二次調用構造函數(shù)執(zhí)行this.x=x時,由于Point.type_feedback_vector仍然為空,因此會發(fā)生第二次IC-Miss,并將IC狀態(tài)修改為monomorphic,此次IC-Miss Hanlder除了發(fā)生Map Transition之外,還會編譯生成IC-Hit Handler,并將map0和IC Hit Handler緩存到Point.type_feedback_vector中。由于此次IC-Miss Handler需要編譯IC-Hit Handler的操作比較耗時,因此第二次執(zhí)行this.x=x是最慢的;
第三次調用構造函數(shù)中this.x=x時,發(fā)現(xiàn)Point.type_feedback_vector不為空,且此時緩存的map0與此時this對象的Map也是一致的,因此會直接調用IC-Hit Handler來添加成員x并進行Map transition。由于此次無需對map0進行分析,也無需編譯IC-Hit Handler,因此此時執(zhí)行效率比前兩次都高。
function f(o) {
return o.x;
}
f({x:1}) //pre-monomorphic
f({x:2}) //monomorphic
f({x:3, y:1}) // polymorphic degree 2
f({x:4, z:1}) // polymorphic degree 3
f({x:5, a:1}) // polymorphic degree 4
f({x:6, b:1}) // megamorphic
IC-Miss性能是最差的;
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。