摸魚前端的自檢(三)v8幹了些什麼?

v8幹了些什麼?

V8是被設計用來提高網頁瀏覽器內部JavaScript執行的性能,那麼如何提高性能呢?接下來我們一個個介紹它幹了些什麼。

有興趣的同學可以去我的github,裏面有我的分享的學習過程和blog.
github.com/193Eric


JIT

說到v8首先我們要明白什麼是解釋型語言和編譯型語言。

  • 編譯型語言,如c/c++,處理該語言實際上使用編譯器直接將它們編譯成本地代碼,所以在使用的時候已經是可以執行代碼,所以速度更快
  • 解釋型語言,與編譯型語言不同的是它需要一遍執行一邊解析

Js語言到底是解釋型語言還是編譯型語言?

就像我們剛學js的時候總會聽到說js是一門解釋型語言,但是v8出現後,還能算嗎?

JIT是什麼?

JIT(Just-in-time)及時編譯編譯器,***編譯***劃重點,不是說是解釋型語言嗎?爲什麼還要編譯… 這就是v8的一個優化速度的方式了,通過JIT及時編譯的方式。

for(i=0; i < 1000; i++){
    sum += i;
}

編譯型的語言直接把它編譯成機器碼,直接可以運行1000次。
解釋型語言執行時會將sum += i轉換(編譯)一千次。對相同的代碼進行一千次轉換會造成非常大的性能損耗。

從上面的例子就可以看出,爲什麼要加入JIT,在javascript中,如果有代碼要運行很多次,JIT 將把這段代碼送到編譯器中編譯並且保存一個編譯後的版本。下一次同樣代碼執行的時候,引擎會跳過翻譯過程直接使用編譯後的版本。而在真正的編譯型語言中,編譯器做的比JIT多的多了。

所以JIT是一個優化性能的工具,所以我覺得javascript是一個解釋和編譯混合的語言(或者可以稱作解釋型的編譯語言)


隱藏類

javascript是動態語言(TS不算哈… ),由於是動態語言呢,類型不確定,每次都要去動態查詢,這就造成大量的性能消耗,從而降低程序運行的速度。V8引擎爲了優化這個,引入了隱藏類這個機制,針對對象進行分組。初始化個對象的時候會侯,會同時生成一個隱藏類或者查找之前已經生成的隱藏類。

我們看一個例子:

let Obj = (name,time)=>{
  this.name = name
  this.time = time
}

let obj1 = new Obj('eric','1993') // 1
let obj2 = new Obj('ming','1994') // 2

obj1.age = '26' // 3
obj1.height = "180" // 4

obj2.height = "170" //5
obj2.age = "25" // 6

分析下上段代碼,其中的隱藏類的一部分用僞代碼表示

  • 1和2開始都是初始化一個Obj,然後會v8會生成一個隱藏類C0,然後同時會加入兩個屬性(name,time),因爲屬性是相同的,所以在C0的基礎生會生成一個C1和C2的過渡隱藏類。
    C1 = Obj{name},C2 = Obj{name,time}

  • 再看3和4,obj1添加了兩個屬性(age,height)所以會在C2的基礎上生成C3和C4的隱藏類。C3 = obj{name,time,age}, C4 = obj{name,time,age,height}

  • 再看5和6,obj2添加了兩個屬性(height,age)因爲屬性的添加順序和obj1不同,首先會去找Obj{name,time,height}的隱藏類,發現沒有,所以在C2的基礎上添加一個C5的隱藏類,然後尋找Obj{name,time,height,age}發現還是沒有,所以在C5的基礎上添加一個C6的隱藏類。
    C5 = obj{name,time,height} , C6 = obj{name,time,heightage}

不同初始化順序的對象,所生成的隱藏類是不一樣的。因此,在實際開發過程中,應該儘量保證屬性初始化的順序一致,這樣生成的隱藏類可以得到共享。同時,儘量在構造函數裏就初始化所有對象成員,減少隱藏類的產生。


內聯緩存

這個很好理解,比如說我們寫代碼的時候,很多時候會用到緩存,查找的時候先查找緩存,如果匹配了,就從緩存裏面取,如果不匹配,再按照原有流程去獲取。

內聯緩存就是,大致思路就是將初次查找的隱藏類和偏移值保存起來,當下次查找的時候,先比較當前對象是否是之前的隱藏類,如果是的話,直接使用之前的緩存結果,減少再次查找表的時間。

當然,如果一個對象有多個屬性,那麼緩存失誤的概率就會提高,因爲某個屬性的類型變化之後,對象的隱藏類也會變化,就與之前的緩存不一致,需要重新使用以前的方式查找哈希表。


垃圾回收

  • 標記清除法
    該算法有兩個階段
    標記階段:找到所有可訪問的對象,做個標記
    清除階段:遍歷堆,把未被標記的對象回收

  • 引用計數法
    每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時可以回收。此方法簡單,但無法解決對象相互循環引用的問題,如下圖所示。

  • 複製算法
    在堆區中,對象分爲新生代(年輕代)、老年代和永生代,而複製算法發生是發生在新生代的。

V8採用了一種分代回收的策略,將內存分爲了新生代(new space)和老生代(old space)新生代的對象爲存活時間較短的對象,老生代中的對象爲存活時間較長或常駐內存的對象。分別對新生代和老生代使用 不同的垃圾回收算法來提升垃圾回收的效率。對象起初都會被分配到新生代,當新生代中的對象滿足某些條件(後面會有介紹)時,會被移動到老生代(晉升)

  1. 新生代內存回收是複製算法,主要採用了Cheney算法。(採用複製的方式實現的垃圾回收算法),具體算法內容可以自行了解,這裏只說個大概。
  2. 老生代內存回收 標記清除法(主要),標記整理
  3. IE9以下的js引擎對非原生對象(如BOM,DOM)的垃圾回收會採用引用計數算法,
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章