日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
深入了解JavaScript引擎精華

本文來自 Google 引擎 V8 工程師 Mathias 和 Benedikt 在 JSConf EU 2018 上的演講。他們對所有 JavaScript 引擎中常見的一些關(guān)鍵基礎(chǔ)內(nèi)容進(jìn)行了介紹。

在天全等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站制作、成都做網(wǎng)站 網(wǎng)站設(shè)計(jì)制作按需定制開發(fā),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),營銷型網(wǎng)站,成都外貿(mào)網(wǎng)站建設(shè)公司,天全網(wǎng)站建設(shè)費(fèi)用合理。

作為一名 JavaScript 開發(fā)者,深入了解 JavaScript 引擎是如何工作的將有助于你了解自己所寫代碼的性能特征。

全文共由五個(gè)部分組成:

1.JavaScript 引擎工作流程:介紹 JavaScript 引擎的處理流水線,這一部分會涉及到解釋器/編譯器的內(nèi)容,且會分點(diǎn)介紹不同引擎間的差別與共同點(diǎn);

2.JavaScript 對象模型;

3. 屬性訪問的優(yōu)化:通過 Shapes、Transistion 鏈與樹、ICs 等概念的穿插介紹引擎是如何優(yōu)化獲取對象屬性的;

4. 高效存儲數(shù)組;

5.Take-aways:對全文內(nèi)容做了一個(gè)小結(jié),并給了兩點(diǎn)建議。

JavaScript 引擎工作流程

JavaScript 引擎在解析源碼后將其轉(zhuǎn)換為抽象語法樹(AST),基于 AST,解釋器便可以開始工作并產(chǎn)生字節(jié)碼,此時(shí)引擎正在執(zhí)行 JavaScript 代碼。

為了使它執(zhí)行得更快,可以將字節(jié)碼與分析數(shù)據(jù)(profiling data)一起發(fā)給優(yōu)化編譯器。優(yōu)化編譯器根據(jù)已有的分析數(shù)據(jù)做出特定假設(shè),然后生成高度優(yōu)化的機(jī)器碼。

如果在某點(diǎn)上一個(gè)假設(shè)被證明是不正確的,那么優(yōu)化編譯器會去優(yōu)化并回退至解釋器部分。

 JavaScript 引擎中的解釋器 / 編譯器流程

現(xiàn)在,讓我們關(guān)注實(shí)際執(zhí)行 JavaScript 代碼的這部分流程,即代碼被解釋和優(yōu)化的地方,并討論其在主要的 JavaScript 引擎之間存在的一些差異。

一般來說,所有 JavaSciript 引擎都有一個(gè)包含解釋器和優(yōu)化編譯器的處理流程。其中,解釋器可以快速生成未優(yōu)化的字節(jié)碼,而優(yōu)化編譯器會需要更長的時(shí)間,以便最終生成高度優(yōu)化的機(jī)器碼。

這個(gè)通用流程幾乎與在 Chrome 和 Node.js 中使用的 V8 引擎工作流程一致:

V8 中的解釋器被稱作 Ignition,它負(fù)責(zé)生成并執(zhí)行字節(jié)碼。當(dāng)它運(yùn)行字節(jié)碼時(shí)會收集分析數(shù)據(jù),而它之后可以被用于加快(代碼)執(zhí)行的速度。當(dāng)一個(gè)函數(shù)變得 hot,例如它經(jīng)常被調(diào)用,生成的字節(jié)碼和分析數(shù)據(jù)則會被傳給 TurboFan——我們的優(yōu)化編譯器,它會依據(jù)分析數(shù)據(jù)生成高度優(yōu)化的機(jī)器碼。

SpiderMonkey,在 Firefox 和 SpiderNode 中使用的 Mozilla 的 JavaScript 引擎,則有一些不同的地方。它們有兩個(gè)優(yōu)化編譯器。解釋器將代碼解釋給 Baseline 編譯器,該編譯器可以生成部分優(yōu)化的代碼。 結(jié)合運(yùn)行代碼時(shí)收集的分析數(shù)據(jù),IonMonkey 編譯器可以生成高度優(yōu)化的代碼。 如果嘗試優(yōu)化失敗,IonMonkey 將回退到 Baseline 階段的代碼。

Chakra,用于 Edge 和 Node-ChakraCore 兩個(gè)項(xiàng)目的微軟 JavaScript 引擎,也有類似兩個(gè)優(yōu)化編譯器的設(shè)置。解釋器將代碼優(yōu)化成 SimpleJIT——其中 JIT 代表 Just-In-Time 編譯器——它可以生成部分優(yōu)化的代碼。 結(jié)合分析數(shù)據(jù),F(xiàn)ullJIT 可以生成更深入優(yōu)化的代碼。

JavaScriptCore(縮寫為 JSC),Apple 的 JavaScript 引擎,被用于 Safari 和 React Native 兩個(gè)項(xiàng)目中,它通過三種不同的優(yōu)化編譯器使效果達(dá)到***。低級解釋器 LLInt 將代碼解釋后傳遞給 Baseline 編譯器,而(經(jīng)過 Baseline 編譯器)優(yōu)化后的代碼便傳給了 DFG 編譯器,(在 DFG 編譯器處理后)結(jié)果最終傳給了 FTL 編譯器進(jìn)行處理。

為什么有些引擎會擁有更多的優(yōu)化編譯器呢?這完全是一些折衷的取舍。解釋器可以快速生成字節(jié)碼,但字節(jié)碼通常不夠高效。另一方面,優(yōu)化編譯器處理需要更長的時(shí)間,但最終會生成更高效的機(jī)器碼。到底是快速獲取可執(zhí)行的代碼(解釋器),還是花費(fèi)更多時(shí)間但最終以***性能運(yùn)行代碼(優(yōu)化編譯器),這其中包含一個(gè)平衡點(diǎn)。一些引擎選擇添加具有不同耗時(shí) / 效率特性的多個(gè)優(yōu)化編譯器,以更高的復(fù)雜性為代價(jià)來對這些折衷點(diǎn)進(jìn)行更細(xì)粒度的控制。

我們剛剛強(qiáng)調(diào)了每個(gè) JavaScript 引擎中解釋器和優(yōu)化編譯器流程中的主要區(qū)別。除了這些差異之外,所有 JavaScript 引擎都有相同的架構(gòu):那就是擁有一個(gè)解析器和某種解釋器 / 編譯器流程。

JavaScript 對象模型

通過關(guān)注一些方面的具體實(shí)現(xiàn),讓我們來看看 JavaScript 引擎間還有哪些共同之處。

例如,JavaScript 引擎是如何實(shí)現(xiàn) JavaScript 對象模型的,以及他們使用了哪些技巧來加快獲取 JavaScript 對象屬性的速度?事實(shí)證明,所有主要引擎在這一點(diǎn)上的實(shí)現(xiàn)都很相似。

ECMAScript 規(guī)范基本上將所有對象定義為由字符串鍵值映射到 property 屬性 的字典。

除 [[Value]] 外,規(guī)范還定義了如下屬性:

  • [[Writable]] 決定該屬性是否可以被重新賦值;
  • [[Enumerable]] 決定該屬性是否出現(xiàn)在 for-in 循環(huán)中;
  • [[Configurable]] 決定該屬性是否可被刪除。

[[雙方括號]] 的符號表示看上去有些特別,但這正是規(guī)范定義不能直接暴露給 JavaScript 的屬性的表示方法。在 JavaScript 中你仍然可以通過 Object.getOwnPropertyDescriptor API 獲得指定對象的屬性值:

 
 
 
 
  1. const object = { foo: 42 };  
  2. Object.getOwnPropertyDescriptor(object, 'foo');  
  3. // → { value: 42, writable: true, enumerable: true, configurable: true } 

JavaScript 就是這個(gè)定義對象的,那么數(shù)組呢?

你可以將數(shù)組想象成一組特殊的對象。兩者的一個(gè)區(qū)別便是數(shù)組會對數(shù)組索引進(jìn)行特殊的處理。這里所指的數(shù)組索引是 ECMAScript 規(guī)范中的一個(gè)特殊術(shù)語。在 JavaScript 中,數(shù)組被限制最多只能擁有 2^32-1 項(xiàng)。數(shù)組索引是指該限制內(nèi)的任何有效索引,即從 0 到 2^32-2 的任何整數(shù)。

另一個(gè)區(qū)別是數(shù)組還有一個(gè)充滿魔力的 length 屬性。

 
 
 
 
  1. const array = ['a', 'b'];  
  2. array.length; // → 2  
  3. array[2] = 'c';  
  4. array.length; // → 3 

在這個(gè)例子中,array 在生成時(shí)長度單位為 2。接著我們向索引為 2 的位置分配了另一個(gè)元素,length 屬性便自動(dòng)更新。

JavaScript 在定義數(shù)組的方式上和對象類似。例如,包括數(shù)組索引的所有鍵值都明確地表示為字符串。 數(shù)組中的***個(gè)元素存儲在鍵值為 ‘0’ 的位置下。

'length' 屬性恰好是另一個(gè)不可枚舉且不可配置的屬性。

一個(gè)元素一旦被添加到數(shù)組中,JavaScript 便會自動(dòng)更新 'length' 屬性的 [[Value]] 屬性值。

一般來說,數(shù)組的行為與對象也非常相似。

屬性訪問的優(yōu)化

讓我們深入了解下 JavaScript 引擎是如何有效地應(yīng)對對象相關(guān)操作的。

觀察 JavaScript 程序,訪問屬性是最常見的一個(gè)操作。使得 JavaScript 引擎能夠快速獲取屬性便至關(guān)重要。

 
 
 
 
  1. const object = {  
  2.  foo: 'bar',  
  3.  baz: 'qux',  
  4. };   
  5.  
  6. // Here, we’re accessing the property `foo` on `object`:  
  7. doSomething(object.foo);  
  8. //          ^^^^^^^^^^ 

Shapes

在 JavaScript 程序中,多個(gè)對象具有相同的鍵值屬性是非常常見的。這些對象都具有相同的形狀。

 
 
 
 
  1. const object1 = { x: 1, y: 2 };  
  2. const object2 = { x: 3, y: 4 };  
  3. // `object1` and `object2` have the same shape. 

訪問具有相同形狀對象的相同屬性也很常見:

 
 
 
 
  1. function logX(object) {  
  2.  console.log(object.x);  
  3.  //          ^^^^^^^^  
  4. }   
  5.  
  6. const object1 = { x: 1, y: 2 };  
  7. const object2 = { x: 3, y: 4 };   
  8.  
  9. logX(object1);  
  10. logX(object2); 

考慮到這一點(diǎn),JavaScript 引擎可以根據(jù)對象的形狀來優(yōu)化對象的屬性獲取。它是這么實(shí)現(xiàn)的。

假設(shè)我們有一個(gè)具有屬性 x 和 y 的對象,它使用我們前面討論過的字典數(shù)據(jù)結(jié)構(gòu):它包含用字符串表示的鍵值,而它們指向各自的屬性值。

如果你訪問某個(gè)屬性,例如 object.y,JavaScript 引擎會在 JSObject 中查找鍵值 'y',然后加載相應(yīng)的屬性值,***返回 [[Value]]。

但這些屬性值在內(nèi)存中是如何存儲的呢?我們是否應(yīng)該將它們存儲為 JSObject 的一部分?假設(shè)我們稍后會遇到更多同形狀的對象,那么在 JSObject 自身存儲包含屬性名和屬性值的完整字典便是很浪費(fèi)(空間)的,因?yàn)閷哂邢嗤螤畹乃袑ο笪覀兌贾貜?fù)了一遍屬性名稱。 它太冗余且引入了不必要的內(nèi)存使用。 作為優(yōu)化,引擎將對象的 Shape 分開存儲。

Shape 包含除 [[Value]] 之外的所有屬性名和其余特性。相反,Shape 包含 JSObject 內(nèi)部值的偏移量,以便 JavaScript 引擎知道去哪查找具體值。每個(gè)具有相同形狀的 JSObject 都指向這個(gè) Shape 實(shí)例。 現(xiàn)在每個(gè) JSObject 只需要存儲對這個(gè)對象來說唯一的那些值。

當(dāng)我們有多個(gè)對象時(shí),優(yōu)勢變得清晰可見。無論有多少個(gè)對象,只要它們具有相同的形狀,我們只需要將它們的形狀與鍵值屬性信息存儲一次!

所有的 JavaScript 引擎都使用了形狀作為優(yōu)化,但稱呼各有不同:

  • 學(xué)術(shù)論文稱它們?yōu)?Hidden Classes(容易與 JavaScript 中的類概念混淆)
  • V8 將它們稱為 Maps(容易與 JavaScript 中的 Map 概念混淆)
  • Chakra 將它們稱為 Types(容易與 JavaScript 中的動(dòng)態(tài)類型和關(guān)鍵字 typeof 混淆)
  • JavaScriptCore 稱它們?yōu)?Structures
  • SpiderMonkey 稱他們?yōu)?Shapes

本文中,我們會繼續(xù)稱它為 shapes。

Transition 鏈與樹

如果你有一個(gè)具有特定形狀的對象,但你又向它添加了一個(gè)屬性,此時(shí)會發(fā)生什么? JavaScript 引擎是如何找到這個(gè)新形狀的?

 
 
 
 
  1. const object = {};  
  2. object.x = 5;  
  3. object.y = 6; 

在 JavaScript 引擎中,shapes 的表現(xiàn)形式被稱作 transition 鏈。以下展示一個(gè)示例:

該對象在初始化時(shí)沒有任何屬性,因此它指向一個(gè)空的 shape。下一個(gè)語句為該對象添加值為 5 的屬性 “x”,所以 JavaScript 引擎轉(zhuǎn)向一個(gè)包含屬性 “x” 的 Shape,并向 JSObject 的***個(gè)偏移量為 0 處添加了一個(gè)值 5。 接下來一個(gè)語句添加了一個(gè)屬性 'y',引擎便轉(zhuǎn)向另一個(gè)包含 'x' 和 'y' 的 Shape,并將值 6 附加到 JSObject(位于偏移量 1 處)。

我們甚至不需要為每個(gè) Shape 存儲完整的屬性表。相反,每個(gè) Shape 只需要知道它引入的新屬性。 例如在此例中,我們不必在***一個(gè) Shape 中存儲關(guān)于 'x' 的信息,因?yàn)樗梢栽诟绲逆溕媳徽业?。要做到這一點(diǎn),每一個(gè) Shape 都會與其之前的 Shape 相連:

如果你在 JavaScript 代碼中寫到了 o.x,則 JavaScript 引擎會沿著 transition 鏈去查找屬性 “x”,直到找到引入屬性 “x”的 Shape。

但是,如果不能只創(chuàng)建一個(gè) transition 鏈呢?例如,如果你有兩個(gè)空對象,并且你為每個(gè)對象都添加了一個(gè)不同的屬性?

 
 
 
 
  1. const object1 = {};  
  2. object1.x = 5;  
  3. const object2 = {};  
  4. object2.y = 6; 

在這種情況下我們便必須進(jìn)行分支操作,此時(shí)我們最終會得到一個(gè) transition 樹 而不是 transition 鏈:

在這里,我們創(chuàng)建一個(gè)空對象 a,然后為它添加一個(gè)屬性 'x'。 我們最終得到一個(gè)包含單個(gè)值的 JSObject,以及兩個(gè) Shapes:空 Shape 和僅包含屬性 x 的 Shape。

第二個(gè)例子也是從一個(gè)空對象 b 開始的,但之后被添加了一個(gè)不同的屬性 'y'。我們最終形成兩個(gè) shape 鏈,總共是三個(gè) shape。

這是否意味著我們總是需要從空

 
 
 
 
  1. const object1 = {};
  2. object1.x = 5;  
  3. const object2 = { x: 6 }; 

在***個(gè)例子中,我們從空 shape 開始,然后轉(zhuǎn)向包含 x 的 shape,這正如我們我們之前所見。

在 object2 一例中,直接生成具有屬性 x 的對象是有意義的,而不是從空對象開始然后進(jìn)行 transition 連接。

包含屬性 'x' 的對象字面量從包含 'x' 的 shape 開始,可以有效地跳過空的 shape。V8 和 SpiderMonkey (至少)正是這么做的。這種優(yōu)化縮短了 transition 鏈,并使得從字面量構(gòu)造對象更加高效。

Benedikt 的博文 surprising polymorphism in React applications 討論了這些微妙之處是如何影響實(shí)際性能的。

Inline Caches (ICs)

Shapes 背后的主要?jiǎng)訖C(jī)是 Inline Caches 或 ICs 的概念。ICs 是促使 JavaScript 快速運(yùn)行的關(guān)鍵因素!JavaScript 引擎利用 ICs 來記憶去哪里尋找對象屬性的信息,以減少昂貴的查找次數(shù)。

這里有一個(gè)函數(shù) getX,它接受一個(gè)對象并從中取出屬性 x 的值:

 
 
 
 
  1. function getX(o) {  
  2.  return o.x;  

如果我們在 JSC 中執(zhí)行這個(gè)函數(shù),它會生成如下字節(jié)碼:

指令一 get_by_id 從***個(gè)參數(shù)(arg1)中加載屬性 'x' 值并將其存儲到地址 loc0 中。 第二條指令返回我們存儲到 loc0 中的內(nèi)容。

JSC 還在 get_by_id 指令中嵌入了 Inline Cache,它由兩個(gè)未初始化的插槽組成。

現(xiàn)在讓我們假設(shè)我們用對象 {x:'a'} 調(diào)用 getX 函數(shù)。正如我們所知,這個(gè)對象有一個(gè)包含屬性 'x' 的 Shape,該 Shape 存儲了屬性 x 的偏移量和其他特性。當(dāng)你***次執(zhí)行該函數(shù)時(shí),get_by_id 指令將查找屬性 'x',然后發(fā)現(xiàn)其值存儲在偏移量 0 處。

嵌入到 get_by_id 指令中的 IC 存儲該屬性的 shape 和偏移量:

對于后續(xù)運(yùn)行,IC 只需要對比 shape,如果它與以前相同,只需從記憶的偏移量處加載該屬性值。具體來說,如果 JavaScript 引擎看到一個(gè)對象的 shape 之前被 IC 記錄過,它則不再需要接觸屬性信息——而是完全可以跳過昂貴的屬性信息查找(過程)。這比每次查找屬性要快得多。

高效存儲數(shù)組

對于數(shù)組來說,存儲屬性諸如數(shù)組索引等是非常常見的。這些屬性的值被稱為數(shù)組元素。存儲每個(gè)數(shù)組中的每個(gè)數(shù)組元素的屬性特性(property attributes)將是一種很浪費(fèi)的存儲方式。相反,由于數(shù)組索引默認(rèn)屬性是可寫的、可枚舉的并且可以配置的,JavaScript 引擎利用這一點(diǎn),將數(shù)組元素與其他命名屬性分開存儲。

考慮這個(gè)數(shù)組:

 
 
 
 
  1. const array = [  
  2.  '#jsconfeu',  
  3. ]; 

引擎存儲了數(shù)組長度(1),并指向包含 offset 和 'length' 特性屬性的 Shape。

這與我們之前見過的類似……但數(shù)組值存儲在哪里呢?

每個(gè)數(shù)組都有一個(gè)單獨(dú)的 elements backing store,其中包含所有數(shù)組索引的屬性值。JavaScript 引擎不必為數(shù)組元素存儲任何屬性特性,因?yàn)樗鼈兺ǔ6际强蓪懙模擅杜e的以及可配置的。

那么如果不是通常的情況呢?如果更改了數(shù)組元素的屬性,該怎么辦?

 
 
 
 
  1. // Please don’t ever do this!  
  2. const array = Object.defineProperty(  
  3.  [],  
  4.  '0',  
  5.  {  
  6.    value: 'Oh noes!!1',  
  7.    writable: false,  
  8.    enumerable: false,  
  9.    configurable: false,  
  10.  }  
  11. ); 

上面的代碼片段定義了一個(gè)名為 '0' 的屬性(這恰好是一個(gè)數(shù)組索引),但其特性(value)被設(shè)置為了一個(gè)非默認(rèn)值。

在這種邊緣情況下,JavaScript 引擎會將全部的 elements backing store 表示為一個(gè)由數(shù)組下標(biāo)映射到屬性特性的字典。

即使只有一個(gè)數(shù)組元素具有非默認(rèn)屬性,整個(gè)數(shù)組的 backing store 處理也會進(jìn)入這種緩慢而低效的模式。 避免在數(shù)組索引上使用 Object.defineProperty! (我不知道為什么你會想這樣做。這看上去似乎是一個(gè)奇怪的且毫無價(jià)值的事情。)

Take-aways

我們已經(jīng)學(xué)習(xí)了 JavaScript 引擎是如何存儲對象和數(shù)組的,以及 Shapes 和 IC 是如何優(yōu)化針對它們的常見操作的。基于這些知識,我們確定了一些有助于提升性能的實(shí)用 JavaScript 編碼技巧:

始終以相同的方式初始化對象,以確保它們不會走向不同的 shape 方向。

不要混淆數(shù)組元素的屬性特性(property attributes),以確??梢愿咝У卮鎯筒僮魉鼈儭?/p>

相關(guān)鏈接

英文原文:

https://mathiasbynens.be/notes/shapes-ics

知乎譯文:

https://zhuanlan.zhihu.com/p/38202123


文章標(biāo)題:深入了解JavaScript引擎精華
網(wǎng)頁鏈接:http://www.dlmjj.cn/article/dhsjdjh.html