成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

精讀《JS 引擎基礎(chǔ)之 Shapes and Inline Caches》

Tecode / 1232人閱讀

摘要:概述的解釋器優(yōu)化器代碼可能在字節(jié)碼或者優(yōu)化后的機器碼狀態(tài)下執(zhí)行,而生成字節(jié)碼速度很快,而生成機器碼就要慢一些了。比如有一個函數(shù),從獲取值引擎生成的字節(jié)碼結(jié)構(gòu)是這樣的指令是獲取參數(shù)指向的對象,并存儲在,第二步則返回。

1 引言

本期精讀的文章是:JS 引擎基礎(chǔ)之 Shapes and Inline Caches

一起了解下 JS 引擎是如何運作的吧!

JS 的運作機制可以分為 AST 分析、引擎執(zhí)行兩個步驟:

JS 源碼通過 parser(分析器)轉(zhuǎn)化為 AST(抽象語法樹),再經(jīng)過 interperter(解釋器)解析為 bytecode(字節(jié)碼)。

為了提高運行效率,optimizing compiler(優(yōu)化編輯器)負(fù)責(zé)生成 optimized code(優(yōu)化后的機器碼)。

本文主要從 AST 之后說起。

2 概述 JS 的解釋器、優(yōu)化器

JS 代碼可能在字節(jié)碼或者優(yōu)化后的機器碼狀態(tài)下執(zhí)行,而生成字節(jié)碼速度很快,而生成機器碼就要慢一些了。

V8 也類似,V8 將 interpreter 稱為 Ignition(點火器),將 optimizing compiler 成為 TurboFan(渦輪風(fēng)扇發(fā)動機)。

可以理解為將代碼先點火啟動后,逐漸進入渦輪發(fā)動機提速。

代碼先快速解析成可執(zhí)行的字節(jié)碼,在執(zhí)行過程中,利用執(zhí)行中獲取的數(shù)據(jù)(比如執(zhí)行頻率),將一些頻率高的方法,通過優(yōu)化編譯器生成機器碼以提速。

火狐使用的 Mozilla 引擎有一點點不同,使用了兩個優(yōu)化編譯器,先將字節(jié)碼優(yōu)化為部分機器碼,再根據(jù)這個部分優(yōu)化后的代碼運行時拿到的數(shù)據(jù)進行最終優(yōu)化,生成高度優(yōu)化的機器碼,如果優(yōu)化失敗將會回退到部分優(yōu)化的機器碼。

筆者:不同前端引擎對 JS 優(yōu)化方式大同小異,后面會繼續(xù)列舉不同前端引擎在解析器、編譯器部分優(yōu)化的方式。

微軟的 Edge 瀏覽器,使用的 Chakra 引擎,優(yōu)化方式與 Mozilla 很像,區(qū)別是第二個最終優(yōu)化的編譯器同時接收字節(jié)碼和部分優(yōu)化的機器碼產(chǎn)生的數(shù)據(jù),并且在優(yōu)化失敗后回退到第一步字節(jié)碼而不是第二步。

Safari、React Native 使用的 JSC 引擎則更為極端,使用了三個優(yōu)化編譯器,其優(yōu)化是一步步漸進的,優(yōu)化失敗后都會回退到第一步部分優(yōu)化的機器碼。

為什么不同前端引擎會使用不同的優(yōu)化策略呢?這是由于 JS 要么使用解釋器快速執(zhí)行(生成字節(jié)碼),或者優(yōu)化成機器碼后再執(zhí)行,但優(yōu)化消耗時間的并不總是小于字節(jié)碼低效運行損耗的時間,所以有些引擎選擇了多個優(yōu)化編譯器,逐層優(yōu)化,盡可能在解析時間與執(zhí)行效率中找到一個平衡點。

JS 的對象模型

JS 是基于面向?qū)ο蟮?,那?JS 引擎是如何實現(xiàn) JS 對象模型的呢?他們用了哪些技巧加速訪問 JS 對象的屬性?

和解析器、優(yōu)化器一樣,大部分主流 JS 引擎在對象模型實現(xiàn)上也很類似。

ECMAScript 規(guī)范確定了對象模型就是一個以字符串為 key 的字典,除了其值以外,還定義了 Writeable Enumerable Configurable 這些配置,表示這個 key 能否被重寫、遍歷訪問、配置。

雖然規(guī)范定義了 [[]] 雙括號的寫法,那這不會暴露給用戶,暴露給用戶的是 Object.getOwnPropertyDescriptor 這個 API,可以拿到某個屬性的配置。

在 JS 中,數(shù)組是對象的特殊場景,相比對象,數(shù)組擁有特定的下標(biāo),根據(jù) ECMAScript 規(guī)范規(guī)定,數(shù)組下標(biāo)的長度最大為 232?1。同時數(shù)組擁有 length 屬性:

length 只是一個不可枚舉、不可配置的屬性,并且在數(shù)組賦值時,會自動更新數(shù)值:

所以數(shù)組是特殊的對象,結(jié)構(gòu)完全一致。

屬性訪問效率優(yōu)化

屬性訪問是最常見的,所以 JS 引擎必須對屬性訪問做優(yōu)化。

Shapes

JS 編程中,給不同對象相同的 key 名很常見,訪問不同對象的同一個 propertyKey 也很常見:

const object1 = { x: 1, y: 2 };
const object2 = { x: 3, y: 4 };

function logX(object) {
  console.log(object.x);
  //          ^^^^^^^^
}

logX(object1);
logX(object2);

這時 object1object2 擁有一個相同的 shape。拿擁有 x、y 屬性的對象來看:

如果訪問 object.y,JS 引擎會先找到 key y,再查找 [[value]]。

如果將屬性值也存儲在 JSObject 中,像 object1 object2 就會出現(xiàn)許多冗余數(shù)據(jù),因此引擎多帶帶存儲 Shape,與真實對象隔離:

這樣具有相同結(jié)構(gòu)的對象可以共享 Shape。所有 JS 引擎都是用這種方式優(yōu)化對象,但并不都稱為 Shape,這里就不詳細(xì)羅列了,可以去原文查看在各引擎中 Shape 的別名。

Transition chains 和 Transition trees

如果給一個對象增加了 key,JS 引擎如何生成新的 Shape 呢?

這種 Shape 鏈?zhǔn)絼?chuàng)建的過程,稱為 Transition chains:

開始創(chuàng)建空對象時,JSObject 和 Shape 都是空,當(dāng)為 x 賦值 5 時,在 JSObject 下標(biāo) 0 的位置添加了 5,并且 Shape 指向了擁有字段 xShape(x),當(dāng)賦值 y6 時,在 JSObject 下標(biāo) 1 的位置添加了 6,并將 Shape 指向了擁有字段 xyShape(x, y)

而且可以再優(yōu)化,Shape(x, y) 由于被 Shape(x) 指向,所以可以省略 x 這個屬性:

筆者:當(dāng)然這里說的主要是優(yōu)化技巧,我們可以看出來,JS 引擎在做架構(gòu)設(shè)計時沒有考慮優(yōu)化問題,而在架構(gòu)設(shè)計完后,再回過頭對時間和空間進行優(yōu)化,這是架構(gòu)設(shè)計的通用思路。

如果沒有連續(xù)的父 Shape,比如分別創(chuàng)建兩個對象:

const object1 = {};
object1.x = 5;
const object2 = {};
object2.y = 6;

這時要通過 Transition trees 來優(yōu)化:

可以看到,兩個 Shape(x) Shape(y) 別分繼承 Shape(empty)。當(dāng)然也不是任何時候都會創(chuàng)建空 Shape,比如下面的情況:

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

生成的 Shape 如下圖所示:

可以看到,由于 object2 并不是從空對象開始的,所以并不會從 Shape(empty) 開始繼承。

Inline Caches

大概可以翻譯為“局部緩存”,JS 引擎為了提高對象查找效率,需要在局部做高效緩存。

比如有一個函數(shù) getX,從 o.x 獲取值:

function getX(o) {
  return o.x;
}

JSC 引擎 生成的字節(jié)碼結(jié)構(gòu)是這樣的:

get_by_id 指令是獲取 arg1 參數(shù)指向的對象 x,并存儲在 loc0,第二步則返回 loc0

當(dāng)執(zhí)行函數(shù) getX({ x: "a" }) 時,引擎會在 get_by_id 指令中緩存這個對象的 Shape

這個對象的 Shape 記錄了自己擁有的字段 x 以及其對應(yīng)的下標(biāo) offset

執(zhí)行 get_by_id 時,引擎從 Shape 查找下標(biāo),找到 x,這就是 o.x 的查找過程。但一旦找到,引擎就會將 Shape 保存的 offset 緩存起來,下次開始直接跳過 Shape 這一步:

以后訪問 o.x 時,只要 Shape 相同,引擎直接從 get_by_id 指令中緩存的下標(biāo)中可以直接命中要查找的值,而這個緩存在指令中的下標(biāo)就是 Inline Cache.

數(shù)組存儲優(yōu)化

和對象一樣,數(shù)組的存儲也可以被優(yōu)化,而由于數(shù)組的特殊性,不需要為每一項數(shù)據(jù)做完整的配置。

比如這個數(shù)組:

const array = ["#jsconfeu"];

JS 引擎同樣通過 Shape 與數(shù)據(jù)分離的方式存儲:

JS 引擎將數(shù)組的值多帶帶存儲在 Elements 結(jié)構(gòu)中,而且它們通常都是可讀可配置可枚舉的,所以并不會像對象一樣,為每個元素做配置。

但如果是這種例子:

// 永遠不要這么做
const array = Object.defineProperty([], "0", {
  value: "Oh noes!!1",
  writable: false,
  enumerable: false,
  configurable: false
});

JS 引擎會存儲一個 Dictionary Elements 類型,為每個數(shù)組元素做配置:

這樣數(shù)組的優(yōu)化就沒有用了,后續(xù)的賦值都會基于這種比較浪費空間的 Dictionary Elements 結(jié)構(gòu)。所以永遠不要用 Object.defineProperty 操作數(shù)組。

通過對 JS 引擎原理的認(rèn)識,作者總結(jié)了下面兩點代碼中的注意事項:

盡量以相同方式初始化對象,因為這樣會生成較少的 Shapes。

不要混淆對象的 propertyKey 與數(shù)組的下標(biāo),雖然都是用類似的結(jié)構(gòu)存儲,但 JS 引擎對數(shù)組下標(biāo)做了額外優(yōu)化。

3 精讀

這次原理系列解讀是針對 JS 引擎執(zhí)行優(yōu)化這個點的,而網(wǎng)頁渲染流程大致如下:

可以看到 Script 在整個網(wǎng)頁解析鏈路中位置是比較靠前的,JS 解析效率會直接影響網(wǎng)頁的渲染,所以 JS 引擎通過解釋器(parser)和優(yōu)化器(optimizing compiler)盡可能 對 JS 代碼提效。

Shapes

需要特別說明的是,Shapes 并不是 原型鏈,原型鏈?zhǔn)敲嫦蜷_發(fā)者的概念,而 Shapes 是面向 JS 引擎的概念。

比如如下代碼:

const a = {};
const b = {};
const c = {};

顯然對象 a b c 之間是沒有關(guān)聯(lián)的,但共享一個 Shapes。

另外理解引擎的概念有助于我們站在語法層面對立面的角度思考問題:在 JS 學(xué)習(xí)階段,我們會執(zhí)著于思考如下幾種創(chuàng)建對象方式的異同:

const a = {};
const b = new Object();
const c = new f1();
const d = Object.create(null);

比如上面四種情況,我們要理解在什么情況下,用何種方式創(chuàng)建對象性能最優(yōu)。

但站在 JS 引擎優(yōu)化角度去考慮,JS 引擎更希望我們都通過 const a = {} 這種看似最沒有難度的方式創(chuàng)建對象,因為可以共享 Shape。而與其他方式混合使用,可能在邏輯上做到了優(yōu)化,但阻礙了 JS 引擎做自動優(yōu)化,可能會得不償失。

Inline Caches

對象級別的優(yōu)化已經(jīng)很極致了,工程代碼中也沒有機會幫助 JS 引擎做得更好,值得注意的是不要對數(shù)組使用 Object 對象下的方法,尤其是 defineProperty,因為這會讓 JS 引擎在存儲數(shù)組元素時,使用 Dictionary Elements 結(jié)構(gòu)替代 Elements,而 Elements 結(jié)構(gòu)是共享 PropertyDescriptor 的。

但也有難以避免的情況,比如使用 Object.defineProperty 監(jiān)聽數(shù)組變化時,就不得不破壞 JS 引擎渲染了。

筆者寫 dob 的時候,使用 proxy 監(jiān)聽數(shù)組變化,這并不會改變 Elements 的結(jié)構(gòu),所以這也從另一個側(cè)面證明了使用 proxy 監(jiān)聽對象變化比 Object.defineProperty 更優(yōu),因為 Object.defineProperty 會破壞 JS 引擎對數(shù)組做的優(yōu)化。

4 總結(jié)

本文主要介紹了 JS 引擎兩個概念: ShapesInline Caches,通過認(rèn)識 JS 引擎的優(yōu)化方式,在編程中需要注意以下兩件事:

盡量以相同方式初始化對象,因為這樣會生成較少的 Shapes

不要混淆對象的 propertyKey 與數(shù)組的下標(biāo),雖然都是用類似的結(jié)構(gòu)存儲,但 JS 引擎對數(shù)組下標(biāo)做了額外優(yōu)化。

5 更多討論
討論地址是:精讀《JS 引擎基礎(chǔ)之 Shapes and Inline Caches》 · Issue #91 · dt-fe/weekly

如果你想?yún)⑴c討論,請點擊這里,每周都有新的主題,周末或周一發(fā)布。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/95661.html

相關(guān)文章

  • 精讀《React 的多態(tài)性》

    摘要:引言本周精讀的文章是,看看作者是如何解釋這個多態(tài)性含義的。讀完文章才發(fā)現(xiàn),文章標(biāo)題改為的多態(tài)性更妥當(dāng),因為整篇文章都在說,而使用場景不局限于。更多討論討論地址是精讀的多態(tài)性如果你想?yún)⑴c討論,請點擊這里,每周都有新的主題,周末或周一發(fā)布。 1 引言 本周精讀的文章是:surprising-polymorphism-in-react-applications,看看作者是如何解釋這個多態(tài)性含...

    tabalt 評論0 收藏0
  • 精讀Caches API》

    摘要:引言這個是針對的。一般結(jié)合使用,因為請求級別的緩存與具有頁面攔截功能的最配。本周精讀的文章是,介紹了瀏覽器緩存接口的基本語法。包含任意命名空間,可以通過創(chuàng)建或訪問。精讀筆者利用實現(xiàn)了純?yōu)g覽器端的后端渲染。前端精讀幫你篩選靠譜的內(nèi)容。 1 引言 caches 這個 API 是針對 Request Response 的。caches 一般結(jié)合 Service Worker 使用,因為請求級...

    Null 評論0 收藏0
  • 用 JavaScript 解釋 JavaScript 虛擬機-內(nèi)聯(lián)緩存(inline caches

    摘要:用解釋虛擬機內(nèi)聯(lián)緩存本文轉(zhuǎn)載自眾成翻譯譯者鏈接原文我知道如何實現(xiàn)用語言或者語言的子集來實現(xiàn)運行該語言虛擬機。有時候我們用了錯誤的抽象層次來解釋虛擬機的工作機制。這正是我們的內(nèi)聯(lián)緩存功能所需要的。 用JavaScript解釋JavaScript虛擬機-內(nèi)聯(lián)緩存(inline caches) 本文轉(zhuǎn)載自:眾成翻譯譯者:LexHuang鏈接:http://www.zcfy.cc/articl...

    mikyou 評論0 收藏0
  • 精讀《V8 引擎 Lazy Parsing》

    摘要:在執(zhí)行函數(shù)時,通過保存堆棧狀態(tài),再保存堆棧跳出后返回位置的指針,最后對變量賦值。這看上去沒有問題,只要將值存在堆棧就搞定了。 1. 引言 本周精讀的文章是 V8 引擎 Lazy Parsing,看看 V8 引擎為了優(yōu)化性能,做了怎樣的嘗試吧! 這篇文章介紹的優(yōu)化技術(shù)叫 preparser,是通過跳過不必要函數(shù)編譯的方式優(yōu)化性能。 2. 概述 & 精讀 解析 Js 發(fā)生在網(wǎng)頁運行的關(guān)鍵路...

    羅志環(huán) 評論0 收藏0
  • 精讀《手寫 SQL 編譯器 - 智能提示》

    摘要:經(jīng)過連續(xù)幾期的介紹,手寫編譯器系列進入了智能提示模塊,前幾期從詞法到文法語法,再到構(gòu)造語法樹,錯誤提示等等,都是為智能提示做準(zhǔn)備。 1 引言 詞法、語法、語義分析概念都屬于編譯原理的前端領(lǐng)域,而這次的目的是做 具備完善語法提示的 SQL 編輯器,只需用到編譯原理的前端部分。 經(jīng)過連續(xù)幾期的介紹,《手寫 SQL 編譯器》系列進入了 智能提示 模塊,前幾期從 詞法到文法、語法,再到構(gòu)造語法...

    ztyzz 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<