執行代碼this.x=x耗時統計 | var p = new Point(0,1); | 4.11ns | var q = new Point(2,3); | 6.63ns | var r = new Point(4,5); | 0.65ns"}}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從表中的結果可以看出,事實並非想象中的從第二次執行開始,速度就會變快,相反,第二次比第一次還要慢,到第三次的時候速度纔會變快。本文後面會通過分析V8 IC機制來解釋爲什麼第二次速度最慢,第三次執行速度又變快的原因。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"問題分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. 對象的隱藏類(Hidden Class)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於JavaScript對象沒有類型信息,幾乎所有JS引擎都採用隱藏類(Hidden Class\/Shape\/Map等)來描述對象的佈局信息,用以在虛擬機內部區分不同對象的類型,從而完成一些基於類型的優化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8對JavaScript對象都使用HeapObject來描述和存儲,每一種JavaScript對象都是HeapObject的子類,而每個HeapObject都用Map來描述對象的佈局。對象的Map描述了對象的類型,即成員數目、成員名稱、成員在內存中的位置信息等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述源碼中對象p、q、r都是由同一個構造函數Point生成,因此他們具有同樣的內存佈局,可以採用同一個Map來描述。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. 隱藏類變遷(Map Transition)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲JavaScript是高度動態的程序設計語言,對象的成員可以被隨意動態地添加、刪除甚至修改類型。因此,對象的隱藏類在程序的運行過程中可能會發生變化,V8內部把這種變化叫隱藏類變遷(Map Transition)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上文代碼爲例,當Point function被聲明時,V8就會給Point創建隱藏類map0,由於暫時還沒有屬性,因此map0爲空。當執行this.x=x時,V8會創建第二個Hidden Class map1,map1是基於map0,並且描述了屬性x在內存中的位置,此時this對象的Hidden Class會通過Map Transition變爲map1。當執行this.y=y時,會重複前面的操作,一個新的Hidden Class map2會被創建,此時this對象的Hidden Class被更新爲map2。this對象的Map從map0變爲map1,再變成map2的過程就叫做Map Transition。圖一描述了Point類對象創建過程中Map Transition的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/22\/6a\/22f7296f70bc70842c3f85a53443726a.jpg","alt":null,"title":"Map Transition示意圖","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3. 類型反饋向量(type feedback vector)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面已經提到IC機制的原理是:對於某代碼語句比如this.x=x,比較上次執行到該語句時緩存的Map和對象當前的Map是否相同,如果相同則執行對應的IC-Hit代碼,反之執行IC-Miss代碼。那麼V8是如何組織被緩存的Map和IC-Hit代碼?以上文代碼爲例,V8會在Point函數對象上添加一個名爲type_feedback_vector的數組成員,對於該函數中的每處可能產生IC的代碼,Point對象中的type_feedback_vector會緩存上一次執行至該語句時對象的Map和對應的IC-Hit代碼(在V8內部稱爲IC-Hit Handler)。上文中的Point函數中有兩處可能產生IC的語句,this.x=x和this.y=y。假設某次執行至this.x=x時,對象this的Map是map0,執行至this.y=y時this的Map是map1,那麼Point對象的type_feedback_vector數據內容如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"embedcomp","attrs":{"type":"table","data":{"content":" |
|
JS 引擎中的 Inline Cache 技術內幕,你知道多少?
{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript以簡單易用而著稱,NodeJS的出現使JavaScript的影響進一步擴大。JavaScript是動態類型的語言,動態類型爲應用開發者帶來了便利,但也爲JavaScript運行時的性能帶來了負擔,例如類型的不斷變化可能會導致基於類型的某些優化失效。爲了解決JavaScript由於動態類型導致的運行性能受損問題,各大JavaScript引擎幾乎都採用了IC(Inline Cache)技術:即通過緩存上一次對象的類型信息來加速當前對象屬性的讀寫訪問。本文從引例入手,以V8 JavaScript引擎(主要由於V8既是Chrome瀏覽器的JS引擎,也是node的JS引擎)爲基礎,深入分析Inline Cache機制的基本原理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"引例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"function Point(x,y) {\nthis.x = x;\nthis.y = y;\n}\nvar p = new Point(0, 1);\nvar q = new Point(2,3);\nvar r = new Point(4,5);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了避免API調用不穩定因素的影響,通過修改V8源碼,在內部插入時間戳的方式。在3.2G 8核機器上,分別測試三次調用new Point(x,y)時執行this.x=x這個語句耗時,結果如下表所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"embedcomp","attrs":{"type":"table","data":{"content":"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.