摘要:用解釋虛擬機(jī)內(nèi)聯(lián)緩存本文轉(zhuǎn)載自眾成翻譯譯者鏈接原文我知道如何實(shí)現(xiàn)用語(yǔ)言或者語(yǔ)言的子集來(lái)實(shí)現(xiàn)運(yùn)行該語(yǔ)言虛擬機(jī)。有時(shí)候我們用了錯(cuò)誤的抽象層次來(lái)解釋虛擬機(jī)的工作機(jī)制。這正是我們的內(nèi)聯(lián)緩存功能所需要的。
用JavaScript解釋JavaScript虛擬機(jī)-內(nèi)聯(lián)緩存(inline caches)
本文轉(zhuǎn)載自:眾成翻譯
譯者:LexHuang
鏈接:http://www.zcfy.cc/article/2959
原文:http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html
我知道如何實(shí)現(xiàn)用語(yǔ)言(或者語(yǔ)言的子集)來(lái)實(shí)現(xiàn)運(yùn)行該語(yǔ)言虛擬機(jī)。如果我在學(xué)?;蛘哂懈嗟臅r(shí)間我肯定會(huì)用JavaScript實(shí)現(xiàn)一個(gè)JavaScript虛擬機(jī)。實(shí)際上這并不會(huì)變成一個(gè)獨(dú)一無(wú)二的JavaScript項(xiàng)目,因?yàn)槊商乩麪柎髮W(xué)的人所造的Tachyon已經(jīng)在某種程度上達(dá)到了同樣的目的,但是我也有些我自己想要追求的點(diǎn)子。
我則有另一個(gè)和自循環(huán)虛擬機(jī)緊密相關(guān)的夢(mèng)想。我想要幫助JavaScript開(kāi)發(fā)者理解JS引擎的工作方式。我認(rèn)為理解你正在使用的工具是我們職業(yè)生涯中最重要的。越多人不在把JS VM看作是將JavaScript源碼轉(zhuǎn)為0-1神秘的黑盒越好。
我應(yīng)該說(shuō)我不是一個(gè)人在追求如何解釋虛擬機(jī)的內(nèi)部機(jī)制并且?guī)椭藗兙帉?xiě)性能更好的代碼。全世界有許多人正在嘗試做同樣的事情。但是我認(rèn)為又一個(gè)問(wèn)題正在阻止知識(shí)有效地被開(kāi)發(fā)者所吸收——我們正在用錯(cuò)誤的形式來(lái)傳授我們的知識(shí)。我對(duì)此深感愧疚:
有時(shí)候我把我對(duì)V8的了解包裝成了很難消化的“做這個(gè),別做那個(gè)”的教條化意見(jiàn)。這樣的問(wèn)題在于它對(duì)于解釋起不到任何幫助并且很容易隨著關(guān)注人的減少而消失。
有時(shí)候我們用了錯(cuò)誤的抽象層次來(lái)解釋虛擬機(jī)的工作機(jī)制。我喜歡一個(gè)想法:看見(jiàn)滿是匯編代碼的ppt演示可能會(huì)鼓勵(lì)人們?nèi)W(xué)習(xí)匯編并且學(xué)會(huì)之后會(huì)去讀ppt演示的內(nèi)容。但我也害怕有時(shí)候這些ppt只會(huì)被人忽視和遺忘而對(duì)于實(shí)踐毫無(wú)用處。
我一直在思考這些問(wèn)題很長(zhǎng)時(shí)間了并且我認(rèn)為用JavaScript來(lái)解釋JavaScript虛擬機(jī)是一個(gè)值得嘗試的事情。我在WebRebels 2012發(fā)表的演講“V8 Inside Out”追求的正是這一點(diǎn)[視頻][演示]并且在本文中我像回顧我一直在奧斯陸所談?wù)摰氖虑榈遣煌氖遣粫?huì)有任何音頻的干擾。(我認(rèn)為我寫(xiě)作的方式比我演講的方式更加嚴(yán)肅些 ?)。
用JavaScript來(lái)實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)言想象你想要為了一個(gè)在語(yǔ)法上非常類(lèi)似于JavaScript但是有著更簡(jiǎn)單的對(duì)象模型的語(yǔ)言——用表來(lái)映射key到任意類(lèi)型的值來(lái)代替JavaScript對(duì)象——而來(lái)用JavaScript實(shí)現(xiàn)其虛擬機(jī)。簡(jiǎn)單起見(jiàn),讓我們想象Lua, 既像JavaScript但作為一個(gè)語(yǔ)言又很不一樣。我最喜歡的“造出一個(gè)充滿點(diǎn)的數(shù)組然后去計(jì)算向量合”的例子看起來(lái)大致如下:
function MakePoint(x, y) local point = {} point.x = x point.y = y return point end function MakeArrayOfPoints(N) local array = {} local m = -1 for i = 0, N do m = m * -1 array[i] = MakePoint(m * i, m * -i) end array.n = N return array end function SumArrayOfPoints(array) local sum = MakePoint(0, 0) for i = 0, array.n do sum.x = sum.x + array[i].x sum.y = sum.y + array[i].y end return sum end function CheckResult(sum) local x = sum.x local y = sum.y if x ~= 50000 or y ~= -50000 then error("failed: x = " .. x .. ", y = " .. y) end end local N = 100000 local array = MakeArrayOfPoints(N) local start_ms = os.clock() * 1000; for i = 0, 5 do local sum = SumArrayOfPoints(array) CheckResult(sum) end local end_ms = os.clock() * 1000; print(end_ms - start_ms)
注意我有一個(gè)至少檢查某些最終結(jié)果的微型基準(zhǔn)測(cè)試的習(xí)慣。這有助于當(dāng)有人發(fā)現(xiàn)我的革命性的jsperf測(cè)試用例只不過(guò)是我自己的bug時(shí),讓我不會(huì)太尷尬。
如果你拿上面的例子放入一個(gè)Lua編譯器你會(huì)得到類(lèi)似于下面的東西:
∮ lua points.lua 150.2
很好,但是對(duì)于了解虛擬機(jī)的工作過(guò)程起不到任何幫助。所以讓我們想想如果我們有用JavaScript編寫(xiě)的類(lèi)Lua虛擬機(jī)會(huì)長(zhǎng)什么樣。“類(lèi)”是因?yàn)槲也幌雽?shí)現(xiàn)完全類(lèi)似于Lua的語(yǔ)法,我更喜歡只關(guān)注于用表來(lái)實(shí)現(xiàn)對(duì)象這一點(diǎn)上。原生編譯器應(yīng)該會(huì)將我們的代碼編譯成下面的JavaScript:
function MakePoint(x, y) { var point = new Table(); STORE(point, "x", x); STORE(point, "y", y); return point; } function MakeArrayOfPoints(N) { var array = new Table(); var m = -1; for (var i = 0; i <= N; i++) { m = m * -1; STORE(array, i, MakePoint(m * i, m * -i)); } STORE(array, "n", N); return array; } function SumArrayOfPoints(array) { var sum = MakePoint(0, 0); for (var i = 0; i <= LOAD(array, "n"); i++) { STORE(sum, "x", LOAD(sum, "x") + LOAD(LOAD(array, i), "x")); STORE(sum, "y", LOAD(sum, "y") + LOAD(LOAD(array, i), "y")); } return sum; } function CheckResult(sum) { var x = LOAD(sum, "x"); var y = LOAD(sum, "y"); if (x !== 50000 || y !== -50000) { throw new Error("failed: x = " + x + ", y = " + y); } } var N = 100000; var array = MakeArrayOfPoints(N); var start = LOAD(os, "clock")() * 1000; for (var i = 0; i <= 5; i++) { var sum = SumArrayOfPoints(array); CheckResult(sum); } var end = LOAD(os, "clock")() * 1000; print(end - start);
但是如果你嘗試用d8(V8的獨(dú)立shell)去運(yùn)行編譯后的代碼,它會(huì)很禮貌的拒絕:
∮ d8 points.js points.js:9: ReferenceError: Table is not defined var array = new Table(); ^ ReferenceError: Table is not defined at MakeArrayOfPoints (points.js:9:19) at points.js:37:13
失敗的原因很簡(jiǎn)單:我們還缺少負(fù)責(zé)實(shí)現(xiàn)對(duì)象模型和存取語(yǔ)法的運(yùn)行時(shí)系統(tǒng)代碼。這可能看起來(lái)很明顯,但是我想要強(qiáng)調(diào)的是:虛擬機(jī)從外面看起來(lái)像是黑盒,在內(nèi)部實(shí)際上是一系列盒子為了得到出最佳性能的相互協(xié)作。這些盒子是:編譯器、運(yùn)行時(shí)例程、對(duì)象模型、垃圾回收等。幸運(yùn)的是我們的語(yǔ)言和例子非常簡(jiǎn)單所以我們的運(yùn)行時(shí)系統(tǒng)僅僅多了幾行代碼:
function Table() { // Map from ES Harmony is a simple dictionary-style collection. this.map = new Map; } Table.prototype = { load: function (key) { return this.map.get(key); }, store: function (key, value) { this.map.set(key, value); } }; function CHECK_TABLE(t) { if (!(t instanceof Table)) { throw new Error("table expected"); } } function LOAD(t, k) { CHECK_TABLE(t); return t.load(k); } function STORE(t, k, v) { CHECK_TABLE(t); t.store(k, v); } var os = new Table(); STORE(os, "clock", function () { return Date.now() / 1000; });
注意到我用了ES6的Map而不是一般的JavaScript對(duì)象因?yàn)闈撛诘谋砜梢允褂萌魏捂I,而不僅是字符串形式的。
∮ d8 **--harmony** quasi-lua-runtime.js points.js 737
現(xiàn)在我們編譯后的代碼可以執(zhí)行但是卻慢地令人失望,因?yàn)槊恳淮巫x和寫(xiě)不得不跨越所有這些抽象層級(jí)后才能拿到值。讓我們通過(guò)所有JavaScript虛擬機(jī)都有的最基本的優(yōu)化inline caching來(lái)嘗試減少這些開(kāi)銷(xiāo)。即使是用Java實(shí)現(xiàn)的JS虛擬機(jī)最終也會(huì)使用它因?yàn)閯?dòng)態(tài)調(diào)用的本質(zhì)是被暴露在字節(jié)碼層面的結(jié)構(gòu)化的內(nèi)聯(lián)緩存。Inline caching(在V8資源里通常簡(jiǎn)寫(xiě)為IC)實(shí)際上是一門(mén)近30年的非常古老的技術(shù),最初用在Smalltalk虛擬機(jī)上。
好鴨子總是叫地一模一樣內(nèi)聯(lián)緩存(Inline caching)背后的思想非常簡(jiǎn)單:創(chuàng)建一個(gè)高速路來(lái)繞過(guò)運(yùn)行時(shí)系統(tǒng)來(lái)讀取對(duì)象的屬性:對(duì)傳入的對(duì)象及其屬性作出某種假設(shè),然后通過(guò)一個(gè)低成本的方式驗(yàn)證這個(gè)假設(shè)是否正確,如果正確就讀取上次緩存的結(jié)果。在充滿了動(dòng)態(tài)類(lèi)型和晚綁定以及其他古怪行為——比如eval——的語(yǔ)言里對(duì)一個(gè)對(duì)象作出合理的假設(shè)是非常困難的,所以我們退而求其次,讓我們的讀/寫(xiě)操作能夠有學(xué)習(xí)能力:一旦它們看見(jiàn)某個(gè)對(duì)象它們就可以以某種方式來(lái)自適應(yīng),使得之后的讀取操作在遇到類(lèi)似結(jié)構(gòu)的對(duì)象時(shí)能夠更快地進(jìn)行。在某種意義上,我們將要在讀/寫(xiě)操作上緩存關(guān)于之前見(jiàn)過(guò)的對(duì)象的布局的相關(guān)知識(shí)——這也是內(nèi)聯(lián)緩存這個(gè)名字的由來(lái)。內(nèi)聯(lián)緩存可以被用在幾乎所有需要?jiǎng)討B(tài)行為的操作上,只要你可以找到正確的高速路:算數(shù)操作、調(diào)用自由函數(shù)、方法調(diào)用等等。有些內(nèi)聯(lián)緩存還能緩存不止一條快速通道,這些內(nèi)聯(lián)緩存就變成了多態(tài)的。
如果我們開(kāi)始思考如何應(yīng)用內(nèi)聯(lián)緩存到上面編譯后的代碼,答案就變得顯而易見(jiàn)了:我們需要改變我們的對(duì)象模型。我們不可能從一個(gè)map中進(jìn)行快速讀取,因?yàn)槲覀兛偸且{(diào)用get方法。[如果我們能夠窺探map后的純哈希表,我們就可以通過(guò)緩存桶索引來(lái)讓內(nèi)聯(lián)緩存替我們工作而不需要相處一個(gè)新的對(duì)象布局。]
探索隱藏結(jié)構(gòu)出于效率角度考慮,用作數(shù)據(jù)結(jié)構(gòu)的表應(yīng)該更類(lèi)似于C結(jié)構(gòu):帶有固定偏移量的命名字段序列。這樣表就和數(shù)組類(lèi)似:我們希望數(shù)字形式的屬性的存儲(chǔ)類(lèi)似于數(shù)組。但是很顯然并不是所有表的鍵都是數(shù)字:鍵可以被設(shè)計(jì)成非字符串非數(shù)字或者包含太多字符串命名的屬性,并且隨著表的修改鍵也會(huì)隨之修改。不幸的是,我們不能做任何昂貴的類(lèi)型推斷。取而代之我們必須找在程序運(yùn)行期間的每一個(gè)表背后的結(jié)構(gòu),并且隨著程序的運(yùn)行可以創(chuàng)建和修改它們。幸運(yùn)的是,有一個(gè)眾所周知的技術(shù) ?:_隱藏類(lèi)(hidden classes)_。
隱藏類(lèi)背后的思想可以歸結(jié)為以下兩點(diǎn):
對(duì)于每個(gè)javascript對(duì)象,運(yùn)行時(shí)系統(tǒng)都會(huì)將其合一個(gè)hidden class關(guān)聯(lián)起來(lái)。就像Java VM會(huì)關(guān)聯(lián)一個(gè)java.lang.Class的實(shí)例給每個(gè)對(duì)象一樣。
如果對(duì)象的布局改變了,則運(yùn)行時(shí)就會(huì) 找到一個(gè)hidden class或者創(chuàng)建一個(gè)新的hidden class來(lái)匹配這個(gè)新對(duì)象布局并且連接到該對(duì)象上。
隱藏類(lèi)有個(gè)非常重要的特性:它們運(yùn)行虛擬機(jī)通過(guò)簡(jiǎn)單比對(duì)緩存過(guò)的隱藏類(lèi)來(lái)檢查關(guān)于某個(gè)對(duì)象布局的假設(shè)。這正是我們的內(nèi)聯(lián)緩存功能所需要的。讓我們?yōu)槲覀兊念?lèi)-Lua運(yùn)行時(shí)來(lái)實(shí)現(xiàn)一些簡(jiǎn)單的隱藏類(lèi)系統(tǒng)。每個(gè)隱藏類(lèi)本質(zhì)上是屬性描述符的集合,每個(gè)描述符要么是一個(gè)真正的屬性,要么是一個(gè)過(guò)渡(transition):從一個(gè)沒(méi)有該屬性的類(lèi)指向一個(gè)有該屬性的類(lèi)。
function Transition(klass) { this.klass = klass; } function Property(index) { this.index = index; } function Klass(kind) { // Classes are "fast" if they are C-struct like and "slow" is they are Map-like. this.kind = kind; this.descriptors = new Map; this.keys = []; }
過(guò)渡之所以存在是為了讓多個(gè)對(duì)象之間能共享隱藏類(lèi):如果你有兩個(gè)對(duì)象共享了隱藏類(lèi)并且你為它們同時(shí)增加了某些屬性,你不希望得到不同的隱藏類(lèi)。
Klass.prototype = { // Create hidden class with a new property that does not exist on // the current hidden class. addProperty: function (key) { var klass = this.clone(); klass.append(key); // Connect hidden classes with transition to enable sharing: // this == add property key ==> klass this.descriptors.set(key, new Transition(klass)); return klass; }, hasProperty: function (key) { return this.descriptors.has(key); }, getDescriptor: function (key) { return this.descriptors.get(key); }, getIndex: function (key) { return this.getDescriptor(key).index; }, // Create clone of this hidden class that has same properties // at same offsets (but does not have any transitions). clone: function () { var klass = new Klass(this.kind); klass.keys = this.keys.slice(0); for (var i = 0; i < this.keys.length; i++) { var key = this.keys[i]; klass.descriptors.set(key, this.descriptors.get(key)); } return klass; }, // Add real property to descriptors. append: function (key) { this.keys.push(key); this.descriptors.set(key, new Property(this.keys.length - 1)); } };
現(xiàn)在我們可以讓我們的表變得更加靈活并且能允許它們自適應(yīng)其自身地構(gòu)造方式
var ROOT_KLASS = new Klass("fast"); function Table() { // All tables start from the fast empty root hidden class and form // a single tree. In V8 hidden classes actually form a forest - // there are multiple root classes, e.g. one for each constructor. // This is partially due to the fact that hidden classes in V8 // encapsulate constructor specific information, e.g. prototype // poiinter is actually stored in the hidden class and not in the // object itself so classes with different prototypes must have // different hidden classes even if they have the same structure. // However having multiple root classes also allows to evolve these // trees separately capturing class specific evolution independently. this.klass = ROOT_KLASS; this.properties = []; // Array of named properties: "x","y",... this.elements = []; // Array of indexed properties: 0, 1, ... // We will actually cheat a little bit and allow any int32 to go here, // we will also allow V8 to select appropriate representation for // the array"s backing store. There are too many details to cover in // a single blog post :-) } Table.prototype = { load: function (key) { if (this.klass.kind === "slow") { // Slow class => properties are represented as Map. return this.properties.get(key); } // This is fast table with indexed and named properties only. if (typeof key === "number" && (key | 0) === key) { // Indexed property. return this.elements[key]; } else if (typeof key === "string") { // Named property. var idx = this.findPropertyForRead(key); return (idx >= 0) ? this.properties[idx] : void 0; } // There can be only string&number keys on fast table. return void 0; }, store: function (key, value) { if (this.klass.kind === "slow") { // Slow class => properties are represented as Map. this.properties.set(key, value); return; } // This is fast table with indexed and named properties only. if (typeof key === "number" && (key | 0) === key) { // Indexed property. this.elements[key] = value; return; } else if (typeof key === "string") { // Named property. var index = this.findPropertyForWrite(key); if (index >= 0) { this.properties[index] = value; return; } } this.convertToSlow(); this.store(key, value); }, // Find property or add one if possible, returns property index // or -1 if we have too many properties and should switch to slow. findPropertyForWrite: function (key) { if (!this.klass.hasProperty(key)) { // Try adding property if it does not exist. // To many properties! Achtung! Fast case kaput. if (this.klass.keys.length > 20) return -1; // Switch class to the one that has this property. this.klass = this.klass.addProperty(key); return this.klass.getIndex(key); } var desc = this.klass.getDescriptor(key); if (desc instanceof Transition) { // Property does not exist yet but we have a transition to the class that has it. this.klass = desc.klass; return this.klass.getIndex(key); } // Get index of existing property. return desc.index; }, // Find property index if property exists, return -1 otherwise. findPropertyForRead: function (key) { if (!this.klass.hasProperty(key)) return -1; var desc = this.klass.getDescriptor(key); if (!(desc instanceof Property)) return -1; // Here we are not interested in transitions. return desc.index; }, // Copy all properties into the Map and switch to slow class. convertToSlow: function () { var map = new Map; for (var i = 0; i < this.klass.keys.length; i++) { var key = this.klass.keys[i]; var val = this.properties[i]; map.set(key, val); } Object.keys(this.elements).forEach(function (key) { var val = this.elements[key]; map.set(key | 0, val); // Funky JS, force key back to int32. }, this); this.properties = map; this.elements = null; this.klass = new Klass("slow"); } };
[我不打算一行一行地解釋上面的代碼,因?yàn)樗呀?jīng)是用JavaScript書(shū)寫(xiě)的了;而不是C++ 或者 匯編...這正是使用JavaScript的意義所在。然而你可以通過(guò)評(píng)論或者郵件來(lái)詢問(wèn)任何不理解的地方。]
既然我們已經(jīng)在運(yùn)行時(shí)系統(tǒng)里加入了隱藏類(lèi),使得我們能夠快速檢查對(duì)象的結(jié)構(gòu)并且通過(guò)它們的索引來(lái)快速讀取屬性,我們只差實(shí)現(xiàn)內(nèi)聯(lián)緩存了。這需要在編譯器和運(yùn)行時(shí)系統(tǒng)增加一些新的功能(還記得我談?wù)撨^(guò)虛擬機(jī)內(nèi)不同成員之間的協(xié)作么?)。
打包生成后代碼實(shí)現(xiàn)內(nèi)聯(lián)緩存的途徑之一是將其分割成兩個(gè)部分:生成代碼里的可變調(diào)用點(diǎn)和可以被調(diào)用點(diǎn)調(diào)用的一系列存根(stubs,一小片生成的本地代碼)。非常重要的一點(diǎn)是:存根本身必須能從調(diào)用它們的調(diào)用點(diǎn)(或者運(yùn)行時(shí)系統(tǒng))中找到:存根只存放特定假設(shè)下的編譯后的快速路徑,如果這些假設(shè)對(duì)存根遇到的對(duì)象不適用,則存根可以初始化調(diào)用該存根的調(diào)用點(diǎn)的變動(dòng)(打包,patching),使得該調(diào)用點(diǎn)能夠適應(yīng)新的情況。我們的純JavaScript仍然包含兩個(gè)部分:
一個(gè)全局變量,每個(gè)ic都會(huì)使用一個(gè)全局變量來(lái)模擬可變調(diào)用指令;
并使用閉包來(lái)代替存根。
在本地代碼里, V8通過(guò)在棧上監(jiān)聽(tīng)返回地址來(lái)找到要打包的內(nèi)聯(lián)緩存點(diǎn)。我們不能通過(guò)純JavaScript來(lái)達(dá)到這一點(diǎn)(arguments.caller的粒度不夠細(xì))。所以我們將只會(huì)顯式地傳遞內(nèi)聯(lián)緩存的id到內(nèi)聯(lián)緩存的存根。通過(guò)內(nèi)聯(lián)緩存優(yōu)化后的代碼如下:
// Initially all ICs are in uninitialized state. // They are not hitting the cache and always missing into runtime system. var STORE$0 = NAMED_STORE_MISS; var STORE$1 = NAMED_STORE_MISS; var KEYED_STORE$2 = KEYED_STORE_MISS; var STORE$3 = NAMED_STORE_MISS; var LOAD$4 = NAMED_LOAD_MISS; var STORE$5 = NAMED_STORE_MISS; var LOAD$6 = NAMED_LOAD_MISS; var LOAD$7 = NAMED_LOAD_MISS; var KEYED_LOAD$8 = KEYED_LOAD_MISS; var STORE$9 = NAMED_STORE_MISS; var LOAD$10 = NAMED_LOAD_MISS; var LOAD$11 = NAMED_LOAD_MISS; var KEYED_LOAD$12 = KEYED_LOAD_MISS; var LOAD$13 = NAMED_LOAD_MISS; var LOAD$14 = NAMED_LOAD_MISS; function MakePoint(x, y) { var point = new Table(); STORE$0(point, "x", x, 0); // The last number is IC"s id: STORE$0 ? id is 0 STORE$1(point, "y", y, 1); return point; } function MakeArrayOfPoints(N) { var array = new Table(); var m = -1; for (var i = 0; i <= N; i++) { m = m * -1; // Now we are also distinguishing between expressions x[p] and x.p. // The fist one is called keyed load/store and the second one is called // named load/store. // The main difference is that named load/stores use a fixed known // constant string key and thus can be specialized for a fixed property // offset. KEYED_STORE$2(array, i, MakePoint(m * i, m * -i), 2); } STORE$3(array, "n", N, 3); return array; } function SumArrayOfPoints(array) { var sum = MakePoint(0, 0); for (var i = 0; i <= LOAD$4(array, "n", 4); i++) { STORE$5(sum, "x", LOAD$6(sum, "x", 6) + LOAD$7(KEYED_LOAD$8(array, i, 8), "x", 7), 5); STORE$9(sum, "y", LOAD$10(sum, "y", 10) + LOAD$11(KEYED_LOAD$12(array, i, 12), "y", 11), 9); } return sum; } function CheckResults(sum) { var x = LOAD$13(sum, "x", 13); var y = LOAD$14(sum, "y", 14); if (x !== 50000 || y !== -50000) throw new Error("failed x: " + x + ", y:" + y); }
上述的改變依舊是不言自明的:每一個(gè)屬性的讀/寫(xiě)點(diǎn)都有屬于它們自己的、帶有id的內(nèi)聯(lián)緩存。距離最終完成還剩一小步:實(shí)現(xiàn)未命中(MISS)存根和可以生存特定存根的“存根編譯器”:
function NAMED_LOAD_MISS(t, k, ic) { var v = LOAD(t, k); if (t.klass.kind === "fast") { // Create a load stub that is specialized for a fixed class and key k and // loads property from a fixed offset. var stub = CompileNamedLoadFastProperty(t.klass, k); PatchIC("LOAD", ic, stub); } return v; } function NAMED_STORE_MISS(t, k, v, ic) { var klass_before = t.klass; STORE(t, k, v); var klass_after = t.klass; if (klass_before.kind === "fast" && klass_after.kind === "fast") { // Create a store stub that is specialized for a fixed transition between classes // and a fixed key k that stores property into a fixed offset and replaces // object"s hidden class if necessary. var stub = CompileNamedStoreFastProperty(klass_before, klass_after, k); PatchIC("STORE", ic, stub); } } function KEYED_LOAD_MISS(t, k, ic) { var v = LOAD(t, k); if (t.klass.kind === "fast" && (typeof k === "number" && (k | 0) === k)) { // Create a stub for the fast load from the elements array. // Does not actually depend on the class but could if we had more complicated // storage system. var stub = CompileKeyedLoadFastElement(); PatchIC("KEYED_LOAD", ic, stub); } return v; } function KEYED_STORE_MISS(t, k, v, ic) { STORE(t, k, v); if (t.klass.kind === "fast" && (typeof k === "number" && (k | 0) === k)) { // Create a stub for the fast store into the elements array. // Does not actually depend on the class but could if we had more complicated // storage system. var stub = CompileKeyedStoreFastElement(); PatchIC("KEYED_STORE", ic, stub); } } function PatchIC(kind, id, stub) { this[kind + "$" + id] = stub; // non-strict JS funkiness: this is global object. } function CompileNamedLoadFastProperty(klass, key) { // Key is known to be constant (named load). Specialize index. var index = klass.getIndex(key); function KeyedLoadFastProperty(t, k, ic) { if (t.klass !== klass) { // Expected klass does not match. Can"t use cached index. // Fall through to the runtime system. return NAMED_LOAD_MISS(t, k, ic); } return t.properties[index]; // Veni. Vidi. Vici. } return KeyedLoadFastProperty; } function CompileNamedStoreFastProperty(klass_before, klass_after, key) { // Key is known to be constant (named load). Specialize index. var index = klass_after.getIndex(key); if (klass_before !== klass_after) { // Transition happens during the store. // Compile stub that updates hidden class. return function (t, k, v, ic) { if (t.klass !== klass_before) { // Expected klass does not match. Can"t use cached index. // Fall through to the runtime system. return NAMED_STORE_MISS(t, k, v, ic); } t.properties[index] = v; // Fast store. t.klass = klass_after; // T-t-t-transition! } } else { // Write to an existing property. No transition. return function (t, k, v, ic) { if (t.klass !== klass_before) { // Expected klass does not match. Can"t use cached index. // Fall through to the runtime system. return NAMED_STORE_MISS(t, k, v, ic); } t.properties[index] = v; // Fast store. } } } function CompileKeyedLoadFastElement() { function KeyedLoadFastElement(t, k, ic) { if (t.klass.kind !== "fast" || !(typeof k === "number" && (k | 0) === k)) { // If table is slow or key is not a number we can"t use fast-path. // Fall through to the runtime system, it can handle everything. return KEYED_LOAD_MISS(t, k, ic); } return t.elements[k]; } return KeyedLoadFastElement; } function CompileKeyedStoreFastElement() { function KeyedStoreFastElement(t, k, v, ic) { if (t.klass.kind !== "fast" || !(typeof k === "number" && (k | 0) === k)) { // If table is slow or key is not a number we can"t use fast-path. // Fall through to the runtime system, it can handle everything. return KEYED_STORE_MISS(t, k, v, ic); } t.elements[k] = v; } return KeyedStoreFastElement; }
代碼很長(zhǎng)(以及注釋?zhuān)?,但是配合上面所有解釋?xiě)?yīng)該不難理解:內(nèi)聯(lián)緩存負(fù)責(zé)觀察而存根編譯器/工程負(fù)責(zé)生產(chǎn)自適應(yīng)和特化后的存根[有心的讀者可能注意到了我本可以初始化所有鍵控的存儲(chǔ)內(nèi)聯(lián)緩存(keyed store ICs),用一開(kāi)始的快速讀取或者當(dāng)它進(jìn)入快速狀態(tài)后就一直保持住]。
如果我們不管上面所有代碼而回到我們的“基準(zhǔn)測(cè)試”,我們會(huì)得到非常令人滿意的結(jié)果:
∮ d8 --harmony quasi-lua-runtime-ic.js points-ic.js 117
這要比我們一開(kāi)始的天真嘗試提升了6倍!
關(guān)于JavaScript虛擬機(jī)優(yōu)化永遠(yuǎn)也不會(huì)有結(jié)論希望你在閱讀這一部分的時(shí)候已經(jīng)看完了之前所有內(nèi)容...我嘗試從不同的角度,JavaScript開(kāi)發(fā)者的角度,來(lái)看某些驅(qū)動(dòng)當(dāng)今JavaScript引擎的點(diǎn)子。所寫(xiě)的代碼越長(zhǎng),我越有一種盲人摸象的感覺(jué)。下面的事實(shí)只是為了給你一種望向深淵的感覺(jué):V8有10種描述符,5種元素類(lèi)型(+9外部元素類(lèi)型),ic.cc里包含了幾乎所有內(nèi)聯(lián)緩存狀態(tài)選擇的邏輯多達(dá)2500行,并且V8的內(nèi)聯(lián)緩存的狀態(tài)不止2個(gè)(它們是uninitialized, premonomorphic, monomorphic, polymorphic, generic states,更別提用于鍵控讀/寫(xiě)的內(nèi)聯(lián)緩存的特殊的狀態(tài)或者是算數(shù)內(nèi)斂緩存的完全不同的狀態(tài)層級(jí)),ia32-specific手寫(xiě)的內(nèi)聯(lián)緩存存根多達(dá)5000行代碼,等等。這些數(shù)字只會(huì)隨著時(shí)間的流逝和V8為了識(shí)別和適應(yīng)越來(lái)越多的對(duì)象布局的學(xué)習(xí)而增長(zhǎng)。而且我甚至都還沒(méi)談到對(duì)象模型本身(objects.cc 13k行代碼),或者垃圾回收,或者優(yōu)化編譯器。
話雖如此,在可預(yù)見(jiàn)的未來(lái)內(nèi),我確信基礎(chǔ)將不會(huì)改變,如果變了肯定會(huì)引發(fā)一場(chǎng)你一定會(huì)注意到的巨大的爆炸!因此我認(rèn)為這次嘗試用JavaScript去理解基礎(chǔ)的練習(xí)是非常非常非常重要的。
我希望明天或者幾周之后你會(huì)停下來(lái)并且大喊“我找到了!”并且告訴你的為什么特定情況下在一個(gè)地方為一個(gè)對(duì)象增加屬性會(huì)影響其余很遠(yuǎn)的接觸這些對(duì)象的熱回路的性能。_你知道的,因?yàn)殡[藏類(lèi)變了!_
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/84990.html
摘要:這些是中可用的最快屬性。通常來(lái)說(shuō)我們將線性屬性存儲(chǔ)中存儲(chǔ)的屬性稱(chēng)為。因此也支持所謂的屬性。整數(shù)索引屬性的處理和命名屬性的復(fù)雜性相同。 本文為譯文,原文地址:http://v8project.blogspot.com...,作者,@Camillo Bruni ,V8 JavaScript Engine Team Blog 在這篇博客中,我們想解釋 V8 如何在內(nèi)部處理 JavaScrip...
摘要:引擎可以是一個(gè)標(biāo)準(zhǔn)的解釋器,也可以是一個(gè)將編譯成某種形式的字節(jié)碼的即時(shí)編譯器。和其他引擎最主要的差別在于,不會(huì)生成任何字節(jié)碼或是中間代碼。不使用中間字節(jié)碼的表示方式,就沒(méi)有必要用解釋器了。 原文地址:https://blog.sessionstack.com... showImg(https://segmentfault.com/img/bVVwZ8?w=395&h=395); 數(shù)周之...
摘要:字節(jié)碼不能直接運(yùn)行,而是運(yùn)行在一個(gè)虛擬機(jī)之上,一般也把虛擬機(jī)稱(chēng)為引擎。這些事件排成隊(duì)列,等候進(jìn)入主線程。執(zhí)行至完成每一個(gè)消息執(zhí)行完成后,其它消息才會(huì)被執(zhí)行。零延遲零延遲并不是意味著回調(diào)會(huì)立即執(zhí)行。 JavaScript虛擬機(jī) JavaScript是一種解釋型語(yǔ)言,也就是說(shuō),它不需要編譯,可以由解釋器實(shí)時(shí)運(yùn)行。這樣的好處是運(yùn)行和修改都比較方便,刷新頁(yè)面就可以重新解釋?zhuān)蝗秉c(diǎn)是每次運(yùn)行都要調(diào)...
摘要:負(fù)責(zé)找出經(jīng)常被調(diào)用的代碼,做內(nèi)聯(lián)緩存優(yōu)化,后面的信息進(jìn)一步說(shuō)明了這個(gè)情況。我們?cè)偈褂脜?shù)看看引擎如何去優(yōu)化。如果使用,直接運(yùn)行輸出的是的命令行參數(shù),如果想查看的,需要使用。后面章節(jié)會(huì)介紹的命令行參數(shù)以及最有意思的。 在上一篇文章中我們講了如何使用 GN 編譯 V8 源碼,文章最后編譯完成的可執(zhí)行文件并不是 V8,而是 D8。這篇我們講一下如何使用 D8 調(diào)試 javascript 代碼...
摘要:延遲加載當(dāng)我們調(diào)用外部的時(shí)候,使用事件在頁(yè)面內(nèi)部被加載前,外部將不被加載腳本調(diào)用外部文件拷貝以上代碼。代碼代碼片段組合外部工具列表頁(yè)面加速優(yōu)化頁(yè)面請(qǐng)求工具工具大全擴(kuò)展閱讀方面的設(shè)置 內(nèi)聯(lián) CSS 優(yōu)點(diǎn) 使用內(nèi)聯(lián) CSS 可以減少瀏覽器去服務(wù)端去下載 CSS 文件 關(guān)鍵 CSS 內(nèi)聯(lián)到 HTML 文件中 缺點(diǎn) CSS 文件沒(méi)法被緩存 注意:該方法只適用于很小的 CSS...
閱讀 3693·2021-10-09 09:44
閱讀 3397·2021-09-22 15:29
閱讀 3154·2019-08-30 15:54
閱讀 3027·2019-08-29 16:19
閱讀 2155·2019-08-29 12:50
閱讀 602·2019-08-26 14:04
閱讀 1707·2019-08-23 18:39
閱讀 1356·2019-08-23 17:59