V8編譯流水線-惰性解析/字節碼

V8在編譯JS的過程當中,並不會將全部的JS解析成中間代碼,主要緣由以下:瀏覽器

  • 若是所有解析和編譯,會增長編譯時間,延長用戶的等待時間。
  • 解析完成的機器碼會放在內存中,若是所有解析完成,內存佔用量會很大。

基於以上緣由,因此JS虛擬機實現了惰性解析。惰性解析就是解析器在解析的過程當中,若是遇到函數聲明,就會跳過函數內部的代碼,僅僅生成頂層代碼的AST和字節碼。其中閉包會讓V8的解析過程變的複雜。緩存

1. 惰性解析

1.1 閉包

閉包的三個特性:

  • JS語言容許在函數內部定義新的函數。
  • 能夠在內部函數中訪問父函數中定義的變量。變量的查找按照詞法做用域鏈來查找。
  • 由於函數是一等公民,因此函數能夠做爲返回值。
function foo() {
	let a = 1
    return function bar(b) {
    	return a + b
    }
}
複製代碼

當函數引用閉包時,函數執行完成,會從棧中彈出,函數中的變量和做用域會被銷燬,可是閉包中引用的父函數變量會依然保存到內存中,沒有被銷燬掉。處理這個任務的模塊叫預解析器。markdown

1.2 預解析器

V8引入預解析器,當解析頂層代碼的時候,若是遇到一個函數,預解析器並不會直接跳過該函數,而是對該函數作一次快速的預解析。閉包

預解析的做用:

  • 檢查語法錯誤
  • 檢查函數內部是否引用了外部變量,若是引用了外部變量,預解析器會將棧中的變量賦值到堆中,在下次執行該函數時,直接使用堆中的引用。

1.3 總結

V8的惰性解析是指解析器在解析過程當中,若是遇到函數聲明,則跳過函數內部代碼,僅生成頂層代碼的AST和字節碼。利用惰性解析能夠加速JS的編譯速度和節約內存。架構

V8解析函數的時候,會使用預解析器快速解析函數內部是否包含了外部函數聲明的變量,若是引用了,就會將該變量複製存放到堆中,即便當前函數執行完也不會釋放該變量,從而實現閉包的功能。函數

2. 字節碼

字節碼就是編譯過程當中的中間代碼,是機器代碼的抽象,V8中的字節碼有兩個做用:優化

  • 解釋器直接解釋執行字節碼。
  • 優化編譯器能夠將字節碼編譯爲二進制代碼,而後執行二進制代碼。

2.1 機器代碼緩存

在瀏覽器執行JS以前,早期的V8會先編譯JS爲未優化過的二進制機器代碼,而後執行二進制代碼。若是瀏覽器再次打開相同的頁面,並且JS沒有修改過,此時再編譯就會浪費CPU的資源,因此瀏覽器引入二進制代碼緩存,把編譯後的二進制代碼緩存在內存中,省去編譯的時間。spa

V8使用兩種策略來緩存生成的代碼:

  • V8第一次執行代碼,編譯源JS代碼,並將編譯後的二進制代碼緩存在內存中,這種方式叫內存緩存。而後經過JS源文件的字符串在內存中查找編譯後的二進制代碼。當執行時,會先去內存中查找是否編譯過。
  • V8除了內存緩存外,還將代碼緩存到硬盤上,這樣即便關閉瀏覽器,下次重開瀏覽器並執行時,也能夠重複使用編譯好的JS代碼。

2.2 字節碼下降內存佔用

採用緩存是典型的以空間換時間的策略,犧牲存儲空間換取執行速度。JS編譯成二進制後,二進制文件十分佔內存,V8爲了提高啓動速度,採用惰性編譯,這樣能解決部份內存佔用的問題。可是若是函數中有較多的閉包,會致使閉包中的代碼沒法緩存。因此只緩存頂層代碼是不完美的,因此V8引入字節碼來繼續下降V8執行時的內存佔用。3d

上圖看出,字節碼的佔用空間遠小於二進制代碼,因此能夠針對字節碼來進行操做優化。同時瀏覽器緩存全部的字節碼。雖然字節碼的執行速度稍慢機器碼,可是採用字節碼能有效下降內存,並提升代碼啓動速度。code

2.3 字節碼如何提升代碼啓動速度

JS代碼啓動流程圖:

解釋器能夠快速生成字節碼,優化編譯器雖然須要更長的時間來處理,可是最終會產生更高效的機器碼,這就是V8在使用的模型,它的解釋器叫Ignition,是全部引擎中最快的解釋器。V8的優化編譯器叫TurboFan,由它生成高度優化的機器碼。

2.4 字節碼如何下降代碼複雜度

引入字節碼,能夠統一將字節碼轉換爲不一樣平臺的二進制代碼(機器碼),不用再去針對不一樣的CPU去作兼容。並且字節碼的執行過程跟CUP執行二進制代碼相似,也下降了轉換底層代碼的工做量。

2.5 總結

早期的V8在啓動時,直接將JS源碼編譯爲二進制的機器碼,可是會產生兩個問題:

  • 時間問題:編譯時間太久。
  • 空間問題:緩存編譯後的二進制代碼會佔用更多的內存。

爲了解決上述問題,V8進行了重構,引入了中間的字節碼。字節碼優點有三點:

  • 解決啓動時間問題:生成字節碼的時間會更短。
  • 解決空間問題:字節碼佔用內存比二進制的機器碼佔用內存更少,而且使用緩存會大大下降內存的使用。
  • 代碼架構清晰:簡化程序複雜度,使得V8移植到不一樣的CUP架構平臺更加容易。
相關文章
相關標籤/搜索