摘要:類將源代碼解釋并構(gòu)建成抽象語法樹,使用類來創(chuàng)建它們,并使用類來分配內(nèi)存。類抽象語法樹的訪問者類,主要用來遍歷抽象語法樹。在該函數(shù)中,先使用類來生成抽象語法樹再使用類來生成本地代碼。
通過上一篇文章,我們知道了JavaScript引擎是執(zhí)行JavaScript代碼的程序或解釋器,了解了JavaScript引擎的基本工作原理。我們經(jīng)常聽說的JavaScript引擎就是V8引擎,這篇文章我們就來認(rèn)識一下V8引擎,我們先來看一下除了V8引擎,還有哪些JS引擎:
V8 開源
由Google開發(fā),用C++編寫。V8 最早被開發(fā)用以嵌入到 Google 的開源瀏覽器 Chrome 中,但是 V8 是一個可以獨立的模塊,完全可以嵌入您自己的應(yīng)用,著名的 Node.js( 一個異步的服務(wù)器框架,可以在服務(wù)端使用 JavaScript 寫出高效的網(wǎng)絡(luò)服務(wù)器 ) 就是基于 V8 引擎的。
Rhino開源
?由Mozilla基金所管理,完全用Java開發(fā)
JavaScriptCore 開源
由蘋果公司為Safari開發(fā)
SpiderMonkey
第一個JavaScript引擎,最早用在Netscape Navigator上,現(xiàn)在用在Firefox上
KJS
KDE的引擎,最初由Harri Porten為KDE項目的Konqueror網(wǎng)頁瀏覽器所開發(fā)
Chakra(JScript9)
?Internet Explorer 瀏覽器
Chakra(JavaScript)
Microsoft Edge
Nashorn
OpenJDK開源項目的一部分,用的是Oracle Java語言和工具組
JerryScript
用于物聯(lián)網(wǎng)的輕量級引擎
在這些項目中,V8引擎因其在性能上的突出表現(xiàn),倍受大家的關(guān)注,所以我們也以介紹V8引擎為主。V8是Google開源的高性能JavaScript引擎,用C++編寫。它用于谷歌瀏覽器,谷歌的開源瀏覽器,以及Node.js等等。
速度是V8追求的主要設(shè)計目標(biāo)之一,在一些性能測試中,V8比IE的JScript,F(xiàn)irefox中的SpiderMonkey以及Safari中的JavaScriptCore要快上數(shù)倍。相比其他的JavaScript引擎轉(zhuǎn)化成字節(jié)碼或解釋執(zhí)行,V8將其編譯成本地代碼,并且使用了如隱類型,內(nèi)聯(lián)緩存等方法來提高性能。
http://kourge.net/node/122
V8按照ECMA-262第5版中的規(guī)定實施ECMAScript,支持眾多操作系統(tǒng),如windows、linux、android等,也支持其他硬件架構(gòu),如IA32,X64,ARM等,具有很好的可移植和跨平臺特性。
V8的工作過程V8工作的整個過程與Java有些類似,大致分成兩個階段:第一是編譯,第二是運行。與C++直接編譯成本地代碼不同的是,V8只有在函數(shù)調(diào)用時才會編譯成本地代碼,這樣就提高了響應(yīng)時間減少了時間開銷。
圖片來源《WebKit技術(shù)內(nèi)幕》
在V8引擎中,源代碼先通過解析器轉(zhuǎn)變成抽象語法樹,這點同JavaScriptCore引擎一樣,不同于JavaScriptCore引擎,V8引擎中并不將抽象語法樹轉(zhuǎn)變成字節(jié)碼或者其他中間表示,而是通過JIT全代碼生成器(full code generator)從抽象語法樹直接生成本地代碼,這樣做可以減少抽象語法樹到字節(jié)碼的轉(zhuǎn)換時間,提高代碼的執(zhí)行速度,但也是因為缺少了轉(zhuǎn)換為字節(jié)碼這一中間過程,也就減少了優(yōu)化中間代碼的機會。
下面來看一下V8引擎編譯JavaScript生成本地代碼使用了哪些主要類:
Script類:表示是JavaScript代碼,既包含源代碼,又包含編譯之后生成的本地代碼,所以它既是編譯入口,又是運行入口
Compiler類:編譯器類,輔助Script類來編譯生成代碼,它主要起一個協(xié)調(diào)者的作用,會調(diào)用解釋器(Parser)來生成抽象語法樹和全代碼生成器,來為抽象 語法樹生成本地代碼。
Parser類:將源代碼解釋并構(gòu)建成抽象語法樹,使用AstNode類來創(chuàng)建它們,并使用Zone類來分配內(nèi)存。
AstNode類:抽象語法樹節(jié)點類,是其他所有節(jié)點的基類,它包含非常多的子類,后面會針對不同的子類生成不同的本地代碼。
AstVisitor類:抽象語法樹的訪問者類,主要用來遍歷抽象語法樹。
FullCodeGenerator:AstVisitor類的子類,通過遍歷抽象語法樹來為JavaScrit生成本地代碼。
圖片來源《WebKit技術(shù)內(nèi)幕》
JavaScript代碼編譯的過程大致為:Script類調(diào)用Compiler類的Compile函數(shù)生成本地代碼。在該函數(shù)中,先使用Parser類來生成抽象語法樹;再使用FullCodeGenerator類來生成本地代碼。
圖片來源《WebKit技術(shù)內(nèi)幕》
本地代碼與具體的硬件平臺密切相關(guān),F(xiàn)ullCodeGenerator使用多個后端來生成與平臺相匹配的本地匯編代碼。由于FullCodeGenerator通過遍歷AST來為每個節(jié)點生成相應(yīng)的匯編代碼,缺失了全局視圖,節(jié)點之間的優(yōu)化也就無從談起。
JavaScript代碼編譯之前需要構(gòu)建一個運行環(huán)境,所以JavaScript代碼編譯之前,V8引擎會構(gòu)建眾多對象并加載一些內(nèi)置的庫(如Math庫)。再次強調(diào)一下,在JavaScript源碼中,并非所有的函數(shù)都被編譯生成本地代碼,而是延時編譯,在調(diào)用時才會編譯。
由于V8缺少生成字節(jié)碼(中間表示)這一環(huán)節(jié),缺少必要的優(yōu)化,為了性能上的考慮,V8會在生成本地代碼后,使用數(shù)據(jù)分析器(Profiler)采集一些信息,然后根據(jù)這些信息對本地代碼進行優(yōu)化,生成更高效率的本地代碼,這是一個逐步改進的過程。同時,當(dāng)發(fā)現(xiàn)優(yōu)化后的代碼性能并沒有提高甚至還有所降低時,V8將退回到原來的代碼。這些都是在運行階段用涉及到的技術(shù)。
現(xiàn)在我們來看一下運行階段使用到的類:
Script: 表示是JavaScript代碼,既包含源代碼,又包含編譯之后生成的本地代碼,所以它既是編譯入口,又是運行入口
Execution: 運行代碼的輔助類,包含一些重要的函數(shù),例如call,它輔助進入和執(zhí)行Script中的本地代碼
JSFunction: 需要執(zhí)行的JavaScript函數(shù)表示類
Runtime:運行這些本地代碼的輔助類,它的功能主要是提供運行時各種各樣的輔助函數(shù),包括但是不限于屬性訪問、類型轉(zhuǎn)換、編譯、算數(shù)、位操作、比較、正則表達(dá)式等
Heap:運行本地代碼需要使用內(nèi)存堆
MarkCompactCollector:垃圾回收機制的主要實現(xiàn)類,用來標(biāo)記(Mark)、清除(Sweep)和整理(Compact)等基本的垃圾回收過程
SweeperThread:負(fù)責(zé)垃圾回收的線程
圖片來源《WebKit技術(shù)內(nèi)幕》
首先,當(dāng)某個JavaScript函數(shù)被調(diào)用時,使用編譯階段的類和操作編譯生成本地代碼。具體的工作方式是V8查找函數(shù)是否已經(jīng)生成本地代碼,如果已經(jīng)生成,那么直接使用這個函數(shù)。否則,V8引擎會觸發(fā)生成本地代碼,這樣的工作方式可以節(jié)約時間,減少去處理那些使用不到的代碼的時間。其次,執(zhí)行編譯后的代碼為JavaScript構(gòu)建JS對象,這需要Runtime類來輔助創(chuàng)建對象,并需要從Heap類分配內(nèi)容。再次,借助Runtime類中的輔助函數(shù)來完成一些功能,如屬性訪問,類型轉(zhuǎn)換等。最后,將不用的空間進行標(biāo)記清除和垃圾回收。
圖片來源《WebKit技術(shù)內(nèi)幕》
FullCodeGenerator編譯器基于抽象語法樹直接生成本地代碼,沒有中間表示層,所以很多時候沒有經(jīng)過很好的優(yōu)化。JavaScript引擎性能之爭非常激烈,沒有經(jīng)過優(yōu)化的代碼導(dǎo)致該引擎在性能上同有特別大的突破,而其他引擎都在進度,有鑒于此,在2010年,V8引入了新的編譯器,這就是Crankshaft編譯器,它主要針對那些熱點函數(shù)進行優(yōu)化。該編譯器基于JavaScript源代碼開始分析,而不是本地代碼,同時構(gòu)建Hydtogen圖并基于此來進行優(yōu)化分析。
FullCodeGenerator是一個簡單且快的編譯器,生成未優(yōu)化的本地代碼,運行起來很慢;Crankshaft是一個相對慢的編譯器,生成高度優(yōu)化的代碼。由FullCodeGenerator生成的未優(yōu)化代碼Crankshaft優(yōu)化代碼替換,傳送門。
Crankshaft編譯器為了性能考慮,通常會做出比較樂觀和大膽的預(yù)測,那就是編譯器認(rèn)為這些代碼比較穩(wěn)定,變量類型不會發(fā)生改變,所以能夠生成高效的本地代碼。但是在實際執(zhí)行過程中,因為JavaScript弱類型語言的特性,變量類型有可能會改變,在這種情況下,V8會將該編譯器做的錯誤優(yōu)化回滾到之前的一般情況,這個過程稱為優(yōu)化回滾。
V8并不只是第一次執(zhí)行一個JavaScript函數(shù)時才編譯它;同一個JavaScript函數(shù)可以被這些JIT編譯器多次編譯。
基本流程是:
[JavaScript函數(shù)] -> 第一次被調(diào)用 -> Full Code -> [初級編譯后的代碼] 足夠熱之后 -> Crankshaft(Optimizing Compiler) -> [優(yōu)化編譯后的代碼] 如果優(yōu)化的代碼需要去優(yōu)化(優(yōu)化回滾) -> deoptimize -> 回到[初級編譯后的代碼] ... 周而復(fù)始 ...
示例如下:
var counter = 0; function test(x,y){ counter ++; if(counter < 10000000){ // do something return 123; } var unknown = new Date(); console.log(unknown); }
函數(shù)test被調(diào)用多次后,V8引擎可能會觸發(fā)Crankshaft編譯器來生成優(yōu)化的代碼,優(yōu)化的代碼認(rèn)為示例代碼的類型等信息都已經(jīng)被獲知,但事實上還未真正執(zhí)行到new Date()這個地方,并未獲取unknown這個變量的類型,V8只得將該部分的代碼進行回滾。優(yōu)化回滾是一個很費時的操作,所以在寫代碼的過程中,盡量不要觸發(fā)這個過程。
二. 隱類型和內(nèi)嵌緩存我們都知道JavaScript屬于動態(tài)類型語言,只有在運行時才能確定變量的類型,在運行時計算和決定類型,會帶來嚴(yán)重的性能損失,這也就導(dǎo)致了JavaScript語言的運行效率比C++或Java都要低很多。
主要體現(xiàn)在以下幾個部分:
編譯確定位置:
C++在編譯階段對象的屬性和偏移信息都計算完成;而這些信息JavaScript只有在執(zhí)行階段才可以確定
偏移信息共享:
C++屬于靜態(tài)類型語言,不能在執(zhí)行時動態(tài)改變類型,這些對象都是共享偏移信息的。訪問對象時就按編譯時的偏移量即可;JavaScript每個對象都是自描述,屬性和位置偏移信息都包含在自身的結(jié)構(gòu)中。
一個簡單的C++函數(shù):
class Class1 { int x; int y; } int add(Class1 a, Class1 b){ return a.x*a.y + b.x*b.y; }
示例代碼中的類型和對象的結(jié)構(gòu)表示,如下圖:
圖片來源《WebKit技術(shù)內(nèi)幕》
一個簡單的JavaScript函數(shù):
function add(a,b){ return a.x*a.y + b.x*b.y; // 這里對象a和b的類型未知 } var a = {x:3.3,y:5.5}; var b = {x:4.4,y:6.6};
示例代碼中對象a和b的結(jié)構(gòu)表示,如下圖:
圖片來源《WebKit技術(shù)內(nèi)幕》
偏移信息查找:
C++中查找偏移地址很簡單,都是在編譯代碼時,對使用到某類型的成員變量直接設(shè)置偏移量。而對于JavaScript,使用到一個對象則需要通過屬性名匹配才能查找到對應(yīng)的值。
因為對象屬性的訪問非常普遍而且次數(shù)非常頻繁,像C++這種通過偏移量來訪問值使用少數(shù)兩個匯編指定就能完成,但是Javascript這種通過屬性名來匹配對于性能造成的影響可能會多很多倍,因為屬性名匹配需要特別長的時間,而且額外浪費很多內(nèi)存空間。
有方法解決這一問題么?答案是肯定的。下面我們就來看一下V8引擎是如何解決這一問題的。雖然JavaScript語言沒有類型的定義,但是V8使用類和偏移位置思想,將本來需要通過字符串匹配來查找屬性值的算法改進為使用類似C++編譯器的偏移位置的機制來實現(xiàn)。這就是隱藏類(Hidden Class)。
JavaScript對象的實現(xiàn)在V8中包含3個成員,第一個是隱藏類的指針,這是V8為JavaScript對象創(chuàng)建的隱藏類。第二個指向這個對象包含的屬性值。第三個指向這個對象包含的元素。
圖片來源《WebKit技術(shù)內(nèi)幕》
隱藏類將對象劃分成不同的組,對于相同的組,也就是該組內(nèi)的對象擁有相同的屬性名和屬性值的情況,將這些屬性名和對應(yīng)的偏移位置保存在一個隱藏類中,組內(nèi)的所有對象共享該信息。同時,也可以識別屬性不同的對象。
V8引擎的發(fā)展歷史2008年9月,V8的第一個版本隨著Chrome的第一版發(fā)布。
2010年12月,官方公布V8的名為Crankshaft的優(yōu)化編譯器,與原來的Full Compiler一起工作,聲稱較2008年版本提高50%性能。
2015年7月7日,官方公布又一個新的中為TurBoFan的優(yōu)化編譯器,主要提供ES6的新語法,以及提高性能。并表明該編譯器最終目標(biāo)是全部替代Crankshaft編譯器。
2015年7月17日,官方公布集成了TurboFan的V8版本(v4.5)
2015年8月28日,V8發(fā)布v4.6版本
2016年3月15日,V8發(fā)布v5.0版本
2016年7月18日,V8發(fā)布v5.3版本,新增名為Ignition的解析器(Interpreter),跟原有的優(yōu)化編譯器(Crankshaft and TurboFan)進行串聯(lián)工作,提供了更加優(yōu)化的內(nèi)存使用方案,主要針對于低內(nèi)存的Android設(shè)備,并稱在未來會普及到全平臺。
2016年9月9日,V8發(fā)布v5.4版本
2016年10月24日,V8發(fā)布v5.5版本,在5.5版本中開始支持ES7異步函數(shù),這使得編寫使用和創(chuàng)建Promise的代碼變得更加容易。
2016年12月2日,V8發(fā)布v5.6版本,從5.6版本開始,V8可以優(yōu)化整個JavaScript語言。而且,許多語言功能都是通過V8中的新優(yōu)化管道發(fā)送的。該管道使用V8的Ignition解釋器作為基準(zhǔn),并使用V8更強大的TurboFan優(yōu)化編譯器優(yōu)化經(jīng)常執(zhí)行的方法。新的流水線激活了新的語言功能(例如ES2015和ES2016規(guī)范中的許多新功能)或Crankshaft(V8的“經(jīng)典”優(yōu)化編譯器)無法優(yōu)化某種方法(例如try-catch,with)的情況。
2017年2月6日,V8發(fā)布v5.7版本
2017年3月20日,V8發(fā)布v5.8版本
2017年4月27日,V8發(fā)布v5.9版本,V8 5.9將成為默認(rèn)啟用Ignition + Turbofan的第一個版本。一般來說,這種交換機應(yīng)該可以降低內(nèi)存消耗,并且可以更快地啟動Web應(yīng)用程序。
2017年6月9日,V8發(fā)布v6.0版本,V8 6.0引入了對SharedArrayBuffer的支持,SharedArrayBuffer是一種在JavaScript工作人員之間共享內(nèi)存并在工作人員之間同步控制流的低級機制。 SharedArrayBuffers為JavaScript提供了對共享內(nèi)存,原子和futex的訪問。 SharedArrayBuffers還解鎖了通過asm.js或WebAssembly將線程化應(yīng)用程序移植到Web的功能。
2017年8月3日,V8發(fā)布v6.1版本
2017年9月11日,V8發(fā)布v6.2版本
2017年10月25日,V8發(fā)布v6.3版本,改進了速度和內(nèi)存消耗,詳細(xì)
2017年12月19日,V8發(fā)布v6.4版本,提升了速度和優(yōu)化內(nèi)存消耗,詳細(xì)
2018年2月1日,V8發(fā)布v6.5版本,編譯速度顯著提升,詳細(xì)
2018年3月27日,V8發(fā)布v6.6版本,異步性能大幅提升,詳細(xì)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/94202.html
摘要:引擎可以用標(biāo)準(zhǔn)解釋器或即時編譯器來實現(xiàn),即時編譯器以某種形式將代碼編譯為字節(jié)碼。這里的主要區(qū)別在于不生成字節(jié)碼或任何中間代碼。請注意,不使用中間字節(jié)碼表示法,不需要解釋器。這允許在正常執(zhí)行期間非常短的暫停。 本系列的第一篇文章重點介紹了引擎,運行時和調(diào)用棧的概述。第二篇文章將深入V8的JavaScript引擎的內(nèi)部。我們還會提供一些關(guān)于如何編寫更好的JavaScript代碼的技巧。 概...
摘要:第二篇文章將深入谷歌的引擎的內(nèi)部。引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將編譯為字節(jié)碼的即時編譯器。這個引擎是在谷歌中使用的,但是,與其他引擎不同的是也用于流行的。一種更復(fù)雜的優(yōu)化編譯器,生成高度優(yōu)化的代碼。不是唯一能夠做到的引擎。 本系列的 第一篇文章 主要介紹引擎、運行時和調(diào)用堆棧。第二篇文章將深入谷歌 V8 的JavaScript引擎的內(nèi)部。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHu...
摘要:第二篇文章將深入谷歌的引擎的內(nèi)部。引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將編譯為字節(jié)碼的即時編譯器。這個引擎是在谷歌中使用的,但是,與其他引擎不同的是也用于流行的。一種更復(fù)雜的優(yōu)化編譯器,生成高度優(yōu)化的代碼。不是唯一能夠做到的引擎。 本系列的 第一篇文章 主要介紹引擎、運行時和調(diào)用堆棧。第二篇文章將深入谷歌 V8 的JavaScript引擎的內(nèi)部。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHu...
摘要:本文將會深入分析的引擎的內(nèi)部實現(xiàn)。該引擎使用在谷歌瀏覽器內(nèi)部。同其他現(xiàn)代引擎如或所做的一樣,通過實現(xiàn)即時編譯器在執(zhí)行時將代碼編譯成機器代碼。這可使正常執(zhí)行期間只發(fā)生相當(dāng)短的暫停。 原文 How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code 幾周前我們開始了一個系列博文旨在深入...
摘要:本章將會深入谷歌引擎的內(nèi)部結(jié)構(gòu)。一個引擎可以用標(biāo)準(zhǔn)解釋程序或者即時編譯器來實現(xiàn),即時編譯器即以某種形式把解釋為字節(jié)碼。引擎的由來引擎是由谷歌開源并以語言編寫。注意到?jīng)]有使用中間字節(jié)碼來表示,這樣就不需要解釋器了。 原文請查閱這里,略有刪減。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第二章。 本章將會深入谷歌 V8 引擎的內(nèi)部結(jié)構(gòu)。我們也會...
閱讀 2573·2021-10-08 10:04
閱讀 2734·2021-09-06 15:02
閱讀 791·2019-08-30 13:50
閱讀 1547·2019-08-30 13:21
閱讀 2586·2019-08-30 11:15
閱讀 2112·2019-08-29 17:19
閱讀 1573·2019-08-26 13:55
閱讀 1260·2019-08-26 10:15