最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧

來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 21:54:42
文檔

JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧

JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧:概述 JavaScript引擎是執(zhí)行 JavaScript 代碼的程序或解釋器。JavaScript引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將JavaScript編譯為字節(jié)碼的即時編譯器。 以為實現(xiàn)JavaScript引擎的流行項目的列表: V8 — 開源,由 Go
推薦度:
導(dǎo)讀JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧:概述 JavaScript引擎是執(zhí)行 JavaScript 代碼的程序或解釋器。JavaScript引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將JavaScript編譯為字節(jié)碼的即時編譯器。 以為實現(xiàn)JavaScript引擎的流行項目的列表: V8 — 開源,由 Go

概述

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

以為實現(xiàn)JavaScript引擎的流行項目的列表:

  • V8 — 開源,由 Google 開發(fā),用 C ++ 編寫
  • Rhino — 由 Mozilla 基金會管理,開源,完全用 Java 開發(fā)
  • SpiderMonkey — 是第一個支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用
  • JavaScriptCore — 開源,以Nitro形式銷售,由蘋果為Safari開發(fā)
  • KJS — KDE 的引擎,最初由 Harri Porten 為 KDE 項目中的 Konqueror 網(wǎng)頁瀏覽器開發(fā)
  • Chakra (JScript9) — Internet Explorer
  • Chakra (JavaScript) — Microsoft Edge
  • Nashorn, 作為 OpenJDK 的一部分,由 Oracle Java 語言和工具組編寫
  • JerryScript —  物聯(lián)網(wǎng)的輕量級引擎
  • 為什么要創(chuàng)建V8引擎?

    由谷歌構(gòu)建的V8引擎是開源的,使用c++編寫。這個引擎是在谷歌Chrome中使用的,但是,與其他引擎不同的是 V8 也用于流行的 node.js。

    V8最初被設(shè)計用來提高web瀏覽器中JavaScript執(zhí)行的性能。為了獲得速度,V8 將 JavaScript 代碼轉(zhuǎn)換成更高效的機器碼,而不是使用解釋器。它通過實現(xiàn) JIT (Just-In-Time) 編譯器將 JavaScript 代碼編譯為執(zhí)行時的機器碼,就像許多現(xiàn)代 JavaScript 引擎(如SpiderMonkey或Rhino (Mozilla)) 所做的那樣。這里的主要區(qū)別是 V8 不生成字節(jié)碼或任何中間代碼。

    V8 曾有兩個編譯器

    在 V8 的 5.9 版本出來之前,V8 引擎使用了兩個編譯器:

  • full-codegen — 一個簡單和非??斓木幾g器,產(chǎn)生簡單和相對較慢的機器碼。
  • Crankshaft — 一種更復(fù)雜(Just-In-Time)的優(yōu)化編譯器,生成高度優(yōu)化的代碼。
  • V8 引擎也在內(nèi)部使用多個線程:

  • 主線程執(zhí)行你所期望的操作:獲取代碼、編譯代碼并執(zhí)行它
  • 還有一個單獨的線程用于編譯,因此主線程可以在前者優(yōu)化代碼的同時繼續(xù)執(zhí)行
  • 一個 Profiler 線程,它會告訴運行時我們花了很多時間,讓 Crankshaft 可以優(yōu)化它們
  • 一些線程處理垃圾收集器
  • 當(dāng)?shù)谝淮螆?zhí)行 JavaScript 代碼時,V8 利用 full-codegen 編譯器,直接將解析的 JavaScript 翻譯成機器代碼而不進行任何轉(zhuǎn)換。這使得它可以非??焖俚亻_始執(zhí)行機器代碼。請注意,V8 不使用中間字節(jié)碼,從而不需要解釋器。

    當(dāng)代碼已經(jīng)運行一段時間后,分析線程已經(jīng)收集了足夠的數(shù)據(jù)來判斷應(yīng)該優(yōu)化哪個方法。

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

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

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

    隱藏類

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

    大多數(shù) JavaScript 解釋器使用類似字典的結(jié)構(gòu)(基于哈希函數(shù))來存儲對象屬性值在內(nèi)存中的位置,這種結(jié)構(gòu)使得在 JavaScript 中檢索屬性的值比在 Java 或 C# 等非動態(tài)編程語言中的計算成本更高。

    在Java中,所有對象屬性都是在編譯之前由固定對象布局確定的,并且無法在運行時動態(tài)添加或刪除(當(dāng)然,C#具有動態(tài)類型,這是另一個主題)。

    因此,屬性值(或指向這些屬性的指針)可以作為連續(xù)緩沖區(qū)存儲在存儲器中,每個緩沖區(qū)之間具有固定偏移量, 可以根據(jù)屬性類型輕松確定偏移的長度,而在運行時可以更改屬性類型的 JavaScript 中這是不可能的。

    由于使用字典查找內(nèi)存中對象屬性的位置效率非常低,因此 V8 使用了不同的方法:隱藏類。隱藏類與 Java 等語言中使用的固定對象(類)的工作方式類似,只是它們是在運行時創(chuàng)建的?,F(xiàn)在,讓我們看看他們實際的例子:

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

    尚未為 Point 定義屬性,因此“C0”為空。

    一旦第一個語句“this.x = x”被執(zhí)行(在“Point”函數(shù)內(nèi)),V8 將創(chuàng)建一個名為 “C1” 的第二個隱藏類,它基于“C0”。 “C1”描述了可以找到屬性 x 的存儲器中的位置(相對于對象指針)。

    在這種情況下,“x”存儲在偏移0處,這意味著當(dāng)將存儲器中的 point 對象視為連續(xù)緩沖區(qū)時,第一偏移將對應(yīng)于屬性 “x”。 V8 還將使用 “類轉(zhuǎn)換” 更新 “C0” ,該類轉(zhuǎn)換指出如果將屬性 “x” 添加到 point 對象,則隱藏類應(yīng)從 “C0” 切換到 “C1”。 下面的 point 對象的隱藏類現(xiàn)在是“C1”。

    每次將新屬性添加到對象時,舊的隱藏類都會更新為指向新隱藏類的轉(zhuǎn)換路徑。隱藏類轉(zhuǎn)換非常重要,因為它們允許在以相同方式創(chuàng)建的對象之間共享隱藏類。如果兩個對象共享一個隱藏類并且同一屬性被添加到它們中,則轉(zhuǎn)換將確保兩個對象都接收相同的新隱藏類以及隨其附帶的所有優(yōu)化代碼。

    當(dāng)語句 “this.y = y” 被執(zhí)行時,會重復(fù)同樣的過程(在 “Point” 函數(shù)內(nèi)部,“this.x = x”語句之后)。

    一個名為“C2”的新隱藏類會被創(chuàng)建,如果將一個屬性 “y” 添加到一個 Point 對象(已經(jīng)包含屬性“x”),一個類轉(zhuǎn)換會添加到“C1”,則隱藏類應(yīng)該更改為“C2”,point 對象的隱藏類更新為“C2”。

    隱藏類轉(zhuǎn)換取決于將屬性添加到對象的順序??纯聪旅娴拇a片段:

    現(xiàn)在,假設(shè)對于p1和p2,將使用相同的隱藏類和轉(zhuǎn)換。那么,對于“p1”,首先添加屬性“a”,然后添加屬性“b”。然而,“p2”首先分配“b”,然后是“a”。因此,由于不同的轉(zhuǎn)換路徑,“p1”和“p2”以不同的隱藏類別結(jié)束。在這種情況下,以相同的順序初始化動態(tài)屬性好得多,以便隱藏的類可以被重用。

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

    V8利用了另一種優(yōu)化動態(tài)類型語言的技術(shù),稱為內(nèi)聯(lián)緩存。內(nèi)聯(lián)緩存依賴于這樣一種觀察,即對同一方法的重復(fù)調(diào)用往往發(fā)生在同一類型的對象上。這里可以找到對內(nèi)聯(lián)緩存的深入解釋。

    接下來將討論內(nèi)聯(lián)緩存的一般概念(如果您沒有時間通過上面的深入了解)。

    那么它是如何工作的呢? V8 維護了在最近的方法調(diào)用中作為參數(shù)傳遞的對象類型的緩存,并使用這些信息預(yù)測將來作為參數(shù)傳遞的對象類型。如果 V8 能夠很好地預(yù)測傳遞給方法的對象的類型,它就可以繞過如何訪問對象屬性的過程,而是使用從以前的查找到對象的隱藏類的存儲信息。

    那么隱藏類和內(nèi)聯(lián)緩存的概念如何相關(guān)呢?無論何時在特定對象上調(diào)用方法時,V8 引擎都必須執(zhí)行對該對象的隱藏類的查找,以確定訪問特定屬性的偏移量。在同一個隱藏類的兩次成功的調(diào)用之后,V8 省略了隱藏類的查找,并簡單地將該屬性的偏移量添加到對象指針本身。對于該方法的所有下一次調(diào)用,V8 引擎都假定隱藏的類沒有更改,并使用從以前的查找存儲的偏移量直接跳轉(zhuǎn)到特定屬性的內(nèi)存地址。這大大提高了執(zhí)行速度。

    內(nèi)聯(lián)緩存也是為什么相同類型的對象共享隱藏類非常重要的原因。 如果你創(chuàng)建兩個相同類型和不同隱藏類的對象(正如我們之前的例子中所做的那樣),V8將無法使用內(nèi)聯(lián)緩存,因為即使這兩個對象屬于同一類型,它們對應(yīng)的隱藏類為其屬性分配不同的偏移量。

    這兩個對象基本相同,但是“a”和“b”屬性的創(chuàng)建順序不同。

    編譯成機器碼

    一旦 Hydrogen 圖被優(yōu)化,Crankshaft 將其降低到稱為 Lithium 的較低級表示。大部分的 Lithium 實現(xiàn)都是特定于架構(gòu)的。寄存器分配往往發(fā)生在這個級別。

    最后,Lithium 被編譯成機器碼。然后就是 OSR :on-stack replacement(堆棧替換)。在我們開始編譯和優(yōu)化一個明確的長期運行的方法之前,我們可能會運行堆棧替換。 V8 不只是緩慢執(zhí)行堆棧替換,并再次開始優(yōu)化。相反,它會轉(zhuǎn)換我們擁有的所有上下文(堆棧,寄存器),以便在執(zhí)行過程中切換到優(yōu)化版本上。這是一個非常復(fù)雜的任務(wù),考慮到除了其他優(yōu)化之外,V8 最初還將代碼內(nèi)聯(lián)。 V8 不是唯一能夠做到的引擎。

    有一種叫去優(yōu)化的安全措施來進行相反的轉(zhuǎn)換,并在假設(shè)引擎無效的情況下返回未優(yōu)化的代碼。

    垃圾收集

    對于垃圾收集,V8采用傳統(tǒng)的 mark-and-sweep 算法 來清理舊一代。 標(biāo)記階段應(yīng)該停止JavaScript執(zhí)行。 為了控制GC成本并使執(zhí)行更穩(wěn)定,V8使用增量標(biāo)記:不是遍歷整個堆,嘗試標(biāo)記每個可能的對象,它只是遍歷堆的一部分,然后恢復(fù)正常執(zhí)行。下一個GC停止將從上一個堆行走停止的位置繼續(xù),這允許在正常執(zhí)行期間非常短暫的暫停,如前所述,掃描階段由單獨的線程處理。

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

    1. 對象屬性的順序:始終以相同的順序?qū)嵗瘜ο髮傩裕员憧梢怨蚕黼[藏的類和隨后優(yōu)化的代碼。
    2. 動態(tài)屬性: 因為在實例化之后向?qū)ο筇砑訉傩詫娭茍?zhí)行隱藏的類更改,并降低之前隱藏類所優(yōu)化的所有方法的執(zhí)行速度,所以在其構(gòu)造函數(shù)中分配所有對象的屬性。
    3. 方法:重復(fù)執(zhí)行相同方法的代碼將比僅執(zhí)行一次的多個不同方法(由于內(nèi)聯(lián)緩存)的代碼運行得更快。
    4. 數(shù)組:避免稀疏數(shù)組,其中鍵值不是自增的數(shù)字,并沒有存儲所有元素的稀疏數(shù)組是哈希表。這種數(shù)組中的元素訪問開銷較高。另外,盡量避免預(yù)分配大數(shù)組。最好是按需增長。最后,不要刪除數(shù)組中的元素,這會使鍵值變得稀疏。
    5. 標(biāo)記值:V8 使用 32 位表示對象和數(shù)值。由于數(shù)值是 31 位的,它使用了一位來區(qū)分它是一個對象(flag = 1)還是一個稱為 SMI(SMall Integer)整數(shù)(flag = 0)。那么,如果一個數(shù)值大于 31 位,V8會將該數(shù)字裝箱,把它變成一個雙精度數(shù),并創(chuàng)建一個新的對象來存放該數(shù)字。盡可能使用 31 位有符號數(shù)字,以避免對 JS 對象的高開銷的裝箱操作。

    Ignition and TurboFan

    隨著2017年早些時候發(fā)布V8 5.9,引入了新的執(zhí)行管道。 這個新的管道在實際的JavaScript應(yīng)用程序中實現(xiàn)了更大的性能提升和顯著節(jié)省內(nèi)存。

    新的執(zhí)行流程是建立在 Ignition( V8 的解釋器)和 TurboFan( V8 的最新優(yōu)化編譯器)之上的。

    自從 V8 5.9 版本問世以來,由于 V8 團隊一直努力跟上新的 JavaScript 語言特性以及這些特性所需要的優(yōu)化,V8 團隊已經(jīng)不再使用 full-codegen 和 Crankshaft(自 2010 年以來為 V8 技術(shù)所服務(wù))。

    這意味著 V8 整體上將有更簡單和更易維護的架構(gòu)。

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

    聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧

    JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧:概述 JavaScript引擎是執(zhí)行 JavaScript 代碼的程序或解釋器。JavaScript引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將JavaScript編譯為字節(jié)碼的即時編譯器。 以為實現(xiàn)JavaScript引擎的流行項目的列表: V8 — 開源,由 Go
    推薦度:
    標(biāo)簽: 代碼 v8 引擎
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top