乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      JavaScript的工作原理:V8引擎內(nèi)部機(jī)制及優(yōu)化代碼的5個(gè)技巧 [每日前端夜話(0x15)]...

       西北望msm66g9f 2020-05-06

      翻譯:瘋狂的技術(shù)宅

      原文:https://blog./how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e

      幾個(gè)星期前,我們開始了一系列旨在深入挖掘 JavaScript 及其工作原理的系列:通過了解JavaScript的構(gòu)建模塊以及它們?nèi)绾喂餐l(fā)揮作用,你將能夠編寫更好的代碼和應(yīng)用程序。

      本系列的第一篇文章重點(diǎn)介紹了引擎,運(yùn)行時(shí)和調(diào)用堆棧的概述。 第二篇文章將深入探討谷歌V8 JavaScript引擎的內(nèi)部原理。 我們還將提供一些關(guān)于如何編寫更好的JavaScript代碼的快速提示: 我們的SessionStack開發(fā)團(tuán)隊(duì)在構(gòu)建產(chǎn)品時(shí)所遵循的最佳實(shí)踐。

      概覽

      JavaScript 引擎是執(zhí)行 JavaScript 代碼的程序或解釋器。 JavaScript 引擎可以實(shí)現(xiàn)為標(biāo)準(zhǔn)解釋器或即時(shí)編譯器,它以某種形式將 JavaScript 編譯為字節(jié)碼。

      下面是一個(gè)JavaScript引擎實(shí)現(xiàn)的熱門項(xiàng)目列表:

      • V8- 由Google開發(fā)的開源軟件,用C ++編寫

      • Rhino? - 由Mozilla Foundation管理,開源,完全用Java開發(fā)

      • SpiderMonkey? - 第一個(gè)JavaScript引擎,最早作為Netscape Navigator的一部分,現(xiàn)在被內(nèi)置到Firefox中

      • JavaScriptCore? - 開源,以名為Nitro的產(chǎn)品銷售,由Apple為Safari開發(fā)

      • KJS- KDE的引擎,最初由Harri Porten為KDE項(xiàng)目的Konqueror Web瀏覽器開發(fā)

      • Chakra(JScript9)? - Internet Explorer

      • Chakra(JavaScript)? - Microsoft Edge

      • Nashorn,作為OpenJDK的一部分的開源,由Oracle Java語言和工具組編寫

      • JerryScript? - 用于物聯(lián)網(wǎng)的輕量級(jí)引擎。

      為什么要開發(fā)V8引擎?

      由谷歌開發(fā)的V8引擎是用C ++編寫開源軟件。 此引擎在Google Chrome中使用。 但是,與其他引擎不同的是,流行的Node.js也把V8也作為運(yùn)行時(shí)環(huán)境使用。

      V8最初是為了提高Web瀏覽器中 JavaScript 執(zhí)行的性能。 為了提高運(yùn)行速度,V8 將 JavaScript 代碼轉(zhuǎn)換為更高效的機(jī)器代碼,而不是使用解釋器運(yùn)行。 它通過實(shí)現(xiàn)JIT(即時(shí))編譯器將 JavaScript 代碼編譯成機(jī)器代碼,這一點(diǎn)與許多現(xiàn)代 JavaScript 引擎一樣,如 SpiderMonkey 或 Rhino(Mozilla)。 不過主要區(qū)別是V8不產(chǎn)生字節(jié)碼或任何中間代碼。

      V8 曾經(jīng)有兩個(gè)編譯器

      在 V8 的 5.9 版本出現(xiàn)之前(2017年上半年發(fā)布),該引擎使用了兩個(gè)編譯器:

      • full-codegen:一個(gè)簡單而快速的編譯器,可以生成簡單且相對較慢的機(jī)器代碼。

      • Crankshaft:一種更復(fù)雜的(即時(shí))優(yōu)化編譯器,可生成高度優(yōu)化的代碼。

      V8引擎還在內(nèi)部使用多個(gè)線程:

      • 主線程完成你的期望:獲取代碼,編譯代碼然后執(zhí)行它

      • 另有一個(gè)單獨(dú)的線程用于編譯,因此主線程可以繼續(xù)執(zhí)行,同時(shí)前者優(yōu)化代碼

      • 一個(gè) 分析器線程,它將告訴運(yùn)行時(shí)需要消耗大量時(shí)間的操作,以便 Crankshaft 可以優(yōu)化它們

      • 一些線程來處理垃圾收集器的清理工作

      當(dāng)首次執(zhí)行 JavaScript 代碼時(shí),V8 會(huì)用 full-codegen直接將解析后的 JavaScript 代碼轉(zhuǎn)換為機(jī)器代碼而無需其它轉(zhuǎn)換。這使得它可以馬上開始執(zhí)行機(jī)器代碼。 請注意:V8不使用中間字節(jié)碼表示,因此無需解釋器。

      當(dāng)代碼運(yùn)行一段時(shí)間之后,分析器線程已經(jīng)收集到了足夠的數(shù)據(jù),知道了應(yīng)該優(yōu)化哪個(gè)方法。

      接下來,Crankshaft優(yōu)化從另一個(gè)線程開始。 它將 JavaScript 抽象語法樹轉(zhuǎn)換成名為 Hydrogen的高級(jí)靜態(tài)單分配(SSA:static single-assignment)表示,并嘗試優(yōu)化 Hydrogen graph。 大多數(shù)優(yōu)化都是在這個(gè)級(jí)別完成的。

      內(nèi)聯(lián)

      第一個(gè)優(yōu)化是提前內(nèi)聯(lián)盡可能多的代碼。 內(nèi)聯(lián)是用被調(diào)函數(shù)的函數(shù)體替換調(diào)用點(diǎn)(調(diào)用函數(shù)的代碼行)的過程。 這個(gè)簡單的步驟使后面的優(yōu)化更有意義。


      隱藏類

      JavaScript是一種基于原型的語言:沒有類,使用克隆過程創(chuàng)建對象。 JavaScript也是一種動(dòng)態(tài)編程語言,這意味著可以在實(shí)例化后可以輕松地在對象中添加或刪除屬性。

      大多數(shù)JavaScript解釋器使用類似字典的結(jié)構(gòu)(基于散列函數(shù))在內(nèi)存中存儲(chǔ)對象屬性值。 這種結(jié)構(gòu)使得在JavaScript中檢索屬性值的計(jì)算成本比在 Java 或 C# 等非動(dòng)態(tài)編程語言中更高。 在Java中,所有對象屬性都是在編譯之前由固定對象布局確定的,并且無法在運(yùn)行時(shí)動(dòng)態(tài)添加或刪除(好吧,C# 具有動(dòng)態(tài)類型,不過這是另一個(gè)話題)。 這樣一來,屬性值(或指向這些屬性的指針)可以作為連續(xù)緩沖區(qū)存儲(chǔ)在存儲(chǔ)器中,每個(gè)緩沖區(qū)之間具有固定偏移量,可以根據(jù)屬性類型輕松確定偏移的長度。而對于在運(yùn)行時(shí)可以更改屬性類型的 JavaScript,這是不可能做到的。

      由于使用字典查找對象屬性在內(nèi)存中的位置效率非常低,因此V8使用不同的方法:隱藏類。 隱藏類的工作方式類似于 Java 等語言中使用的固定對象布局(類),除非它們是在運(yùn)行時(shí)創(chuàng)建的。 現(xiàn)在,讓我們看看它們實(shí)際上是什么樣的:

      一旦new Point(1, 2)調(diào)用發(fā)生,V8將創(chuàng)建一個(gè)名為C0的隱藏類。

      此時(shí)尚未為Point定義任何屬性,因此C0為空。

      一旦第一個(gè)語句this.x = x被執(zhí)行(在Point函數(shù)內(nèi)),V8將創(chuàng)建一個(gè)名為C1的第二個(gè)隱藏類,它基于C0。 C1描述了可以找到屬性 x 的存儲(chǔ)器中的位置(相對于對象指針)。 在這種情況下,x存儲(chǔ)在偏移0處,這意味著當(dāng)將存儲(chǔ)器中的點(diǎn)對象視為連續(xù)緩沖區(qū)時(shí),第一偏移將對應(yīng)于屬性x。 V8還將使用“類轉(zhuǎn)換”更新C0,該類轉(zhuǎn)換指出如果將屬性 x添加到點(diǎn)對象,則隱藏類應(yīng)從C0切換到 C1。 下面的點(diǎn)對象的隱藏類現(xiàn)在是 C1。

      每次將新屬性添加到對象時(shí),舊的隱藏類都會(huì)更新為指向新隱藏類的轉(zhuǎn)換路徑。 隱藏類轉(zhuǎn)換非常重要,因?yàn)樗鼈冊试S在以相同方式創(chuàng)建的對象之間共享隱藏類。 如果兩個(gè)對象共享一個(gè)隱藏類,并且同一屬性被添加到它們之中,那么轉(zhuǎn)換將確保兩個(gè)對象都能夠接收到相同的新隱藏類和隨之附帶的所有優(yōu)化代碼。

      每次將新屬性添加到對象時(shí),舊的隱藏類都會(huì)更新為指向新隱藏類的轉(zhuǎn)換路徑。 隱藏類轉(zhuǎn)換非常重要,因?yàn)樗鼈冊试S在以相同方式創(chuàng)建的對象之間共享隱藏類。 如果兩個(gè)對象共享一個(gè)隱藏類,并且同一屬性被添加到它們之中,那么轉(zhuǎn)換將確保兩個(gè)對象都能夠接收到相同的新隱藏類和隨之附帶的所有優(yōu)化代碼。

      在執(zhí)行語句this.y = y時(shí)重復(fù)此過程(再一次,在Point函數(shù)內(nèi),在this.x = x語句之后)。

      創(chuàng)建一個(gè)名為C2的新隱藏類,將類轉(zhuǎn)換添加到C1,聲明如果將屬性y添加到Point對象(已包含屬性x),則隱藏類應(yīng)更改為C2,點(diǎn)對象的隱藏類更新為C2。

      隱藏類的轉(zhuǎn)換取決于屬性添加到對象的順序。 看下面的代碼片段:

      看到上面的代碼,你會(huì)認(rèn)為對于p1和p2,將使用相同的隱藏類和轉(zhuǎn)換。 實(shí)際上不是這樣的。 對于p1,首先添加屬性a,然后添加屬性b。 但是,對于p2,首先分配b,然后是a。 因此,作為不同轉(zhuǎn)換路徑的結(jié)果,p1p2以不同的隱藏類結(jié)束。 在這種情況下,以相同的順序初始化動(dòng)態(tài)屬性要好得多,因?yàn)榭梢灾赜秒[藏的類。

      內(nèi)聯(lián)緩存

      V8 還使用了另一種技術(shù)來優(yōu)化動(dòng)態(tài)類型語言,被稱為內(nèi)聯(lián)緩存。 內(nèi)聯(lián)緩存依賴于觀察到的一種現(xiàn)象,那就是相同方法總是會(huì)被同一類型的對象的重復(fù)調(diào)用。 可以在這里找到對內(nèi)聯(lián)緩存的深入解釋 (https://github.com/sq/JSIL/wiki/Optimizing-dynamic-JavaScript-with-inline-caches)。

      下面我們將討論內(nèi)聯(lián)緩存的一般概念(如果你沒有時(shí)間仔細(xì)閱讀上面的深入解釋的話)。

      那么它是怎樣工作的呢? V8 維護(hù)一個(gè)在最近的方法調(diào)用中作為參數(shù)傳遞的對象類型的緩存,并以此信息來推測將來作為參數(shù)傳遞的對象類型。如果V8能夠正確的推測出對傳遞給方法的對象類型,那么它就可以跳過確定如何訪問對象屬性的這一個(gè)步驟,這樣就可以使用之前查找過的信息確定對象的隱藏類。

      那么隱藏類和內(nèi)聯(lián)緩存這兩個(gè)概念的關(guān)聯(lián)是什么呢?每當(dāng)在特定對象上調(diào)用方法時(shí),V8 引擎必須找到該對象的隱藏類,才能確定訪問特定屬性的偏移量。當(dāng)同一方法兩次成功調(diào)用到同一個(gè)隱藏類之后,V8會(huì)省略對隱藏類的查找,直接將屬性的偏移量添加到對象指針本身。對于該方法的所有將來的調(diào)用,V8引擎假設(shè)隱藏類并未更改,并且使用之前查找到并存儲(chǔ)的偏移量直接跳轉(zhuǎn)到特定屬性的內(nèi)存地址。這就大大提高了執(zhí)行速度。

      內(nèi)聯(lián)緩存也是相同類型的對象共享隱藏類的重要原因。如果你要?jiǎng)?chuàng)建兩個(gè)類型相同但是隱藏類不同的對象(正如我們之前的例子中所做的那樣)的話,V8將無法使用內(nèi)聯(lián)緩存,因?yàn)榧词惯@兩個(gè)對象屬于同一類型,但是它們相對應(yīng)的隱藏類為其屬性分配的偏移量很有可能是不同的。

      這兩個(gè)對象基本相同,但ab兩個(gè)屬性是按照不同順序創(chuàng)建的。

      這兩個(gè)對象基本相同,但ab兩個(gè)屬性是按照不同順序創(chuàng)建的。

      編譯為機(jī)器代碼

      Hydrogen graph優(yōu)化后,Crankshaft 會(huì)將其降低到被稱為 Lithium 的低級(jí)別表示。大多數(shù) Lithium 實(shí)現(xiàn)都是特定于體系結(jié)構(gòu)的。寄存器分配發(fā)生在這一級(jí)別。

      最后,Lithium 被編譯成機(jī)器代碼。然后發(fā)生了一些被稱為 OSR 的事:棧替換(on-stack replacement)。當(dāng)一個(gè)顯然會(huì)長時(shí)間運(yùn)行的方法在我們開始編譯和優(yōu)化之前,它可能已經(jīng)在運(yùn)行。 V8 在重新啟動(dòng)優(yōu)化版本之前并會(huì)任由這些代碼緩慢的執(zhí)行。相反,它將轉(zhuǎn)換我們擁有的所有上下文(堆棧,寄存器),以便可以在執(zhí)行過程中切換到優(yōu)化版本。這是一項(xiàng)非常復(fù)雜的任務(wù),考慮到其他優(yōu)化,V8在一開始就已經(jīng)內(nèi)聯(lián)了代碼。 V8并不是唯一能夠做到這一點(diǎn)的引擎。

      有一種被稱為去優(yōu)化的保護(hù)措施可以進(jìn)行相反的轉(zhuǎn)換,如果引擎作出的假設(shè)不再成立,則恢復(fù)到非優(yōu)化代碼。

      垃圾收集

      對于垃圾收集,V8采用傳統(tǒng)的標(biāo)記和掃描方式來清理老生代。標(biāo)記階段應(yīng)該停止JavaScript執(zhí)行。為了控制GC成本并使執(zhí)行更加穩(wěn)定,V8使用了增量標(biāo)記:不是遍歷整個(gè)堆的同時(shí)嘗試標(biāo)記每個(gè)可能的對象,它只是遍歷堆的一部分,然后恢復(fù)正常執(zhí)行。 下一次GC將從上一次堆遍歷停止的位置繼續(xù)。這樣會(huì)在正常執(zhí)行期間只有非常短暫的暫停。 如前文所述,掃描階段由單獨(dú)的線程進(jìn)行處理。

      Ignition and TurboFan

      2017年早些時(shí)候發(fā)布的V8 5.9,引入了新的執(zhí)行管道。 事實(shí)證明,這個(gè)新的管道實(shí)現(xiàn)了更高的性能提升,并顯著的節(jié)省了內(nèi)存開銷。

      新的執(zhí)行管道建立在 Ignition (https://github.com/v8/v8/wiki/Interpreter)、V8的解釋器和TurboFan(V8的最新優(yōu)化編譯器)之上。

      你可以查看V8團(tuán)隊(duì)關(guān)于該主題的博客文章 (https://v8project./2017/05/launching-ignition-and-turbofan.html)。

      自從V8的 5.9 版本問世以來,V8已經(jīng)不再使用 full-codegen 和 Crankshaft(自2010年以來為V8提供服務(wù)的技術(shù))用于JavaScript執(zhí)行,因?yàn)閂8團(tuán)隊(duì)一直在努力跟上新的JavaScript語言功能,并且這些功能需要優(yōu)化。

      這意味著整體V8將會(huì)具有更簡單,更易維護(hù)的架構(gòu)。

      Web和Node.js基準(zhǔn)測試的改進(jìn)

      這些改進(jìn)只是一個(gè)開始。 新的Ignition和TurboFan管道為進(jìn)一步優(yōu)化鋪平了道路,這些優(yōu)化將在未來幾年內(nèi)提升JavaScript性能,并減少V8在Chrome和Node.js中所占用的空間。

      最后,有一些關(guān)于如何編寫良好優(yōu)化的JavaScript的技巧和竅門。 你可以從上面的內(nèi)容輕松地推導(dǎo)出這些內(nèi)容,下面是一個(gè)簡要的總結(jié):

      如何編寫優(yōu)化的JavaScript代碼

      1. 對象屬性的順序:始終以相同的順序?qū)嵗瘜ο髮傩?,以便可以共享隱藏類和隨后優(yōu)化的代碼。

      2. 動(dòng)態(tài)屬性:在實(shí)例化后向?qū)ο筇砑訉傩詫?huì)強(qiáng)制更改隱藏類, 并且會(huì)減慢之前隱藏類優(yōu)化的所有方法。應(yīng)該在其構(gòu)造函數(shù)中分配所有對象的屬性。

      3. 方法:重復(fù)執(zhí)行相同方法的代碼將比只執(zhí)行一次不同方法的代碼運(yùn)行得更快(由于內(nèi)聯(lián)緩存)。

      4. 數(shù)組:避免鍵值不是增量數(shù)的稀疏數(shù)組。訪問哈希表中的元素會(huì)有更多的消耗。另外,盡量避免預(yù)先分配大型數(shù)組。最后,不要?jiǎng)h除數(shù)組中的元素,這樣會(huì)使鍵變得稀疏。

      5. 標(biāo)記值:V8使用32個(gè)bit位表示對象和數(shù)字。它用一個(gè)bit位來表示剩下的個(gè)31個(gè)bit位是一個(gè)對象(flag = 1)還是一個(gè)名為SMI(SMall Integer)的整數(shù)(flag = 0)。如果數(shù)值大于31位,則V8將對該數(shù)字進(jìn)行裝箱操作,把它變?yōu)殡p精度并創(chuàng)建一個(gè)新對象以將數(shù)字放入其中。要盡可能的使用31位帶符號(hào)的數(shù)字,以避免對 JS 對象進(jìn)行昂貴的裝箱操作。

      我們在為 SessionStack 編寫高度優(yōu)化的 JavaScript 代碼時(shí)一直遵循這些最佳實(shí)踐。 原因是一旦把 SessionStack 集成到Web應(yīng)用的生產(chǎn)環(huán)境中,它就會(huì)開始記錄所有內(nèi)容:所有DOM更改、用戶交互、JavaScript異常、堆棧跟蹤、失敗的網(wǎng)絡(luò)請求和調(diào)試消息。

      通過SessionStack,你可以將網(wǎng)絡(luò)應(yīng)用中的問題重現(xiàn),并查看發(fā)生的所有事情,同時(shí)對你的Web應(yīng)用沒有性能影響。

      有一個(gè)免費(fèi)的工具,不需要支付任何費(fèi)用。 現(xiàn)在就可以試試(https://www./solutions/developers/?utm_source=medium&utm_medium=blog&utm_content=Post-1-overview-getStarted)。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多