日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

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

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
解讀JavaScript之V8引擎及優(yōu)化代碼的5個技巧

幾個星期前,我們開始了一系列旨在深入研究 JavaScript 及其實際工作方式的系列文章:我們認(rèn)為通過了解 JavaScript 的構(gòu)建塊以及它們?nèi)绾我黄饏f(xié)作的,你將能夠編寫更好的代碼和應(yīng)用程序。

成都創(chuàng)新互聯(lián)公司專注于企業(yè)網(wǎng)絡(luò)營銷推廣、網(wǎng)站重做改版、青山網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5建站、購物商城網(wǎng)站建設(shè)、集團公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為青山等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

本系列的***篇文章重點介紹了引擎,運行時和調(diào)用堆棧的概述。第二篇文章將深入到 Google V8 JavaScript 引擎的內(nèi)部。我們還將提供一些關(guān)于如何編寫更好的 JavaScript 代碼的快速技巧 - 我們的 SessionStack 開發(fā)團隊在構(gòu)建產(chǎn)品時所遵循的***實踐。

概覽

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

下面是一個實現(xiàn)了 JavaScript 引擎的流行項目列表:

  1. V8? — ?開源,由 Google 開發(fā),用 C ++ 編寫
  2. Rhino ?— ?由 Mozilla 基金會管理,開源,完全用 Java 開發(fā)
  3. SpiderMonkey? —? 是***個支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用
  4. JavaScriptCore? — ?開源,由蘋果公司為 Safari 開發(fā)
  5. KJS ?— ?KDE 的引擎,最初由 Harri Porten 為 KDE 項目中的 Konqueror 網(wǎng)頁瀏覽器開發(fā)
  6. Chakra (JScript9) ?— ?Internet Explorer
  7. Chakra (JavaScript)? —? Microsoft Edge
  8. Nashorn, 作為 OpenJDK 的一部分,由 Oracle Java 語言和工具組編寫
  9. JerryScript ?— 物聯(lián)網(wǎng)的輕量級引擎

為什么創(chuàng)建 V8 引擎?

由 Google 構(gòu)建的 V8 引擎是開源的,用 C ++ 編寫。 此引擎被用在 Google Chrome 中。 與其他引擎不同的是,V8 也被用于流行的 Node.js 中。

V8 最初是被設(shè)計用來提高網(wǎng)頁瀏覽器內(nèi)部 JavaScript 執(zhí)行的性能。為了獲得更快的速度,V8 將 JavaScript 代碼翻譯成更高效的機器代碼,而不是使用解釋器來翻譯代碼。它通過使用 JIT(Just-In-Time)編譯器(如 SpiderMonkey 或 Rhino(Mozilla)等許多現(xiàn)代 JavaScript 引擎)來將 JavaScript 代碼編譯為機器代碼。 這里的主要區(qū)別在于 V8 不生成字節(jié)碼或任何中間代碼。

V8 曾有兩個編譯器

在 V8 的 5.9 版本出來之前(今年早些時候發(fā)布),引擎使用了兩個編譯器:

  • full-codegen - 一個簡單而且速度非常快的編譯器,可以生成簡單且相對較慢的機器代碼。
  • Crankshaft? - 一種更復(fù)雜(Just-In-Time)的優(yōu)化編譯器,生成高度優(yōu)化的代碼。

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

  • 主線程完成您期望的任務(wù):獲取代碼,編譯并執(zhí)行它
  • 還有一個單獨的線程用于編譯,以便主線程可以繼續(xù)執(zhí)行,而前者正在優(yōu)化代碼
  • 一個 Profiler 線程,它會告訴運行時我們花了很多時間,讓 Crankshaft 可以優(yōu)化它們
  • 一些線程處理垃圾收集器

當(dāng)***次執(zhí)行 JavaScript 代碼時,V8 利用 full-codegen 編譯器,直接將解析的 JavaScript 翻譯成機器代碼而不進行任何轉(zhuǎn)換。這使得它可以非常快速地開始執(zhí)行機器代碼。請注意,V8 不使用中間字節(jié)碼,從而不需要解釋器。

當(dā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ū)存儲在內(nèi)存中,每個值之間有一個固定的偏移量。偏移量的長度可以很容易地根據(jù)屬性類型來確定,而在運行時屬性類型可以改變的 JavaScript 中這是不可能的。

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

 
 
 
 
  1. function Point(x, y) {
  2.     this.x = x;
  3.     this.y = y;
  4. }
  5. var p1 = new Point(1, 2);

一旦 “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的在內(nèi)存中的位置(相對于對象指針)。在這種情況下,“x”被存儲在0處,這意味著當(dāng)在內(nèi)存中將點對象看作一段連續(xù)存儲空間時,***個地址將對應(yīng)于屬性“x”。 V8 也會用“class transition”來更新“C0”,如果一個屬性“x”被添加到一個點對象時,隱藏類應(yīng)該從“C0”切換到“C1”。下面的點對象的隱藏類現(xiàn)在是“C1”。

每當(dāng)一個新的屬性被添加到一個對象時,舊的隱藏類將被更新為到新的隱藏類的轉(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”,點對象的隱藏類更新為“C2”。

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

 
 
 
 
  1. function Point(x, y) {
  2.     this.x = x;
  3.     this.y = y;
  4. }
  5. var p1 = new Point(1, 2);
  6. p1.a = 5;
  7. p1.b = 6;
  8. var p2 = new Point(3, 4);
  9. p2.b = 7;
  10. p2.a = 8;

現(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 利用另一種被稱為內(nèi)聯(lián)緩存的技術(shù)來優(yōu)化動態(tài)類型語言。內(nèi)聯(lián)緩存依賴于發(fā)生在相同類型的對象上的相同方法的重復(fù)調(diào)用的觀察上。內(nèi)嵌緩存的更多解釋可以在這里找到。

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

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

那么隱藏類和內(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)化的保護措施來做出相反的變換,并且在假設(shè)引擎優(yōu)化無效的情況下,還原回非優(yōu)化的代碼。

垃圾收集

對于垃圾收集,V8 采用了傳統(tǒng)的分代式掃描方式來清理老一代。標(biāo)記階段應(yīng)該停止 JavaScript 的執(zhí)行。為了控制 GC 成本并使執(zhí)行更加穩(wěn)定,V8 使用了漸進式標(biāo)記:而不是走遍整個堆內(nèi)容,試圖標(biāo)記每一個可能的對象。它只走一部分堆內(nèi)容,然后恢復(fù)正常執(zhí)行。下一個 GC 將從先前堆走過的地方繼續(xù)執(zhí)行。這允許在正常執(zhí)行期間非常短的暫停。如前所述,掃描階段由不同的線程處理。

Ignition 和 TurboFan

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

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

你可以查看 V8 團隊關(guān)于這個話題的博客文章。

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

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

在 Web 和 Node.js 性能上的提升

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

***,這里有一些關(guān)于如何編寫優(yōu)化的、更好的 JavaScript 的技巧。你可以很容易地從上面的內(nèi)容中得到這些,不過,這里有一個為你提供便利的總結(jié):

如何編寫優(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 對象的高開銷的裝箱操作。

我們在 SessionStack 中試圖編寫高度優(yōu)化的 JavaScript 代碼時遵循這些***實踐。 原因是,一旦將 SessionStack 集成到你的產(chǎn)品級的 Web 應(yīng)用程序中,它就會開始記錄所有的東西:所有的 DOM 更改、用戶交互、JavaScript 異常、堆棧跟蹤、網(wǎng)絡(luò)請求失敗、調(diào)試消息等。

通過 SessionStack ,你可以以視頻的方式重現(xiàn)問題,并查看發(fā)生在用戶身上的所有事情。所有這些都必須在對你的網(wǎng)絡(luò)應(yīng)用程序的性能沒有任何影響的情況下進行的。

這有一個免費的方案,所以你可以試試看。


當(dāng)前文章:解讀JavaScript之V8引擎及優(yōu)化代碼的5個技巧
文章位置:http://m.5511xx.com/article/cdejphg.html