摘要:常規(guī)元素,不能表示為或雙精度的值。元素種類可從過(guò)渡轉(zhuǎn)變?yōu)?。這是一個(gè)簡(jiǎn)化的可視化,僅顯示最常見(jiàn)的元素種類只能通過(guò)格子向下過(guò)渡。目前有種不同的元素種類,每種元素都有自己的一組可能的優(yōu)化。再次重申更具體的元素種類可以進(jìn)行更細(xì)粒度的優(yōu)化。
原文:“Elements kinds” in V8
JavaScript 對(duì)象可以具有與它們相關(guān)聯(lián)的任意屬性。對(duì)象屬性的名稱可以包含任何字符。JavaScript 引擎可以進(jìn)行優(yōu)化的一個(gè)有趣的例子是當(dāng)屬性名是純數(shù)字時(shí),一個(gè)特例就是數(shù)組索引的屬性。
在 V8 中,如果屬性名是數(shù)字(最常見(jiàn)的形式是 Array 構(gòu)造函數(shù)生成的對(duì)象)會(huì)被特殊處理。盡管在許多情況下,這些數(shù)字索引屬性的行為與其他屬性一樣,V8 選擇將它們與非數(shù)字屬性分開(kāi)存儲(chǔ)以進(jìn)行優(yōu)化。在引擎內(nèi)部,V8 甚至給這些屬性一個(gè)特殊的名稱:元素。對(duì)象具有映射到值的屬性,而數(shù)組具有映射到元素的索引。
盡管這些內(nèi)部結(jié)構(gòu)從未直接暴露給 JavaScript 開(kāi)發(fā)人員,但它們解釋了為什么某些代碼模式比其他代碼模式更快。
常見(jiàn)的元素種類運(yùn)行 JavaScript 代碼時(shí),V8 會(huì)跟蹤每個(gè)數(shù)組所包含的元素。這些信息可以幫助 V8 優(yōu)化數(shù)組元素的操作。例如,當(dāng)您在數(shù)組上調(diào)用 reduce,map 或 forEach 時(shí),V8 可以根據(jù)數(shù)組包含哪些元素來(lái)優(yōu)化這些操作。
拿這個(gè)數(shù)組舉例:
const array = [1, 2, 3];
它包含什么樣的元素?如果你使用 typeof 操作符,它會(huì)告訴你數(shù)組包含 numbers。在語(yǔ)言層面,這就是你所得到的:JavaScript 不區(qū)分整數(shù),浮點(diǎn)數(shù)和雙精度 - 它們只是數(shù)字。然而,在引擎級(jí)別,我們可以做出更精確的區(qū)分。這個(gè)數(shù)組的元素是 PACKED_SMI_ELEMENTS。在 V8
中,術(shù)語(yǔ) Smi 是指用于存儲(chǔ)小整數(shù)的特定格式。(后面我們會(huì)在 PACKED 部分中說(shuō)明。)
稍后在這個(gè)數(shù)組中添加一個(gè)浮點(diǎn)數(shù)將其轉(zhuǎn)換為更通用的元素類型:
const array = [1, 2, 3]; // 元素類型: PACKED_SMI_ELEMENTS array.push(4.56); // 元素類型: PACKED_DOUBLE_ELEMENTS
向數(shù)組添加字符串再次改變其元素類型。
const array = [1, 2, 3]; // 元素類型: PACKED_SMI_ELEMENTS array.push(4.56); // 元素類型: PACKED_DOUBLE_ELEMENTS array.push("x"); // 元素類型: PACKED_ELEMENTS
到目前為止,我們已經(jīng)看到三種不同的元素,具有以下基本類型:
小整數(shù),又稱 Smi。
雙精度浮點(diǎn)數(shù),浮點(diǎn)數(shù)和不能表示為 Smi 的整數(shù)。
常規(guī)元素,不能表示為 Smi 或雙精度的值。
請(qǐng)注意,雙精度浮點(diǎn)數(shù)是 Smi 的更為一般的變體,而常規(guī)元素是雙精度浮點(diǎn)數(shù)之上的另一個(gè)概括??梢员硎緸?Smi 的數(shù)字集合是可以表示為
double 的數(shù)字的子集。
這里重要的一點(diǎn)是,元素種類轉(zhuǎn)換只能從一個(gè)方向進(jìn)行:從特定的(例如 PACKED_SMI_ELEMENTS)到更一般的(例如 PACKED_ELEMENTS)。例如,一旦數(shù)組被標(biāo)記為 PACKED_ELEMENTS,它就不能回到 PACKED_DOUBLE_ELEMENTS。
到目前為止,我們已經(jīng)學(xué)到了以下內(nèi)容:
V8 為每個(gè)數(shù)組分配一個(gè)元素種類。數(shù)組的元素種類并沒(méi)有被捆綁在一起 - 它可以在運(yùn)行時(shí)改變。在前面的例子中,我們從 PACKED_SMI_ELEMENTS 過(guò)渡到 PACKED_ELEMENTS。元素種類轉(zhuǎn)換只能從特定種類轉(zhuǎn)變?yōu)楦毡榈姆N類。
PACKED vs HOLEY密集數(shù)組 PACKED 和稀疏數(shù)組 HOLEY。
到目前為止,我們只處理密集或打包(PACKED)數(shù)組。在數(shù)組中創(chuàng)建稀疏數(shù)組將元素降級(jí)到其 HOLEY 變體:
const array = [1, 2, 3, 4.56, "x"]; // 元素類型: PACKED_ELEMENTS array.length; // 5 array[9] = 1; // array[5] until array[8] are now holes // 元素類型: HOLEY_ELEMENTS
V8 之所以做這個(gè)區(qū)別是因?yàn)?PACKED 數(shù)組的操作比在 HOLEY 數(shù)組上的操作更利于進(jìn)行優(yōu)化。對(duì)于 PACKED 數(shù)組,大多數(shù)操作可以有效執(zhí)行。相比之下, HOLEY 數(shù)組的操作需要對(duì)原型鏈進(jìn)行額外的檢查和昂貴的查找。
到目前為止,我們看到的每個(gè)基本元素(即 Smis,double 和常規(guī)元素)有兩種:PACKED 和 HOLEY。我們不僅可以從 PACKED_SMI_ELEMENTS 轉(zhuǎn)變?yōu)?PACKED_DOUBLE_ELEMENTS 我們也可以從任何 PACKED 形式轉(zhuǎn)變成 HOLEY 形式。
回顧一下:
最常見(jiàn)的元素種類 PACKED 和 HOLEY。PACKED 數(shù)組的操作比在 HOLEY 數(shù)組上的操作更為有效。元素種類可從過(guò)渡 PACKED 轉(zhuǎn)變?yōu)?HOLEY。
The elements kind lattice 元素種類的格V8 將這個(gè)變換系統(tǒng)實(shí)現(xiàn)為格(數(shù)學(xué)概念)。這是一個(gè)簡(jiǎn)化的可視化,僅顯示最常見(jiàn)的元素種類:
只能通過(guò)格子向下過(guò)渡。一旦將單精度浮點(diǎn)數(shù)添加到 Smi 數(shù)組中,即使稍后用 Smi 覆蓋浮點(diǎn)數(shù),它也會(huì)被標(biāo)記為 DOUBLE。類似地,一旦在數(shù)組中創(chuàng)建了一個(gè)洞,它將被永久標(biāo)記為有洞 HOLEY,即使稍后填充它也是如此。
V8 目前有 21 種不同的元素種類,每種元素都有自己的一組可能的優(yōu)化。
一般來(lái)說(shuō),更具體的元素種類可以進(jìn)行更細(xì)粒度的優(yōu)化。元素類型的在格子中越是向下,該對(duì)象的操作越慢。為了獲得最佳性能,請(qǐng)避免不必要的不具體類型 - 堅(jiān)持使用符合您情況的最具體的類型。
性能提示在大多數(shù)情況下,元素種類的跟蹤操作都隱藏在引擎下面,您不需要擔(dān)心。但是,為了從系統(tǒng)中獲得最大的收益,您可以采取以下幾方面。再次重申:更具體的元素種類可以進(jìn)行更細(xì)粒度的優(yōu)化。元素類型的在格子中越是向下,該對(duì)象的操作越慢。為了獲得最佳性能,請(qǐng)避免不必要的不具體類型 - 堅(jiān)持使用符合您情況的最具體的類型。
避免創(chuàng)建洞(hole)假設(shè)我們正在嘗試創(chuàng)建一個(gè)數(shù)組,例如:
const array = new Array(3); // 此時(shí),數(shù)組是稀疏的,所以它被標(biāo)記為 `HOLEY_SMI_ELEMENTS` // i.e. 給出當(dāng)前信息的最具體的可能性。 array[0] = "a"; // 接著,這是一個(gè)字符串,而不是一個(gè)小整數(shù)...所以過(guò)渡到`HOLEY_ELEMENTS`。 array[1] = "b"; array[2] = "c"; // 這時(shí),數(shù)組中的所有三個(gè)位置都被填充,所以數(shù)組被打包(即不再稀疏)。 // 但是,我們無(wú)法轉(zhuǎn)換為更具體的類型,例如 “PACKED_ELEMENTS”。 // 元素類保留為“HOLEY_ELEMENTS”。
一旦數(shù)組被標(biāo)記為有洞,它永遠(yuǎn)是有洞的 - 即使它被打包了!從那時(shí)起,數(shù)組上的任何操作都可能變慢。如果您計(jì)劃在數(shù)組上執(zhí)行大量操作,并且希望對(duì)這些操作進(jìn)行優(yōu)化,請(qǐng)避免在數(shù)組中創(chuàng)建空洞。V8 可以更有效地處理密集數(shù)組。
創(chuàng)建數(shù)組的一種更好的方法是使用字面量:
const array = ["a", "b", "c"]; // elements kind: PACKED_ELEMENTS
如果您提前不知道元素的所有值,那么可以創(chuàng)建一個(gè)空數(shù)組,然后再 push 值。
const array = []; // … array.push(someValue); // … array.push(someOtherValue);
這種方法確保數(shù)組不會(huì)被轉(zhuǎn)換為 holey elements。因此,V8 可以更有效地優(yōu)化數(shù)組上的任何操作。
避免讀取超出數(shù)組的長(zhǎng)度當(dāng)讀數(shù)超過(guò)數(shù)組的長(zhǎng)度時(shí),例如讀取 array[42] 時(shí),會(huì)發(fā)生類似的情況 array.length === 5。在這種情況下,數(shù)組索引 42 超出范圍,該屬性不存在于數(shù)組本身上,因此 JavaScript 引擎必須執(zhí)行相同的昂貴的原型鏈查找。
不要這樣寫(xiě)你的循環(huán):
// Don’t do this! for (let i = 0, item; (item = items[i]) != null; i++) { doSomething(item); }
該代碼讀取數(shù)組中的所有元素,然后再次讀取。直到它找到一個(gè)元素為 undefined 或 null 時(shí)停止。(jQuery 在幾個(gè)地方使用這種模式。)
相反,將你的循環(huán)寫(xiě)成老式的方式,只需要一直迭代到最后一個(gè)元素。
for (let index = 0; index < items.length; index++) { const item = items[index]; doSomething(item); }
當(dāng)你循環(huán)的集合是可迭代的(數(shù)組和 NodeLists),還有更好的選擇:只需要使用 for-of。
for (const item of items) { doSomething(item); }
對(duì)于數(shù)組,您可以使用內(nèi)置的 forEach:
items.forEach((item) => { doSomething(item); });
如今,兩者的性能 for-of 和 forEach 可以和舊式的 for 循環(huán)相提并論。
避免讀數(shù)超出數(shù)組的長(zhǎng)度!這樣做和數(shù)組中的洞一樣糟糕。在這種情況下,V8 的邊界檢查失敗,檢查屬性是否存在失敗,然后我們需要查找原型鏈。
避免元素種類轉(zhuǎn)換一般來(lái)說(shuō),如果您需要在數(shù)組上執(zhí)行大量操作,請(qǐng)嘗試堅(jiān)持盡可能具體的元素類型,以便 V8 可以盡可能優(yōu)化這些操作。
這比看起來(lái)更難。例如,只需給數(shù)組添加一個(gè) -0,一個(gè)小整數(shù)的數(shù)組即可將其轉(zhuǎn)換為 PACKED_DOUBLE_ELEMENTS。
const array = [3, 2, 1, +0]; // PACKED_SMI_ELEMENTS array.push(-0); // PACKED_DOUBLE_ELEMENTS
因此,此數(shù)組上的任何操作都將以與 Smi 完全不同的方式進(jìn)行優(yōu)化。
避免 -0,除非你需要在代碼中明確區(qū)分 -0 和 +0。(你可能并不需要)
同樣還有 NaN 和 Infinity。它們被表示為雙精度,因此添加一個(gè) NaN 或 Infinity 會(huì)將 SMI_ELEMENTS 轉(zhuǎn)換為
DOUBLE_ELEMENTS。
const array = [3, 2, 1]; // PACKED_SMI_ELEMENTS array.push(NaN, Infinity); // PACKED_DOUBLE_ELEMENTS
如果您計(jì)劃對(duì)整數(shù)數(shù)組執(zhí)行大量操作,在初始化的時(shí)候請(qǐng)考慮規(guī)范化 -0,并且防止 NaN 以及 Infinity。這樣數(shù)組就會(huì)保持 PACKED_SMI_ELEMENTS。
事實(shí)上,如果你對(duì)數(shù)組進(jìn)行數(shù)學(xué)運(yùn)算,可以考慮使用 TypedArray。每個(gè)數(shù)組都有專門(mén)的元素類型。
類數(shù)組對(duì)象 vs 數(shù)組JavaScript 中的某些對(duì)象 - 特別是在 DOM 中 - 雖然它們不是真正的數(shù)組,但是他們看起來(lái)像數(shù)組。可以自己創(chuàng)建類數(shù)組的對(duì)象:
const arrayLike = {}; arrayLike[0] = "a"; arrayLike[1] = "b"; arrayLike[2] = "c"; arrayLike.length = 3;
該對(duì)象具有 length 并支持索引元素訪問(wèn)(就像數(shù)組?。?,但它的原型上缺少數(shù)組方法,如 forEach。盡管如此,仍然可以調(diào)用數(shù)組泛型:
Array.prototype.forEach.call(arrayLike, (value, index) => { console.log(`${ index }: ${ value }`); }); // This logs "0: a", then "1: b", and finally "2: c".
這個(gè)代碼工作原理如下,在類數(shù)組對(duì)象上調(diào)用數(shù)組內(nèi)置的 Array.prototype.forEach。但是,這比在真正的數(shù)組中調(diào)用 forEach 慢,引擎數(shù)組的 forEach 在 V8 中是高度優(yōu)化的。如果你打算在這個(gè)對(duì)象上多次使用數(shù)組內(nèi)置函數(shù),可以考慮先把它變成一個(gè)真正的數(shù)組:
const actualArray = Array.prototype.slice.call(arrayLike, 0); actualArray.forEach((value, index) => { console.log(`${ index }: ${ value }`); }); // This logs "0: a", then "1: b", and finally "2: c".
為了后續(xù)的優(yōu)化,進(jìn)行一次性轉(zhuǎn)換的成本是值得的,特別是如果您計(jì)劃在數(shù)組上執(zhí)行大量操作。
例如,arguments 對(duì)象是類數(shù)組的對(duì)象??梢栽谄渖险{(diào)用數(shù)組內(nèi)置函數(shù),但是這樣的操作將不會(huì)被完全優(yōu)化,因?yàn)檫@些優(yōu)化只針對(duì)真正的數(shù)組。
const logArgs = function() { Array.prototype.forEach.call(arguments, (value, index) => { console.log(`${ index }: ${ value }`); }); }; logArgs("a", "b", "c"); // This logs "0: a", then "1: b", and finally "2: c".
ES2015 的 rest 參數(shù)在這里很有幫助。它們產(chǎn)生真正的數(shù)組,可以優(yōu)雅的代替類似數(shù)組的對(duì)象 arguments。
const logArgs = (...args) => { args.forEach((value, index) => { console.log(`${ index }: ${ value }`); }); }; logArgs("a", "b", "c"); // This logs "0: a", then "1: b", and finally "2: c".
如今,沒(méi)有理由直接使用對(duì)象 arguments。
通常,盡可能避免使用數(shù)組類對(duì)象,應(yīng)該使用真正的數(shù)組。
避免多態(tài)如果您的代碼需要處理包含多種不同元素類型的數(shù)組,則可能會(huì)比單個(gè)元素類型數(shù)組要慢,因?yàn)槟愕拇a要對(duì)不同類型的數(shù)組元素進(jìn)行多態(tài)操作。
考慮以下示例,其中使用了各種元素種類調(diào)用。(請(qǐng)注意,這不是本機(jī) Array.prototype.forEach,它具有自己的一些優(yōu)化,這些優(yōu)化不同于本文中討論的元素種類優(yōu)化。)
const each = (array, callback) => { for (let index = 0; index < array.length; ++index) { const item = array[index]; callback(item); } }; const doSomething = (item) => console.log(item); each([], () => {}); each(["a", "b", "c"], doSomething); // `each` is called with `PACKED_ELEMENTS`. V8 uses an inline cache // (or “IC”) to remember that `each` is called with this particular // elements kind. V8 is optimistic and assumes that the // `array.length` and `array[index]` accesses inside the `each` // function are monomorphic (i.e. only ever receive a single kind // of elements) until proven otherwise. For every future call to // `each`, V8 checks if the elements kind is `PACKED_ELEMENTS`. If // so, V8 can re-use the previously-generated code. If not, more work // is needed. each([1.1, 2.2, 3.3], doSomething); // `each` is called with `PACKED_DOUBLE_ELEMENTS`. Because V8 has // now seen different elements kinds passed to `each` in its IC, the // `array.length` and `array[index]` accesses inside the `each` // function get marked as polymorphic. V8 now needs an additional // check every time `each` gets called: one for `PACKED_ELEMENTS` // (like before), a new one for `PACKED_DOUBLE_ELEMENTS`, and one for // any other elements kinds (like before). This incurs a performance // hit. each([1, 2, 3], doSomething); // `each` is called with `PACKED_SMI_ELEMENTS`. This triggers another // degree of polymorphism. There are now three different elements // kinds in the IC for `each`. For every `each` call from now on, yet // another elements kind check is needed to re-use the generated code // for `PACKED_SMI_ELEMENTS`. This comes at a performance cost.
內(nèi)置方法(如 Array.prototype.forEach)可以更有效地處理這種多態(tài)性,因此在性能敏感的情況下考慮使用它們而不是用戶庫(kù)函數(shù)。
V8 中單態(tài)與多態(tài)的另一個(gè)例子涉及對(duì)象形狀(object shape),也稱為對(duì)象的隱藏類。要了解更多,請(qǐng)查看 Vyacheslav 的文章。
調(diào)試元素種類找出一個(gè)給定的對(duì)象的“元素種類”,可以使用一個(gè)調(diào)試版本 d8(參見(jiàn)“從源代碼構(gòu)建”),并運(yùn)行:
$ out.gn/x64.debug/d8 --allow-natives-syntax
這將打開(kāi) d8 REPL 中的特殊函數(shù),如 %DebugPrint(object)。輸出中的“元素”字段顯示您傳遞給它的任何對(duì)象的“元素種類”。
d8> const array = [1, 2, 3]; %DebugPrint(array); DebugPrint: 0x1fbbad30fd71: [JSArray] - map = 0x10a6f8a038b1 [FastProperties] - prototype = 0x1212bb687ec1 - elements = 0x1fbbad30fd19[PACKED_SMI_ELEMENTS (COW)] - length = 3 - properties = 0x219eb0702241 { #length: 0x219eb0764ac9 (const accessor descriptor) } - elements= 0x1fbbad30fd19 { 0: 1 1: 2 2: 3 } […]
請(qǐng)注意,“COW” 表示寫(xiě)時(shí)復(fù)制,這是另一個(gè)內(nèi)部?jī)?yōu)化。現(xiàn)在不要擔(dān)心 - 這是另一個(gè)博文的主題!
調(diào)試版本中可用的另一個(gè)有用的標(biāo)志是 --trace-elements-transitions。啟用它讓 V8 在任何元素發(fā)生類型轉(zhuǎn)換時(shí)通知您。
$ cat my-script.js const array = [1, 2, 3]; array[3] = 4.56; $ out.gn/x64.debug/d8 --trace-elements-transitions my-script.js elements transition [PACKED_SMI_ELEMENTS -> PACKED_DOUBLE_ELEMENTS] in ~+34 at x.js:2 for 0x1df87228c911from 0x1df87228c889 to 0x1df87228c941
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/88587.html
摘要:與異步編程按照維基百科上的解釋獨(dú)立于主控制流之外發(fā)生的事件就叫做異步。因?yàn)榈拇嬖?,至少在被?biāo)準(zhǔn)化的那一刻起,就支持異步編程了。然而異步編程真正發(fā)展壯大,的流行功不可沒(méi)。在握手過(guò)程中,端點(diǎn)交換認(rèn)證和密鑰以建立或恢復(fù)安全會(huì)話。 1、前端 排序算法總結(jié) 排序算法可能是你學(xué)編程第一個(gè)學(xué)習(xí)的算法,還記得冒泡嗎? 當(dāng)然,排序和查找兩類算法是面試的熱門(mén)選項(xiàng)。如果你是一個(gè)會(huì)寫(xiě)快排的程序猿,面試官在比較...
摘要:與異步編程按照維基百科上的解釋獨(dú)立于主控制流之外發(fā)生的事件就叫做異步。因?yàn)榈拇嬖?,至少在被?biāo)準(zhǔn)化的那一刻起,就支持異步編程了。然而異步編程真正發(fā)展壯大,的流行功不可沒(méi)。在握手過(guò)程中,端點(diǎn)交換認(rèn)證和密鑰以建立或恢復(fù)安全會(huì)話。 1、前端 排序算法總結(jié) 排序算法可能是你學(xué)編程第一個(gè)學(xué)習(xí)的算法,還記得冒泡嗎? 當(dāng)然,排序和查找兩類算法是面試的熱門(mén)選項(xiàng)。如果你是一個(gè)會(huì)寫(xiě)快排的程序猿,面試官在比較...
摘要:與異步編程按照維基百科上的解釋獨(dú)立于主控制流之外發(fā)生的事件就叫做異步。因?yàn)榈拇嬖?,至少在被?biāo)準(zhǔn)化的那一刻起,就支持異步編程了。然而異步編程真正發(fā)展壯大,的流行功不可沒(méi)。在握手過(guò)程中,端點(diǎn)交換認(rèn)證和密鑰以建立或恢復(fù)安全會(huì)話。 1、前端 排序算法總結(jié) 排序算法可能是你學(xué)編程第一個(gè)學(xué)習(xí)的算法,還記得冒泡嗎? 當(dāng)然,排序和查找兩類算法是面試的熱門(mén)選項(xiàng)。如果你是一個(gè)會(huì)寫(xiě)快排的程序猿,面試官在比較...
摘要:可以更有效地處理密集數(shù)組。然后有人提出了一個(gè)疑問(wèn)為什么先指定長(zhǎng)度再初始化測(cè)試出來(lái)會(huì)快一點(diǎn)其實(shí),兩者相比只是可能變慢。具體因素有很多,比如預(yù)分配一個(gè)很大的數(shù)組,這時(shí)可以變快,的函數(shù)就是這么做的。如果數(shù)組很大,預(yù)先分配大小后性能反而會(huì)提升。 在我的上一篇文章 JavaScript 在 V8 中的元素種類及性能優(yōu)化 中寫(xiě)道: showImg(https://segmentfault.com...
摘要:這些是中可用的最快屬性。通常來(lái)說(shuō)我們將線性屬性存儲(chǔ)中存儲(chǔ)的屬性稱為。因此也支持所謂的屬性。整數(shù)索引屬性的處理和命名屬性的復(fù)雜性相同。 本文為譯文,原文地址:http://v8project.blogspot.com...,作者,@Camillo Bruni ,V8 JavaScript Engine Team Blog 在這篇博客中,我們想解釋 V8 如何在內(nèi)部處理 JavaScrip...
閱讀 958·2021-11-23 09:51
閱讀 1202·2021-11-15 17:57
閱讀 1717·2021-09-22 15:24
閱讀 864·2021-09-07 09:59
閱讀 2295·2019-08-29 15:10
閱讀 1905·2019-08-29 12:47
閱讀 816·2019-08-29 12:30
閱讀 3454·2019-08-26 13:51