V8引擎

1、摘要

V8使用C++開發,並在谷歌瀏覽器中使用。在運行JavaScript以前,相比其它的JavaScript的引擎轉換成字節碼(包含執行程序的二進制文件)或解釋執行,V8將其編譯成原生機器碼(IA-32, x86-64, ARM, or MIPS CPUs),而且使用瞭如內聯緩存(inline caching)等方法來提升性能。有了這些功能,JavaScript程序在V8引擎下的運行速度媲美二進制程序。
javascript

2、詳解java

1. 字節碼(Bytecode):一種包含執行程序、由一序列op代碼、數據對組成的二進制文件,是一種中間碼,編譯器將源碼編譯成字節碼,特定平臺上的虛擬機器將字節碼轉譯爲能夠直接執行的機器碼。c++

原生機器碼(machine code):也被稱爲原生碼(Native Code),是電腦的CPU可直接解讀的數據,機器碼是計算機能夠直接執行,而且執行速度最快的代碼。編出的程序所有是0和1組成的指令代碼瀏覽器

字節碼裝換成原生機器碼:首先編譯器將源碼編譯成字節碼,特定平臺上 的虛擬機(如:Java虛擬機)器將字節碼轉譯成能夠直接執行的指令。字節碼的典型應用爲Java bytecode緩存

2. javascript引擎性能優化

語言編譯過程:bash


如今其餘的js引擎的執行過程大體:源代碼 -> 抽象語法樹 -> 字節碼 -> JIT -> 本地代碼閉包

(如:蘋果的JavascriptCore引擎,08年引入SquirrelFish,實現了一個字節碼寄存器Register Machine;Mozilla公司的SpiderMonkey;微軟的Chakra等)ide

抽象語法樹函數


V8引擎中沒有中間字節碼,直接將抽象語法樹經過JIT技術轉換成本地代碼,後經過Profiler採集一些信息,優化本地代碼,雖然少了這一階段的性能優化,但極大減小了轉換時間

可是在V8的5.9版本,新增了Ignition字節碼解釋器,默認啓動,緣由是減輕機器碼佔用的內存空間,提升代碼的啓動速度,重構V8的代碼並下降代碼複雜度。

3. V8引擎

3.1 5.9以前的版本:直譯成原生機器碼,並使用如內聯緩存等方法提升性能

3.1.1 數據表示

javascript是一種無類型語言,在編譯時不能肯定變量的類型,只能在執行時肯定;而像c++、java等靜態類型語言在編譯時就能知道變量類型,肯定變量存取地址。在運行時計算和決定類型是會下降運行效率的緣由。

變量的存取在代碼執行過程當中是很是廣泛和平凡的,js的對象須要經過屬性名匹配找到相應的值,須要更多的操做和內存空間。V8使用了一種特殊的方法:數據的內部表示由數據的實際內容和數據的句柄構成。

數據的實際內容長度可變,類型可變;數據的句柄固定大小,包含指向數據的指針。變量存取時經過查找、修改句柄中的指針便可。V8一個句柄對象的大小是4字節(32位設備)和8字節(64位設備),javascriptCore中是8字節。

V8中的指針包含三類:隱藏類指針,是V8爲js對象建立的隱藏類;屬性值表指針,指向該對象包含的屬性值;元素表指針,指向該對象包含的屬性。

3.1.2工做過程

V8中,js代碼是在須要執行時才進行編譯,而不是一次性所有編譯;這也就提升了響應時間。源代碼先被解析器轉成抽象語法樹(AST),而後使用JIT編譯器的全代碼生成器將其直接生成本地客執行代碼。爲了提高性能,V8在生成本地代碼後,使用數據分析器(profiler)採集一些信息,而後根據這些信息優化本地代碼,若是優化後代碼性能更差,就會優化回滾。

在執行編譯前,V8會構建衆多全局對象,並加載一些內置的庫(如math庫),來構建運行環境。

3.1.3優化回滾

V8沒有通過中間表示層的優化,即先編譯肯定變量類型等,因此引入Crankshaft編譯器對熱點函數,基於javascript源代碼,進行優化分析。Crankshaft爲了性能考慮,默認代碼穩定且變量類型不變,生成高效的本地代碼;但若遇到變量類型在執行過程當中改變的狀況,V8會將該編譯器作的優化進行回滾,即從新從源碼開始再次編譯。

var counter =
0;

function
test(x, y) {

    counter++;

    if (counter < 1000000)
{

        // do something

        return 'jeri';

    }

    var unknown = new Date();

    console.log(unknown);

}複製代碼

在未執行到new Date()以前,並不肯定unknown變量的類型,執行到時,V8只能將這部分代碼進行回滾。優化回滾是很耗時的操做。

3.1.4隱藏類


使用point構造了兩個對象p和q,這兩個對象有相同的屬性名,V8將他們歸爲同一隱藏類,具備相同的偏移位置信息,p和q共享這一信息,進行屬性訪問時,只需根據隱藏類的偏移信息便可。但假如,代碼執行後,對象q執行了q.z = 5,則p和q不在具備相同的隱藏類,q是一個新的隱藏類。

隱藏類轉換取決於將屬性添加到對象的順序:

function Point(x, y) {

    this.x = x;

    this.y = y;

}

var p = new Point(1, 2);

p.a = 5;

p.b = 6;

var q = new Point(3, 4);

q.b = 7;

q.a = 8;複製代碼

而在該函數中,p和q添加屬性的順序不一樣,隱藏類的偏移量不一樣,也是兩個不一樣的隱藏類。

3.1.5內聯緩存

正常訪問對象的過程:首先獲取隱藏類的地址,而後根據屬性名查找偏移量,而後計算該屬性的地址。屢次變量存取,就要重複執行這一過程,也較耗時。所以,V8提供了內嵌緩存,即將初次查找的隱藏類和偏移量保存起來,當下次查找相同對象時,能夠省略計算地址的過程。可是若是一個對象有多個屬性,緩存失誤的機率就會提升,由於某個屬性的類型變化後,對象的隱藏類也會發生變化,就與以前的緩存不一致,須要從新計算。

3.1.6內存管理

V8垃圾回收機制限制js使用的內存(若是可以使用內存太大,垃圾回收時須要耗費更多的資源和時間),所以對內存進行管理:分配和回收。

內存的管理組要由分配和回收兩個部分構成。V8的內存劃分以下:

Zone:管理小塊內存。其先本身申請一塊內存,而後管理和分配一些小內存,當一塊小內存被分配以後,不能被Zone回收,只能一次性回收Zone分配的全部小內存。當一個過程須要不少內存,Zone將須要分配大量的內存,卻又不能及時回收,會致使內存不足狀況。

堆:管理JavaScript使用的數據、生成的代碼、哈希表等。爲方便實現垃圾回收,堆被分爲三個部分:
年輕分代:爲新建立的對象分配內存空間,常常須要進行垃圾回收。爲方便年輕分代中的內容回收,可再將年輕分代分爲兩半,一半用來分配,另外一半在回收時負責將以前還須要保留的對象複製過來。
年老分代:根據須要將年老的對象、指針、代碼等數據保存起來,較少地進行垃圾回收。
大對象:爲那些須要使用較多內存對象分配內存,固然一樣可能包含數據和代碼等分配的內存,一個頁面只分配一個對象。


3.2 5.9版本

3.2.1引入Ignition字節碼解釋器的緣由


1).V8對代碼的編譯是在該段代碼被執行時,所以代碼須要被解析屢次--綠色代碼(總的)先解析一次,當new Person被調用時黃色代碼再解析一次,當doWork被調用時紅色代碼再解析一次。所以,若是閉包嵌套了n層,在Crankshaft預算正確的狀況下,代碼至少要被V8解析n次。

2).機器碼佔空間大,若是要將全部js直譯的機器碼緩存,佔用的內存、磁盤空間很大,而退出瀏覽器再從新打開,執行使用緩存的時間也會很長。如此,Chrome的緩存之做用在js代碼的最外層,真正的執行邏輯並無被緩存,這也是致使內存資源被佔用被浪費的緣由。

字節碼比機器碼緊湊更多,能夠下降代碼內存的佔有率


(來源: docs.google.com/presentatio…)

一樣的內存分配下,內存佔用減小,啓動速度加快,使得V8能夠指望提早編譯全部js代碼,並把字節碼緩存,二次打開網站時速度就會更快。也就再也不須要Cranshaft這個舊的編譯器,引用新的Turbofan直接從字節碼優化,並當須要進行反優化的時候,直接反優化到字節碼,而不是將機器碼反優化到js源碼。


(原文出處: docs.google.com/presentatio…)

3、總結

從上述V8引擎的設計特徵總結來看,在編碼過程當中應注意:

  1. 類型。由於JS是動態類型語言,JavaScriptCore和V8都使用隱藏類和內嵌緩存來提升性能。爲了下降反優化機率,一個函數應該使用較少的數據類型;對於對象,應儘可能存放相同類型的數據。
  2. 內存。及時回收不用的內存,再也不使用的對象設置爲null。
  3. 優化回滾。閉包嵌套層數不要過多,且執行屢次後,儘可能不修改對象類型。
  4. 動態屬性。屢次New 一個相同的對象時,最好以相同的順序初始化動態屬性,以便隱藏類能夠被複用。
相關文章
相關標籤/搜索