摘要:這些是中可用的最快屬性。通常來(lái)說(shuō)我們將線性屬性存儲(chǔ)中存儲(chǔ)的屬性稱為。因此也支持所謂的屬性。整數(shù)索引屬性的處理和命名屬性的復(fù)雜性相同。
本文為譯文,原文地址:http://v8project.blogspot.com...,作者,@Camillo Bruni ,V8 JavaScript Engine Team Blog
在這篇博客中,我們想解釋 V8 如何在內(nèi)部處理 JavaScript 屬性。從 JavaScript 的角度來(lái)看,屬性只有一些區(qū)別。JavaScript 對(duì)象主要表現(xiàn)為字典,字符串作為鍵名以及任意對(duì)象作為鍵值。然而,該規(guī)范在迭代過(guò)程中對(duì)整數(shù)索引(integer-indexed)屬性和其它屬性進(jìn)行了不同的處理。除此之外,不同的屬性的行為大致相同,與它們是否為整數(shù)索引無(wú)關(guān)。
然而,在 V8 引擎下,由于性能和內(nèi)存的原因,確實(shí)依賴于幾種不同的屬性表示。在這篇博客中,我們將介紹 V8 如何在處理動(dòng)態(tài)添加的屬性時(shí)提供快速的屬性訪問(wèn)。了解屬性的工作原理對(duì)于解釋諸如內(nèi)聯(lián)緩存(inline caches)在 V8 中的優(yōu)化是至關(guān)重要的。
這篇博客解釋了處理整數(shù)索引和命名屬性(named properties)的區(qū)別。之后我們展示了當(dāng)添加命名屬性時(shí) V8 如何維護(hù) HiddenClasses,便于提供一種快速的方式來(lái)識(shí)別對(duì)象的形狀。然后,我們將繼續(xù)深入了解命名屬性如何針對(duì)快速訪問(wèn)進(jìn)行優(yōu)化,或依據(jù)用途進(jìn)行快速修改。在最后一個(gè)章節(jié),我們將提供有關(guān) V8 如何處理整數(shù)索引或數(shù)組索引(array indices)的詳細(xì)信息。
命名屬性(Named Properties)和元素(Elements)我們首先來(lái)分析一個(gè)簡(jiǎn)單的對(duì)象,如 {a: "foo", b: "bar"}。該對(duì)象有兩個(gè)命名屬性,“a” 和 “b”。它是沒(méi)有任何屬性名稱的整數(shù)索引。數(shù)組索引(array-indexed properties)的屬性(通常稱為元素),在數(shù)組上最為突出。例如,數(shù)組 ["foo", "bar"],有兩個(gè)數(shù)組索引屬性:0,值為“foo”,1,值為“bar”。這是 V8 處理屬性的第一個(gè)主要區(qū)別。
下圖顯示了一個(gè)基本的 JavaScript 對(duì)象在內(nèi)存中的樣子。
元素和屬性存儲(chǔ)在兩個(gè)多帶帶的數(shù)據(jù)結(jié)構(gòu)中,這使得添加和訪問(wèn)屬性或元素對(duì)于不同的使用模式更有效。
元素主要用于各種 Array.prototype 方法,如 pop 或 slice。假設(shè)這些函數(shù)訪問(wèn)連續(xù)范圍內(nèi)的屬性,V8 大部分時(shí)間上也將它們內(nèi)部表示為簡(jiǎn)單的數(shù)組。在這篇文章的后面,我們將會(huì)解釋我們?nèi)绾吻袚Q到基于稀疏字典的表示(sparse dictionary-based representation)來(lái)節(jié)省內(nèi)存。
命名屬性以類似的方式存儲(chǔ)在多帶帶的數(shù)組中。然而,不同于元素,我們不能簡(jiǎn)單地使用鍵推斷它所在數(shù)組中的位置,我們需要一些額外的元數(shù)據(jù)。在 V8 中,每個(gè) JavaScript 對(duì)象都有一個(gè) HiddenClass 關(guān)聯(lián)。HiddenClass 存儲(chǔ)有關(guān)對(duì)象形狀的信息,其中包括從屬性名到索引再到屬性的映射。為了是事情復(fù)雜化,我們有時(shí)會(huì)為屬性而不是簡(jiǎn)單的數(shù)組使用字典。我們將在專門的章節(jié)中更詳細(xì)地解釋這一點(diǎn)。
從這一節(jié)開(kāi)始:
數(shù)組索引的屬性存儲(chǔ)在多帶帶的元素存儲(chǔ)中。
命名的屬性存儲(chǔ)在屬性存儲(chǔ)中。
元素和屬性可以是數(shù)組或字典。
每個(gè) JavaScript 對(duì)象都有一個(gè)關(guān)聯(lián)的 HiddenClass,它保存關(guān)于對(duì)象形狀的信息。
HiddenClass 和 DescriptorArrays在解釋元素和命名屬性的區(qū)別之后,我們需要看看 HiddenClass 在 V8 中的工作原理。HiddenClass 存儲(chǔ)有關(guān)對(duì)象的元信息,包括對(duì)象上的屬性以及對(duì)象原型的引用數(shù)量。HiddenClass 在概念上類似與典型的面向?qū)ο缶幊陶Z(yǔ)言中的類。然而,在基于原型的語(yǔ)言(如 JavaScript )中,通常不可能預(yù)先知道類。因此,在這種情況下,V8 引擎的 HiddenClass 是隨機(jī)創(chuàng)建的,并隨著對(duì)象的改變而動(dòng)態(tài)更新。HiddenClass 作為對(duì)象形狀的標(biāo)識(shí)符,并且是 V8 優(yōu)化編譯器和內(nèi)聯(lián)緩存(inline caches)的一個(gè)非常重要的組成部分。例如,優(yōu)化編輯器可以直接內(nèi)聯(lián)屬性訪問(wèn),如果它可以通過(guò) HiddenClass 確保兼容對(duì)象結(jié)構(gòu)。
讓我們來(lái)看看 HiddenClass 的重要部分。
在 V8 中,JavaScript 對(duì)象的第一個(gè)字段指向一個(gè) HiddenClass。(實(shí)際上,這是在 V8 堆上由垃圾收集器管理的任何對(duì)象的情況)。在屬性方面,最重要的信息是存儲(chǔ)屬性數(shù)量的第三位字段和指向描述符數(shù)組的指針。描述符數(shù)組包含有關(guān)命名屬性的信息,如名稱本身和存儲(chǔ)值的位置。請(qǐng)注意,我們?cè)谶@里不跟蹤整數(shù)索引屬性,因此描述符數(shù)組中沒(méi)有條目。
關(guān)于 HiddenClass 的基本假設(shè)是具有相同結(jié)構(gòu)的對(duì)象。例如,相同的命名屬性以相同的順序共享相同的 HiddenClass。為了實(shí)現(xiàn)這一點(diǎn),當(dāng)一個(gè)屬性被添加到一個(gè)對(duì)象時(shí),我們使用一個(gè)不同的 HiddenClass。在下面的例子中,我們從一個(gè)空對(duì)象開(kāi)始,并添加三個(gè)命名屬性。
每次添加新的屬性時(shí),對(duì)象的 HiddenClass 都會(huì)被更改。在后臺(tái) V8 創(chuàng)建一個(gè)將 HiddenClass 鏈接在一起的轉(zhuǎn)換樹(shù)。V8 知道當(dāng)你向空對(duì)象添加屬性“a”時(shí)要使用哪個(gè) HiddenClass。如果以相同的順序添加相同的屬性,則此轉(zhuǎn)換樹(shù)將確保最終具有相同的最終 HiddenClass。以下實(shí)例顯示,即使我們?cè)趦烧咧g添加簡(jiǎn)單的索引屬性,也將遵循相同的轉(zhuǎn)換樹(shù)。
然而,如果我們創(chuàng)建一個(gè)新的對(duì)象來(lái)獲取不同的屬性,在這種情況下,屬性“b”,V8 將為新的 HiddenClass 創(chuàng)建一個(gè)多帶帶的分支。
從本節(jié)開(kāi)始:
具有相同結(jié)構(gòu)(相同屬性的相同順序)的對(duì)象具有相同的 HiddenClass。
默認(rèn)情況下,添加的每個(gè)新的命名屬性都將創(chuàng)建一個(gè)新的 HiddenClass。
添加數(shù)組索引屬性不會(huì)創(chuàng)建新的 HiddenClass。
三種不同類型的命名屬性在概述 V8 如何使用 HiddenClass 跟蹤對(duì)象的形狀之后,讓我們來(lái)看就這些屬性實(shí)際是如何存儲(chǔ)的。如上面的介紹所述,有兩種基本類型的屬性:命名和索引。以下部分包含命名屬性。
一個(gè)簡(jiǎn)單的對(duì)象,如 {a: 1, b: 2},可以在 V8 中有各種內(nèi)部表現(xiàn)。雖然 JavaScript 的行為或多或少與外部的簡(jiǎn)單字典相似,但 V8 視圖避免使用字典,因?yàn)樗鼈冏璧K了一些優(yōu)化,例如內(nèi)聯(lián)緩存,我們將在多帶帶的文章中解釋。
In-object 和 Normal Properties:V8 支持直接存儲(chǔ)在對(duì)象本身上的所謂 in-object 屬性。這些是 V8 中可用的最快屬性。因?yàn)樗鼈兛梢詿o(wú)間接訪問(wèn)。對(duì)象 in-object 的數(shù)量由對(duì)象的初始大小預(yù)先確定。如果對(duì)象中有空格添加了更多屬性,它們將被存儲(chǔ)在屬性存儲(chǔ)中。屬性存儲(chǔ)添加了一個(gè)間接級(jí)別,但可以獨(dú)立生長(zhǎng)。
fast 和 slow 屬性:下一個(gè)重要區(qū)別在于 fast 和 slow 之間的屬性。通常來(lái)說(shuō)我們將線性屬性存儲(chǔ)中存儲(chǔ)的屬性稱為“fast”。fast 屬性是可以簡(jiǎn)單的通過(guò)索引來(lái)訪問(wèn)的。要從屬性的名稱到屬性存儲(chǔ)中的實(shí)際位置,我們必須先查看 HiddenClass 中的描述符數(shù)組,如前所述。
然而,如果許多屬性從對(duì)象中添加和刪除,則可能會(huì)生成大量時(shí)間和內(nèi)存開(kāi)銷來(lái)維護(hù)描述符數(shù)組和 HiddenClass。因此,V8 也支持所謂的 slow 屬性。具有 slow 屬性的對(duì)象具有自包含的字典作為屬性存儲(chǔ)。所有屬性元信息不再存儲(chǔ)在 HiddenClass 中的描述符數(shù)組中,而是直接存儲(chǔ)在屬性字典中。因此,可以添加和刪除屬性,而無(wú)需更新 HiddenClass。由于內(nèi)聯(lián)緩存不能與字典屬性一起使用,后者通常比 fast 屬性慢。
從這一節(jié)開(kāi)始:
有三種不同的命名屬性類型:in-object,fast 和 slow 字典。
in-object 屬性直接存儲(chǔ)在對(duì)象本身上,并提供最快訪問(wèn)。
fast 屬性存儲(chǔ)在屬性中,所有元信息都存儲(chǔ)在 HiddenClass 的描述符數(shù)組中。
slow 屬性存儲(chǔ)在自包含(self-contained)屬性字典中,元信息不再通過(guò) HiddenClass 共享
slow 屬性允許有效的屬性刪除和添加,但訪問(wèn)速度比其他兩種類型更慢。
元素或數(shù)組索引屬性到目前為止,我們已經(jīng)查看了命名屬性,忽略了常用于數(shù)組的整數(shù)索引屬性。整數(shù)索引屬性的處理和命名屬性的復(fù)雜性相同。即使所有索引屬性始終在元素存儲(chǔ)中多帶帶存儲(chǔ),也有 20 種不同類型的元素!
Packed 或 Holey 元素:V8 做出的第一個(gè)主要區(qū)別是元素是否支持存儲(chǔ)打包(packed)或有空位(holes)。如果你刪除索引元素,或者你沒(méi)有定義它,你將在后臺(tái)存儲(chǔ)中找到空位。一個(gè)簡(jiǎn)單的例子是 [1,,3],第二個(gè)條目是一個(gè)空位。下面的例子說(shuō)明了這個(gè)問(wèn)題:
const o = "a", "b", "c" (); console.log(o1 ()); // Prints "b". delete o1 (); // Introduces a hole in the elements store. console.log(o1 ()); // Prints "undefined"; property 1 does not exist. o.proto = {1: "B"}; // Define property 1 on the prototype. console.log(o0 ()); // Prints "a". console.log(o1 ()); // Prints "B". console.log(o2 ()); // Prints "c". console.log(o3 ()); // Prints undefined
簡(jiǎn)單來(lái)說(shuō),如果接收方不存在屬性,則必須繼續(xù)查找原型鏈。鑒于元素是獨(dú)立的,例如我們不在 HiddenClass 上存儲(chǔ)有關(guān)當(dāng)前索引屬性的信息,因此我們需要一個(gè)名為 the_hole 的特殊值來(lái)標(biāo)記不存在的屬性。這對(duì)于數(shù)組非常重要。如果我們知道沒(méi)有空位,即元素存儲(chǔ)被打包,我們可以執(zhí)行本地操作,而不必浪費(fèi)在原型鏈上查找。
Fast 或 Dictionary 元素:元素上第二個(gè)主要的區(qū)別是它們是 fast 還是 dictionary 模式。fast 元素是簡(jiǎn)單的 VM 內(nèi)部數(shù)組,其中屬性索引映射到元素存儲(chǔ)中的索引。然而,對(duì)于只有少數(shù)條目被占用的非常大的 sparse/holey 數(shù)組,這幾乎是相當(dāng)浪費(fèi)的。在這種情況下,我們使用基于字典的表示形式來(lái)節(jié)省內(nèi)存,代價(jià)是訪問(wèn)速度稍慢:
const sparseArray = (); sparseArray9999 () = "foo"; // Creates an array with dictionary elements.
在這個(gè)例子中,使用 10k 條目分配一個(gè)完整的數(shù)組那是相當(dāng)浪費(fèi)的。而 V8 會(huì)創(chuàng)建一個(gè)字典,我們存儲(chǔ)一個(gè)鍵值描述符三元組。在這個(gè)例子中,鍵名會(huì)是 9999,鍵值為 “foo” 和默認(rèn)描述符。鑒于我們沒(méi)有辦法在 HiddenClass 上存儲(chǔ)描述符詳細(xì)信息,所以當(dāng)你使用自定義描述符定義索引屬性時(shí),V8 將采用 slow 元素:
const array = (); Object.defineProperty(array, 0, {value: "fixed", configurable: false}); console.log(array0 ()); // Prints "fixed". array0 () = "other value"; // Cannot override index 0. console.log(array0 ()); // Still prints "fixed".
在這個(gè)例子中,我們?cè)跀?shù)組中添加了一個(gè)不可配置的屬性。該信息存儲(chǔ)在 slow 元素字典三元組的描述符部分中。重要的是要注意,對(duì)于具有 slow 元素的對(duì)象,Array 函數(shù)執(zhí)行的相當(dāng)慢。
Smi 和 Double 元素:對(duì)于 fast 元素,V8 中還有另一個(gè)重要區(qū)別。例如,如果只將數(shù)組中的整數(shù)存儲(chǔ)在一個(gè)常見(jiàn)的用例中,則 GC 不必查看數(shù)組,因?yàn)檎麛?shù)直接編碼為所謂的小整數(shù)(Smis)。另一個(gè)特殊情況是數(shù)組只包含 doubles。與 Smis 不同,浮點(diǎn)數(shù)通常表示為占據(jù)多個(gè)單詞的完整對(duì)象。然而,V8 存儲(chǔ)純雙數(shù)組的原始雙精度,以避免內(nèi)存和性能開(kāi)銷。以下示例列出了 Smis 和 double 元素的 4 個(gè)示例:
const a1 = 1, 2, 3 (); // Smi Packed const a2 = 1, , 3 (); // Smi Holey, a21 () reads from the prototype const b1 = 1.1, 2, 3 (); // Double Packed const b2 = 1.1, , 3 (); // Double Holey, b21 () reads from the prototype
特殊元素:目前為止,我們涵蓋了 20 種不同元素中的 7 種。為了簡(jiǎn)單起見(jiàn),我們排除了 TpyedArrays 的 9 個(gè)元素類型,以及兩個(gè)用于字符串包裝,最后剩下兩個(gè)更特殊的元素種類的參數(shù)對(duì)象。
ElementsAccessor: 可以想象,我們并不是完全熱衷于在 C++ 中編寫(xiě)數(shù)組函數(shù) 20 次,對(duì)于每一個(gè)元素都是一樣。那就是展現(xiàn) C++ 魔法的時(shí)刻了,而不是一遍遍地實(shí)現(xiàn) Array 函數(shù),我們構(gòu)建了 ElementsAccessor,只需要實(shí)現(xiàn)從后備存儲(chǔ)器訪問(wèn)元素的簡(jiǎn)單函數(shù)。ElementsAccessor 依賴于 CRTP 來(lái)創(chuàng)建每個(gè) Array 函數(shù)的專用版。因此,如果你在數(shù)組上調(diào)用 slice,V8 就會(huì)內(nèi)部調(diào)用 C++ 編寫(xiě)的內(nèi)建函數(shù),并通過(guò) ElementsAccessor 調(diào)用該函數(shù)的專用版本:
從這一節(jié)開(kāi)始:
fast 和字典模式索引的屬性和元素
fast 屬性可以打包,也可以包含指示索引屬性已被刪除的空位。
元素專門針對(duì)其內(nèi)容來(lái)加快陣列功能并降低 GC 開(kāi)銷。
了解屬性的工作原理是 V8 中許多優(yōu)化的關(guān)鍵。對(duì)于 JavaScript 開(kāi)發(fā)者,許多內(nèi)部決策不是直接可見(jiàn)的,但它們解釋了為什么某些代碼模式比其他代碼模式更快。更改屬性或元素類型通常會(huì)導(dǎo)致 V8 創(chuàng)建一個(gè)不同的 HiddenClass,這可能導(dǎo)致類似污染,從而阻止 V8 生成最佳代碼。請(qǐng)繼續(xù)關(guān)注 V8 的內(nèi)部虛擬機(jī)的工作原理。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/91790.html
摘要:內(nèi)存結(jié)構(gòu)與屬性訪問(wèn)詳解從屬于筆者的前端入門與工程實(shí)踐,推薦閱讀我的前端之路工具化與工程化。內(nèi)存結(jié)構(gòu)與屬性訪問(wèn)上世紀(jì)九十年代,隨著網(wǎng)景瀏覽器的發(fā)行,首次進(jìn)入人們的視線。 V8 Object 內(nèi)存結(jié)構(gòu)與屬性訪問(wèn)詳解從屬于筆者的Web 前端入門與工程實(shí)踐,推薦閱讀2016-我的前端之路:工具化與工程化。更多關(guān)于 JavaScript 引擎文章參考這里。 V8 Object 內(nèi)存結(jié)構(gòu)與屬性訪問(wèn)...
摘要:可以更有效地處理密集數(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...
摘要:內(nèi)容爲(wèi)該問(wèn)題下的答案是對(duì)的學(xué)習(xí)筆記。一個(gè)叫做的過(guò)程用來(lái)決定對(duì)象的大小,其後的屬性作爲(wèi)使用單獨(dú)的數(shù)組儲(chǔ)存。其中,包括然而有時(shí)也會(huì)降級(jí)爲(wèi)當(dāng)然由於分開(kāi)儲(chǔ)存,降級(jí)並不會(huì)影響到其它類型的屬性。 內(nèi)容爲(wèi)該問(wèn)題下的答案:http://segmentfault.com/q/1010000002423380 是對(duì) http://jayconrod.com/posts/52/a-tour-of-v8-o...
摘要:對(duì)于每個(gè)前端程序員來(lái)講都有一個(gè)終極理想,那就是搞懂引擎是如何工作的。性能經(jīng)過(guò)了兩次飛躍第次飛躍是年發(fā)布,第次則是年的。從去年底開(kāi)始連載源碼分析,記錄一下自己學(xué)習(xí)源碼的點(diǎn)點(diǎn)滴滴。月星期六晚點(diǎn)和大家一起聊聊引擎前端程序員應(yīng)該懂點(diǎn)知識(shí)講堂。 對(duì)于每個(gè)前端程序員來(lái)講都有一個(gè)終極理想,那就是搞懂 javascript 引擎是如何工作的。 從我的網(wǎng)絡(luò) ID(justjavac)可以看出來(lái),當(dāng)我開(kāi)始...
摘要:所做的最重要的事情,就是對(duì)成千上萬(wàn)的網(wǎng)頁(yè)進(jìn)行排序,所以它存在的意義是基于網(wǎng)頁(yè)的。確實(shí)有很多非常成功的產(chǎn)品,比如,,,但是它們其實(shí)都是收購(gòu)來(lái)的。為什么呢因?yàn)橐龅綐O簡(jiǎn)主義,需要深刻思考用戶需求以及產(chǎn)品價(jià)值。 摘要: Chrome改變世界。 《JavaScript深入淺出》系列: JavaScript深入淺出第1課:箭頭函數(shù)中的this究竟是什么鬼? JavaScript深入淺出第2課:...
閱讀 2612·2023-04-26 02:57
閱讀 1504·2023-04-25 21:40
閱讀 2343·2021-11-24 09:39
閱讀 3656·2021-08-30 09:49
閱讀 838·2019-08-30 15:54
閱讀 1229·2019-08-30 15:52
閱讀 2235·2019-08-30 15:44
閱讀 1328·2019-08-28 18:27