V8引擎詳解(三)——從字節碼看V8的演變

前言

本文是V8引擎詳解系列的第三篇,重點內容是關於瞭解字節碼的概念,以及字節碼在V8引擎演變過程當中的重要影響,同時幫您梳理v8引擎的架構幫助您更好的瞭解V8引擎架構,文末會有已經完成的系列文章的連接,本系列文章還在不斷更新歡迎持續關注。前端

字節碼概念

1、什麼是字節碼

wiki百科中字節碼的描述是這樣的:java

字節碼(英語:Bytecode)一般指的是已經通過編譯,但與特定機器代碼無關,須要解釋器轉譯後才能成爲機器代碼的中間代碼。字節碼一般不像源碼同樣可讓人閱讀,而是編碼後的數值常量、引用、指令等構成的序列。瀏覽器

按照做者的對字節碼的理解大概是這樣:
計算機只能識別二進制代碼,而二進制代碼(指令集)是不合適人類書寫和閱讀的,不一樣的CPU架構對應的指令集也是徹底不一樣的,爲了克服這個問題,大神們就創造出了適合人類的語言,也就是所謂的 「高級」 語言,這些高級語言與人類的天然語言以及數學公式的使用是很是接近的,並且不用考慮CPU架構差別。而高級語言和二進制代碼之間的差別是至關大的,直接轉換會很是麻煩,這時就有了兩者中間的代碼——字節碼緩存

2、字節碼的優勢

要了解字節碼的優勢,最直觀的方式就是直接看字節碼給java帶來了什麼,早期java推廣的口號就是Compile Once,Run anywhere(一次編譯處處運行),Java源代碼通過編譯程序編譯以後生成擴展名爲.class的字節碼文件。再經過JVM將字節碼翻譯爲機器的計算機指令(目標機器必需要安裝對應的JVM(java虛擬機))。
Java 語言使用字節碼的方式,必定程度的解決了解釋性語言執行效率低的問題,同時因爲字節碼不針對一種特定的機器,因此Java程序無須從新編譯就可在多種不一樣的計算機上運行。架構

字節碼的優勢總結來講就是:函數

  • 不針對特定CPU架構
  • 比原始的高級語言轉換成機器語言更快

V8的演變

1、V8早期架構

V8未誕生以前,早期最主流的JavaScript引擎是JavaScriptCore引擎。javasSriptCore是經過生成字節碼再將字節碼轉化成二進制代碼的方式運行的,而V8誕生的使命就是性能的極致,Google以爲這這種架構生成字節碼會浪費時間,V8早期採用了直接生成機器碼的方式以下圖:post

(圖片來源: blog.itpub.net/69912579/vi…

咱們一塊兒來看一下V8早期架構如何執行js代碼的:性能

  • 第一步,將js源代碼轉化成AST(抽象語法樹)
  • 第二步,經過Full-Codegen引擎編譯AST變成二進制文件,而後直接執行這些二進制文件。
  • 第三步,在執行二進制代碼的過程當中,標記重複執行的函數,將標記的代碼經過Crankshaft引擎進行優化編譯生成效率更高的二進制代碼,再次運行到這個函數時使用效率更高的二進制代碼。

同時採用了將二進制代碼緩存(緩存到內存和硬盤上)的策略來省去重複編譯的時間,在初期這種架構的確帶來了速度上的改善。優化

2、爲何要引入字節碼

隨着網頁的複雜化以及移動端的流行,早期的架構也帶來很是多的問題,編碼

1.內存佔用問題

最核心的問題就是空間佔用問題,在V8執行的過程會將js源代碼轉化成二進制代碼而且將二進制代碼存儲到內存中,退出進程後會將二進制代碼存儲到硬盤上。
將js源碼轉化成的二進制代碼佔用的內存空間是很是巨大的,若是說一個js源碼的文件大小是1M,那麼生成的二進制代碼可能就是十幾M,而早期手機的內存廣泛不高,過分佔用會致使性能大大下降。

2.代碼複雜度過高

上文提到過不一樣的CPU架構對應的指令集是徹底不一樣的,而市面上CPU架構的種類又很是多,那麼將AST轉化爲二進制代碼的Full-Codegen引擎以及優化編譯的Crankshaft引擎要針對不一樣的CPU架構編寫代碼,這個複雜程度及工做量可想而知,而對字節碼進行編譯能夠大大的減小這個工做量,大概以下圖:

3.一個Bug

咱們先來看一下這個 bug ,大概描述是這樣的:
Bug的報告人在當時的Chrome瀏覽器下重複加載Facebook,並打開了各項監控發現:第一次加載時 v8.CompileScript 花費了 165 ms,而重複加載時發現真正耗時高的js代碼並無被緩存,致使重複加載時編譯的時間和第一次加載的消耗大體相同。

致使這個問題的緣由其實也很好理解,以前提到過由於二進制代碼佔用內存空間大,根據惰性編譯的優化原則,因此V8並不會將全部代碼進行編譯只會編譯最外層的代碼,而在函數內部的代碼會在第一次調用時編譯,好比:

若是瀏覽器只緩存最外層代碼,那麼對咱們前端高度工程化的模塊來講會致使裏面的關鍵代碼卻沒法被緩存,這也是致使上述bug的主要緣由。

3、V8現有架構

爲了解決上述的問題,V8開始採用引入字節碼的架構,最終採用了以下圖的架構:

(圖片來源: blog.itpub.net/69912579/vi…
咱們來一塊兒看一下V8現有架構是如何執行js代碼的:

  • 第一步,將js源代碼轉化成AST(抽象語法樹)
  • 第二步,經過Ignition解釋器將AST編譯成字節碼,開始逐句對字節碼進行解釋成二進制代碼並執行。
  • 第三步,在解釋執行的過程當中,標記重複執行的熱點代碼,將標記的代碼經過Turbofan引擎進行編譯生成效率更高二進制代碼,再次運行到這個函數時便只執行高效代碼而再也不解釋執行字節碼。

V8引入了字節碼的架構模式後明顯的解決了以下問題:

  • 啓動時間較長:啓動時只須要編譯出字節碼,而後逐句執行字節碼,編譯出字節碼的速度可遠遠快於編譯出二進制代碼的速度。
  • 內存佔用較大:字節碼的空間佔用也是遠遠低於二進制代碼的空間佔用。
  • 代碼複雜度過高:大大下降了V8適應不一樣CPU所須要的代碼複雜程度。

最後咱們再來看一下新架構和原有架構比較帶來的效果:

總結

本文主要經過 字節碼 的特色來了解了V8引擎的架構演變過程分析了演變的緣由,以及對架構進行了一次梳理來幫助咱們後面繼續瞭解V8,若是有什麼錯誤,請在評論中和做者一塊兒討論,若是您以爲本文對您有幫助請幫忙點個贊,感激涕零。

參考文章

blog.itpub.net/69912579/vi…
time.geekbang.org/column/arti…

相關文章

V8引擎詳解(一)——概述
V8引擎詳解(二)——AST
V8引擎詳解(三)——從字節碼看V8的演變

相關文章
相關標籤/搜索