How JavaScript works - 2 (譯)js 工做原理

inside the V8 engine + 5 tips on how to write optimized code

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

Foreword 前言

兩週前咱們開始了js深刻了解以及它如何的工做原理的研究:咱們認爲經過了解js的代碼構建和他們的運行機制能夠寫出更好的代碼和app。
前面的博文主要是js引擎概述,運行和堆棧。第二篇會深刻谷歌V8的js引擎部分,咱們也會提供一點點關於如何編寫更好的js代碼的建議-最好java

Overview 概覽

js引擎是一個程序或者執行js代碼解析器。js引擎能夠實現爲一個標準的翻譯,或者以某種形式將js代碼即時編譯成字節碼。 下面是流行的js執行引擎清單列表:node

  • v8 - 開源,由google開發,用C++ 編寫
  • Rhino — 由火狐管理,開源,徹底用java開發
  • SpiderMonkey
  • JavaScriptCore
  • KJS
  • Chakra
  • Chakra
  • Nashorn
  • JerryScript

爲什麼創造v8引擎

由google開發V8引擎是開源的,用c++編寫。這個引擎用於Google Chrome。不像其餘的引擎,當前流行的node.js也是基於V8運行。 c++

v8最初旨在增長js在web瀏覽器中的性能。爲了獲取速度,V8將js代碼轉換成更高校的機器代碼而不是翻譯js。他將js代碼編譯成機器代碼,在執行的時候是即時編譯的,就像大多數js引擎模型同樣。主要的區別在於v8不會產生任何字節碼或者任何中間代碼。

v8有兩個編譯器

在V8的5.9版本出現以前,v8使用兩個編譯器:

  • full-codegen -- 一個簡單快速的編譯器,生成編譯簡單和相對慢的機器碼。
  • Crankshaft -- 一個更復雜的(即時)優化編譯器,生成高度優化的代碼。

v8引擎內部也是使用多線程的:

  • 主線程作你想要的工做:獲取代碼,編譯代碼而後執行。
  • 還有一個單獨的線程編譯,因此主線成能夠繼續執行代碼,而前者是優化代碼
  • 在運行時有一個分析器的線程會告訴咱們哪些方法花費了大量的事件,這樣Crankshaf能夠去優化它
  • 一些線程用來處理垃圾回收掃描

當開始執行js代碼,v8使用full-codegen直接解析js翻譯成機器碼,沒有任何轉換。這容許它開始執行機器代碼很是快。注意v8不使用中間字節碼錶示這種方式不須要翻譯。web

當你的代碼運行一段時間後,解析器線程會彙集足夠的數據告訴咱們哪些方法必須優化。 接下來,Crankshaft開始在另外一條線程上優化。它將js抽象語法樹(syntax tree )轉換成一個叫Hydrogen 的高級靜態單元分配表示 (SSA) ,並且嘗試去優化 Hydrogen這個圖。大多數的優化是在這級完成的。編程

Inlining 代碼嵌入

首次優化儘量多的提早嵌入代碼。代碼嵌入是將使用函數的地方(調用函數的那一行)替換成被調用函數的本體。這個簡單的步驟可使接下來的優化更有意義。 瀏覽器

Hidden class 隱藏類

js是一個基於原型鏈的語言:它沒有類和對象是經過克隆產生的。js也是一個動態編程語言,這意味着在對象實力化之後能夠很簡單增長或者移除屬性。 大多數js解析器使用相似字典同樣的結構(基於散列函數) 去儲存對象屬性值在內存中的地址。這種構造使js在檢索對象屬性值上比其餘相似java,c++等動態語言,花費更大的計算量。在java中,全部的對象屬性在編譯以前都已經被固定的對象容器決定。並且在運行時不能夠動態的添加或者刪除屬性。(c++的動態類型是另外一個話題)。結果,每個屬性的值可以以連續的buffer儲存在內存中,在每一個屬性值之間有一個偏移量。這個偏移量的長度很容易根據屬性類型決定。然而在js中這是不可能的,由於js的屬性值能夠在運行時改變。 由於使用字典去查找對象屬性在內存中的地址的效率很低,V8使用了一個不一樣的方法代替:隱藏類。隱藏類與在java等語言中使用的固定對象佈局的工做方式相似,除了他在運行時被建立。來讓咱們看一下真實的代碼:緩存

function Point(x,y){
     this.x = x;
     this.y = y;
 }
 var p1 = new Point(1,2);
複製代碼

一旦 new Point(1,2) 被調用,V8會建立一個名爲 C0 的隱藏類。 session

此時他尚未被定義任何屬性,所以 C0 是空的。 一旦第一排代碼被執行 「this.x = x」 (在Point函數中),V8會建立一個基於 C0 名爲 C1 的第二個隱藏類。 C1 記錄的是屬性x在內存中的地址(相對於對象指針)。在這個案例中,x 被存儲在 offset偏移量爲0中,這意味着當瀏覽一個做爲持續的buffer指針對象的時候,第一個偏移量對應屬性 ‘x’。若是屬性 x被放入制定對象的時候,V8也會用一個 過分的類更新 C0的狀態。隱藏類就會從 C0C1。如今指定對象的隱藏類指定 C1

每一次新的屬性被添加到對象中,舊的隱藏類就會用一個過渡的路徑更新到新的隱藏類上。隱藏類的過渡很重要,由於他們容許隱藏類之間共享相同的方式建立對象。若是兩個對象分享一個隱藏類,相同的屬性會被添加到這兩個對象中。過渡能夠確保這兩個對象接收到相同的隱藏類和全部的優化代碼。

Inline caching 內部緩存

Compilation to machine code 編譯機械代碼

Ignition and TurboFan

How to write optimized JavaScript 如何優化js代碼

Resources

相關文章
相關標籤/搜索