什么是JavaScript引擎?
簡單來講,就是能夠提供執行JavaScript代碼的運行環境。要解釋這一概念,需要了解一些編譯原理的基礎概念和現代語言需要的一些新編譯技術。
首先來看C/C++語言 - 編譯執行。
它們是比較悠久的語言了,實際上就是使用編譯器直接將它們編譯成本地代碼,這一切都是由開發人員在代碼編寫完成之后實施。用戶只是使用這些編譯好的本地代碼,這些本地代碼被系統的加載器加載執行,這些本地代碼(也就是機器指令)由操作系統調度CPU直接執行,無需其它額外的輔助虛擬機等。這一過程基本上是從源代碼開始,然后抽象語法樹,之后中間表示,最后到本地代碼。其次,來看看Python等腳本語言 - 解釋執行。
處理腳本語言通常的做法是開發者將寫好的代碼直接交給用戶,用戶使用腳本的解釋器將腳本文件加載然后解釋執行。當然,現在Python也可以支持將腳本編譯生成中間表示,當然這是后話。所以這表示,對于腳本語言,沒有開發人員的編譯過程,當然也不是絕對,這主要因為使用場景不一樣,它的目的不是高性能。這一過程是源代碼,到抽象語法樹,再到解釋器解釋執行。-
然后來看看Java語言。
其做法可以理解為比較明顯的兩個階段:- 首先是像C++語言一樣的編譯器,但是,同C++編譯器生成的本地代碼結果不同,經過編譯器編譯之后的是字節碼,字節碼是平臺無關的。
- 在運行字節碼階段,Java的運行環境也就是Java虛擬機會加載字節碼,使用解釋執行這些字節碼。
如果僅是這樣,那Java的性能就差C++太多了?,F代Java虛擬機一般都引入了JIT技術,也就是前面說的將字節碼轉變成本地代碼來提高執行效率。這主要兩個階段,第一階段對時間要求不嚴格,第二階段則對每個步驟所花費的時間非常敏感,時間越短越好。
最后回到JavaScript語言上來。前面已經說了它是一種解釋性腳本語言。是的,它的確是,但是隨著眾多工程師不斷投入資源來提高它的速度,這使得它能夠使用了Java虛擬機和C++編譯器中眾多的技術,它的工作方式也在演變:
- 早期也是一樣由解釋器來解釋它們即可,就是將源代碼轉變成抽象語法樹,然后在抽象語法樹上解釋執行。
- 隨著將Java虛擬機的JIT技術引入,現在的做法是將抽象語法樹轉成中間表示(也就是字節碼),然后通過JIT技術轉成本地代碼,這能夠大大的提高了執行效率。當然也有些做法直接從抽象語法樹生成本地代碼的JIT技術,例如V8。
這是因為JavaScript跟Java還是有以下一些區別的:
- 其一當然是類型,JavaScript是無類型的語言,這使得對于對象的表示和屬性的訪問上比Java存在比較大的性能損失。不過現在有一些新的技術,參考C++或者Java的類型系統的優點,構建隱式的類型信息,這些后面逐一介紹。
- 其二是Java語言通常是將源代碼編譯成字節碼,這個同執行階段是分開的,也就是從源代碼到抽象語法樹到字節碼這段時間的長短是無所謂的(或者說不是特別重要),所以主要是盡可能的生成高效的字節碼即可。而對于JavaScript而言,這些都是在網頁和JavaScript文件下載后同執行階段一起在網頁的加載和渲染過程中來實施的,所以對它們的處理時間也有著很高的要求。
描述JavaScript代碼執行的過程,這一過程中因為不同技術的引入,導致非常復雜,而且因為都是在代碼運行過程中來處理這些步驟,所以每個階段時間越少越好,而且每引入一個階段都是額外的時間開銷,可能最后的本地代碼執行效率很高,但是之前步驟如果耗費太多時間的話,最后的執行結果可能并不會好。所以不同的JavaScript引擎選擇了不同的路徑,這里先不仔細介紹,后面再描述它們。
所以一個JavaScript引擎不外乎包括以下部分:
第一, 編譯器。主要工作是將源代碼編譯成抽象語法樹,然后在某些引擎中還包含將抽象語法樹轉換成字節碼。
第二, 解釋器。在某些引擎中,解釋器主要是接受字節碼,解釋執行這個字節碼,然后也依賴來及回收機制等。
第三, JIT工具。一個能夠能夠JIT的工具,將字節碼或者抽象語法樹轉換成本地代碼,當然它也需要依賴牢記
第四, 垃圾回收器和分析工具(profiler)。它們負責垃圾回收和收集引擎中的信息,幫助改善引擎的性能和功效。
JavaScript引擎和渲染引擎
網頁的工作過程需要使用到兩個引擎,也就是渲染引擎和JavaScript引擎。從模塊上看,目前,它們是兩個獨立的模塊,分別負責不同的事情:JavaScript引擎負責執行JavaScript代碼,而渲染引擎負責渲染網頁。
JavaScript引擎提供調用接口被渲染引擎使用,渲染引擎使用JavaScript引擎來處理JavaScript代碼并獲取結果。這當然不是全部,事情不是這么簡單,JavaScript引擎需要能夠訪問渲染引擎構建的DOM樹,所以JavaScript引擎通常需要提供橋接的接口,而渲染引擎則根據橋接接口來提供讓JavaScript訪問DOM的能力。
在現在眾多的HTML5能力中,很多都是通過JavaScript接口提供給開發者的,所以這部分同樣需要根據橋接接口來實現具體類,以便讓JavaScript引擎能夠回調渲染引擎的具體實現。下圖是一個簡單的關于兩者引擎的關系。
在WebKit/Blink項目中,這兩種引擎通過橋接接口來訪問DOM結構,這對性能來說是一個重大的損失,因為每次JavaScript代碼訪問DOM都需要通過復雜和低效的橋接接口來完成。鑒于訪問DOM樹的普遍性,這是一個常見的問題。希望以后可以減少這一方面的開銷。
JavaScriptCore引擎
JavaScriptCore引擎是WebKit中默認的引擎,在早期階段,它的性能不是特別突出。特別是,它只有解釋器來解釋執行JavaScript代碼。這一效率十分的低效,當然從2008年開始,JavaScriptCore引擎開始一個新的優化工作,就是重新實現了編譯器和字節碼解釋器,這就是SquirrelFish。該工作對于引擎的性能優化作了比較大的改進。隨后,蘋果內部代號為”Nitro”的JavaScript引擎也是基于JavaScriptCore項目的,它的性能還是非常出色的,鑒于其是內部項目,所以具體還有什么特別的處理就不得而知了。在這之后,開發者們又將內嵌緩存、基于正則表達式的JIT和簡單的JIT引入到JavaScriptCore中。然后,又陸續加入的字節碼解釋器??梢钥闯?,JavaScriptCore引擎也在不斷的高速發展中。
V8引擎
V8是Google的一個開源項目,現在成為了JavaScript引擎和眾多相關技術的引領者。它的目的很簡單,就是為了提高性能。因為之前不管JavaScriptCore引擎還是其它JavaScript引擎,在當時的情況下,它們的性能都不能令人非常滿意。為了達到高性能的JavaScript代碼執行效率從而獲得更好的網頁瀏覽效果,它甚至采用直接將JavaScript編譯成本地代碼的方式。
V8支持眾多的操作系統,包括但是不限于Windows、Linux、Android、Mac OS X等。同時它也是能夠支持眾多的硬件架構,例如IA32、X64、ARM、MIPS等。這么看下來,它將主流軟硬件平臺一網打盡,由于它是一個開源項目,開發者可以自由的使用它的強大能力,一個例子就是目前炙手可熱的NodeJS項目,它就是基于V8項目的。開源的好處就是大家可以很方便地學習、貢獻和使用,下圖是V8引擎最基礎的代碼執行過程。
從圖中可以看出,首先它也是將源代碼轉變成抽象語法樹的,這一點同JavaScriptCore引擎一樣,之后兩個引擎開始分道揚鑣。不同于JavaScriptCore引擎,V8引擎并不將抽象語法樹轉變成字節碼或者其它中間表示,而是通過JIT編譯器的全代碼生成器(full code generator)從抽象語法樹直接生成本地代碼,所以沒有像Java一樣的虛擬機或者字節碼解釋器。
這樣做的原因,主要是因為減少這抽象語法樹到字節碼的轉換時間,這一切都在網頁加載時候完成,雖然可以提高優化的可能,但是這些分析可能帶來巨大的時間浪費。
當然,缺點也很明顯,至少包括兩點:
- 第一是某些JavaScript使用場景其實使用解釋器更為合適,因為沒有必要生成本地代碼;
- 第二是因為沒有中間表示,會減少優化的機會因為缺少一個中間表示層。
在之后的版本中,V8工程師們引入了Crankshaft編譯器,它能夠對熱點的JS函數進行分析和優化后再生成本地代碼,原因在于不是所有的JavaScript代碼都合適做如此深層次的優化,因為優化本身也需要花費一定的時間。
目前V8和JavaScriptCore引擎各有特色,相信兩者的發展能夠不斷推進JavaScript語言的執行性能。
本文參考http://blog.csdn.net/milado_nju/article/details/22101681