上世紀九十年代的第一次瀏覽器大戰,微軟憑借其雄厚的資金和超過(guò)90%的桌面操作系統覆蓋率,毫無(wú)懸念地擠掉了網(wǎng)景,讓IE成為當時(shí)市場(chǎng)上的主導瀏覽器,巔峰時(shí)一度達到了96%的市場(chǎng)份額。此后數年,IE一枝獨秀,沒(méi)有了競爭對手的同時(shí)也放緩(可以說(shuō)是停滯)了瀏覽器技術(shù)革新的腳步。
瀏覽器
JavaScript引擎
發(fā)布時(shí)間
Firefox 3.5
TraceMonkey
2009
Chrome
V8
2008
Safari
Nitro
2008
Opera
CaraKan
2009
圖1 瀏覽器與JavaScript引擎
直到2004年11月的firefox1.0發(fā)布開(kāi)始,瀏覽器市場(chǎng)才又開(kāi)始活躍起來(lái),2008~2009年,Chrome,Safari,Opera群雄發(fā)力,這個(gè)階段這也被業(yè)界稱(chēng)作是第二次瀏覽器大戰。從下面這個(gè)表中,可以看到各家瀏覽器廠(chǎng)商在這一階段都引入了全新的JavaScript引擎??梢哉f(shuō)這次瀏覽器大戰的核心就是JavaScript引擎的較量?! 〈藭r(shí)一向不太努力的IE瀏覽器,也肯定不會(huì )無(wú)動(dòng)于衷,于是我們在第二年的春天(2010年3月)迎來(lái)了IE9,而微軟的應對策略即是最新引入的JavaScript引擎Chakra。 JavaScript引擎的性能對整個(gè)瀏覽器的影響可以說(shuō)是至關(guān)重要,就在過(guò)去的幾年里,各個(gè)瀏覽器的JavaScript引擎性能都有質(zhì)的飛躍。引用Dave Mandelin在“How to Make Your JavaScript Fast”一文中一張圖:
圖2 JavaScript引擎的性能提升示意圖
在過(guò)去5~6年,JavaScript引擎的性能普遍提高了十倍甚至幾十倍以上, 運行時(shí)性能已經(jīng)非常接近C/C++了。在享受了性能提升所帶來(lái)的超炫體驗之后,我們也來(lái)探探性能提升背后的技術(shù)變遷?! ”疚耐ㄟ^(guò)分析開(kāi)源的瀏覽器項目中的JavaScript引擎,歷數JavaScript引擎的幾種實(shí)現方式:從原始的遍歷語(yǔ)法樹(shù),到字節碼方式(bytecode interpreter),直到引入了JIT編譯方式。每一種新方式的出現都是JavaScript引擎性能的一次飛躍。遍歷語(yǔ)法樹(shù)(Syntax Tree Walker) 和很多腳本語(yǔ)言的解釋器一樣,JavaScript引擎最先也是采用遍歷語(yǔ)法樹(shù)(syntax tree walker)的方式。執行一條語(yǔ)句的過(guò)程就是遍歷一次相對應的語(yǔ)法樹(shù)的過(guò)程。舉個(gè)例子:一條賦值語(yǔ)句: i = a + b * c; 經(jīng)過(guò)詞法分析后,就生成了一堆單詞流: 'i', '=', 'a', '+', 'b', '*', 'c'; 再經(jīng)過(guò)語(yǔ)法分析后,就生成了如下的語(yǔ)法樹(shù):
圖3 賦值語(yǔ)句的語(yǔ)法樹(shù)
執行這條語(yǔ)句,就是遍歷這顆語(yǔ)法樹(shù)的過(guò)程。遍歷語(yǔ)法樹(shù)的過(guò)程在程序設計上一般采用訪(fǎng)問(wèn)者模式(vistor pattern)來(lái)實(shí)現。要遍歷這顆語(yǔ)法樹(shù),只要將根節點(diǎn)傳給visit函數, 然后這個(gè)函數遞歸調用相應子節點(diǎn)的visit函數,如此反復直到葉子節點(diǎn)。例如,在這個(gè)例子中根節點(diǎn)是個(gè)賦值語(yǔ)句,他知道應該計算出右邊表達式的值,然后賦給左邊的地址;而在計算右邊表達式的時(shí)候,發(fā)現是一個(gè)加法表達式,于是接著(zhù)遞歸計算加法表達式的值,如此遞歸進(jìn)行直到這顆樹(shù)的葉子節點(diǎn),然后一步步回溯,將值傳到到根節點(diǎn),就完成了一次遍歷,也即完成了一次執行。 這樣的方式雖然原始,但是實(shí)現起來(lái)簡(jiǎn)單,對于性能要求不高,只是完成小任務(wù)的一些腳本語(yǔ)言,也是可以接受的。WebKit的JavaScript引擎在2008年6月(SquirrelFish的推出)之前就是用這種方式。 每次執行這條語(yǔ)句,都要進(jìn)行一次遍歷樹(shù)的過(guò)程,這種方式存在著(zhù)很大的性能缺陷: 1. 語(yǔ)法樹(shù)只是描述語(yǔ)法結構,并不是執行這條語(yǔ)句要進(jìn)行的操作。例如對于語(yǔ)句:{x = 1; y = x;},根節點(diǎn)是個(gè)復合語(yǔ)句“{...}”,他的子節點(diǎn)是兩個(gè)賦值語(yǔ)句,解釋器首先訪(fǎng)問(wèn)這個(gè)復合語(yǔ)句節點(diǎn),但實(shí)際上沒(méi)有做任何事情,然后訪(fǎng)問(wèn)第一個(gè)賦值語(yǔ)句,接著(zhù)訪(fǎng)問(wèn)第二個(gè)賦值語(yǔ)句。在一個(gè)復雜的程序中,這種不是執行單元,但卻是語(yǔ)法樹(shù)不可或缺的節點(diǎn)很多。因此就會(huì )導致做很多無(wú)用功。 2. 訪(fǎng)問(wèn)每個(gè)節點(diǎn)的代價(jià)太大。遍歷語(yǔ)法樹(shù)一般采用訪(fǎng)問(wèn)者模式,訪(fǎng)問(wèn)每個(gè)節點(diǎn)至少需要一次虛函數調用和返回,也即有兩個(gè)間接跳轉,而對于現代CPU而言,這種間接跳轉意味著(zhù)大大增加了分支預測失敗的可能行(后文還有關(guān)于分支預測的討論)。 因此采用遍歷語(yǔ)法樹(shù)方式的JavaScript引擎是很低效的,而且有很大的提升空間。所以即使WebKit團隊對這種syntax tree walker已經(jīng)優(yōu)化到了極致,但是由于這個(gè)方式中存在著(zhù)上述提到的固有缺陷,之后他們也跟其它的JavaScript引擎一樣引入了字節碼(bytecode)。字節碼(bytecode) 從上面介紹的遍歷語(yǔ)法樹(shù)方式中可以發(fā)現,要執行一棵語(yǔ)法樹(shù),實(shí)際上是一個(gè)后序遍歷樹(shù)的過(guò)程。以上面這個(gè)例子,要計算賦值語(yǔ)句,先計算加法表達式,那就必須先計算乘法表達式,也就是說(shuō)只有子結點(diǎn)計算好了之后,父節點(diǎn)才能計算,典型的后序遍歷。 如果在后序遍歷這棵樹(shù)后,生成對應的后綴記法(逆波蘭式)的操作序列,然后在執行時(shí),直接解釋執行這個(gè)后綴記法的操作序列。那么這時(shí)候就把這種樹(shù)狀結構,變換成了一種線(xiàn)性結構。這種操作序列就是字節碼(bytecode),這種執行方式就是字節碼解釋方式(bytecode interpreter)。沿用上面那個(gè)遍歷語(yǔ)法樹(shù)的例子,轉成相應的字節碼,如下圖所示:

圖4語(yǔ)法樹(shù)轉換成bytecode
在JavaScript引擎中直接執行字節碼,肯定比每次都遍歷一遍語(yǔ)法樹(shù)高效?! ∽止澊a,是一種與平臺無(wú)關(guān)的,需要在對應的虛擬機中執行的中間表示。如Java編譯器把Java源代碼編譯成Java字節碼,然后在對應平臺的Java虛擬機中執行; ActiveScript語(yǔ)言,也是通過(guò)轉換成字節碼,然后在對應的FLASH虛擬機中執行的。Java和ActionScript語(yǔ)言都有標準的字節碼格式,但是JavaScript的字節碼沒(méi)有標準的格式,每個(gè)JavaScript引擎廠(chǎng)商都有自己的標準。 雖然標準不一,但是JavaScript字節碼在設計上大體上都可歸類(lèi)為以下兩類(lèi):基于棧(stack-based)和基于寄存器(register-based)。 傳統的字節碼設計大多是基于棧的,這種方式將所有的操作數和中間表示都保存在一個(gè)數據棧中。如語(yǔ)句:c = a + b,轉換后的字節碼如下: LOAD a # 將a推入棧頂LOAD b # 將b推入棧頂ADD # 從棧頂彈出兩個(gè)操作數,相加后,將結果推入棧頂STORE c #將棧頂數據保存到C中 基于寄存器的字節碼通過(guò)一些槽(slot)或稱(chēng)為寄存器(register)的方式保存操作數。這里的寄存器與匯編代碼中的寄存器是兩個(gè)概念。存在寄存器(或槽)中可以想象成就是存入一個(gè)固定數組中了。上面例子要是轉換成基于寄存器的代碼如下: ADD c, a, b # 兩個(gè)操作數分別存在a和b中,將結果放在c中?! ∵@兩種字節碼設計各有優(yōu)劣,如棧式字節碼每條的指令更短(因為操作樹(shù)是隱式得存在棧中),但是總的指令條數更多;棧式虛擬機實(shí)現起來(lái)比寄存器式來(lái)得簡(jiǎn)單。目前這兩種方式都有各自的實(shí)現:如Flash Player的ActionScript虛擬機Tamarin,Firefox的JagerMonkey,采用的是棧式字節碼設計;而webkit,carakan采用基于寄存器方式。 采用哪種設計取決于設計者關(guān)注的側重點(diǎn)不同。想詳細了解這兩種設計的優(yōu)劣,可參考一些論文(The case for virtual register machines 和Virtual machine showdown: Stack versus registers)?! ∽止澊a是需要在虛擬機中執行的,而虛擬機的執行過(guò)程與CPU過(guò)程類(lèi)似,也是取指,解碼,執行的過(guò)程。通常情況下,每個(gè)操作碼對應一段處理函數,然后通過(guò)一個(gè)無(wú)限循環(huán)加一個(gè)switch的方式進(jìn)行分派。如:

圖5 JavaScript引擎的Switch Loop分派方式
這里的vpc是一個(gè)指向字節碼數組的指針,在虛擬機中作用與PC寄存器在實(shí)際機器中的作用類(lèi)似,所以稱(chēng)作虛擬PC(virtual program counter)?! ∨c遍歷語(yǔ)法樹(shù)方式相比,字節碼方式就消除了遍歷語(yǔ)法樹(shù)所引起的大部分性能負擔。首先字節碼序列直接描述了需要執行的動(dòng)作,去除了多余的語(yǔ)法信息;其次,執行一條字節碼語(yǔ)句,只是一次的內存訪(fǎng)問(wèn)(取指令)再加上一次間接跳轉(分派到對應的處理函數),這也比訪(fǎng)問(wèn)語(yǔ)法樹(shù)中一個(gè)節點(diǎn)開(kāi)銷(xiāo)來(lái)的要小。 因此,字節碼方式與遍歷語(yǔ)法樹(shù)相比在性能上有很大的提升。雖然從語(yǔ)法樹(shù)生成字節碼也是需要時(shí)間的,但是這一小段時(shí)間可以從直接執行字節碼所獲得的性能提升上得到補償。畢竟在實(shí)際的代碼中,不會(huì )所有的代碼都只被執行一次。而且生成了字節碼之后,就可以對于這種中間代碼進(jìn)行各種優(yōu)化,比如常量傳播,常量折疊,公共子表達式刪除等等。當然這些優(yōu)化都是有針對性和選擇性的,畢竟優(yōu)化的過(guò)程也是需要消耗時(shí)間的。而這些優(yōu)化要想直接在語(yǔ)法樹(shù)上進(jìn)行幾乎是不可能的?! ‰m然字節碼方式相對于遍歷語(yǔ)法樹(shù)已經(jīng)前進(jìn)了一大步,但是在分派方式上還可以再改進(jìn)。在圖5中,Switch Loop分派方式每次處理完一條指令后,都要回到循環(huán)的開(kāi)始,處理下一條,并且每次switch操作,可能都是一次線(xiàn)性搜索(現代編譯器一般都能對switch語(yǔ)句進(jìn)行優(yōu)化, 以消除這種線(xiàn)性搜索開(kāi)銷(xiāo),但這種優(yōu)化也是只限于特定條件,如case的數量和值的跨度范圍等),這對于一般的函數,只有有限的幾個(gè)switch case,尚可接受,但是對于虛擬機來(lái)說(shuō),有上百個(gè)switch case并且是頻繁地執行,執行一條指令就需要一次線(xiàn)性搜索,那還是太慢了。如果能用查表的方式直接跳轉,就可以省去線(xiàn)性搜索的過(guò)程了。于是在字節碼的分派方式上,有的新的改進(jìn),稱(chēng)作Direct Threading。Direct Threading Direct Threading,這里的threading與我們通常理解的線(xiàn)程沒(méi)有任何關(guān)系,可以理解成是針線(xiàn)中的那個(gè)“線(xiàn)”。以這種方式執行時(shí),每執行完一條指令后不是回到循環(huán)的開(kāi)始,而是直接跳到下一條要執行的指令地址。這種方式就比原來(lái)的Switch Loop方式有效許多。但是要想有效的實(shí)現Direct Threading,需要用到一個(gè)gcc的擴展“Labels As Values”,普通的goto語(yǔ)句的標號是在編譯時(shí)指定的,但是利用“Labels As Values”擴展,goto語(yǔ)句的標號是就可以在運行時(shí)計算(這種goto語(yǔ)句也叫Computed Goto),利用這個(gè)特性就可以很容易地實(shí)現Direct Threading。(想在windows平臺用這個(gè)特性,也有幾個(gè)GCC的windows移植版本,如MinGW, Cygwin等) 上面的Switch Loop如果用Direct Threading方式,就如下圖右邊所示:

圖6 Direct Threading分派方式
右圖中的Direct Threading方式已經(jīng)沒(méi)有了循環(huán)和switch分支,所有的字節碼分派就是通過(guò)“goto *vpc++”進(jìn)行的。 vpc在這里是指向字節碼數組的指針,字節碼數組里的元素就是各個(gè)標號的地址。例如,如果有個(gè)指令序列是: mov, add, ret 那么對應的字節碼數組就是: [&&mov, &&add, &&ret] 一開(kāi)始,vpc指向數組的第一條指令,即vpc = &&mov, 那么goto *vpc,就會(huì )跳到標號為“mov”的地方開(kāi)始執行(普通的goto語(yǔ)句無(wú)法完成,這是利用gcc的“l(fā)abels as values”特性);在執行“mov”處理函數末尾的“goto *vpc++”之后,就直接跳轉到標號“add”的地方開(kāi)始執行;直到最后。 Direct Threading的執行過(guò)程如下圖所示:
圖7 Direct Threading執行過(guò)程
最左邊是生成的字節碼序列,中間就是字節碼序列對應的數組,右邊是對應的虛擬機實(shí)現代碼。開(kāi)始執行時(shí),vpc指向字節碼數組的開(kāi)始,即“enter”指令,虛擬機開(kāi)始執行“enter”指令對應的操作,在“enter”對應的操作的末尾有個(gè)“goto *vpc++”,這時(shí)的vpc就指向字節碼數組的下一條字節碼,在圖7中即為mov指令,然后進(jìn)入mov指令對應的操作。如此反復直到執行完這個(gè)字節碼數組中的指令。每執行完一條指令,就直接跳轉到下一條指令的地址處,這就跟一根“線(xiàn)”穿過(guò)一條彎曲的隧道,雖然道路是彎曲的,但每次都是前進(jìn)的,而不是想Switch Loop那樣,每次執行完一條字節碼后,又回到起點(diǎn)。 在引入即時(shí)編譯(JIT)之前,Direct Threading方式基本上就是采用字節碼方式的解釋器的最有效和最塊的分派方式了。對于一般的JavaScript運算,這種方式也足夠用了。但是畢竟解釋執行方式肯定比不上直接執行二進(jìn)制代碼。于是接下來(lái)即時(shí)編譯(JIT)技術(shù)被引入了JavaScript引擎。即時(shí)編譯(Just-In-Time) 其實(shí)JIT這種技術(shù)本身很古老,可以追溯到上世紀60年代的LISP語(yǔ)言;并且現代的大部分運行時(shí)環(huán)境(runtime environment),如微軟的.NET框架和大多數的Java實(shí)現都是依賴(lài)JIT技術(shù)來(lái)提高運行性能。在JavaScript引擎中引入JIT技術(shù)則是在2008年才開(kāi)始。 JIT編譯技術(shù)是一種提高程序運行性能的方法。通常一個(gè)程序有兩種方式執行:靜態(tài)編譯和解釋執行。靜態(tài)編譯就是在運行前先將源代碼(如c,c++)針對特定平臺(如x86,arm,mips)編譯成機器代碼,在運行時(shí)就可以直接在相應的平臺上執行;而解釋執行則是每次運行的時(shí)候,將每條源代碼(如python, javascript)翻譯成相應的機器碼并立刻執行,并不保存翻譯后的機器碼,周而復始??梢钥吹浇忉寛绦械倪\行效率很低,因為每次執行都需要逐句地翻譯成機器碼然后執行;而靜態(tài)編譯在運行前就編譯成相應平臺的代碼。但是靜態(tài)編譯使得平臺移植性很差,也無(wú)法實(shí)施運行時(shí)優(yōu)化,而且對于動(dòng)態(tài)語(yǔ)言(弱類(lèi)型語(yǔ)言),變量的類(lèi)型在運行前未知,很難做到靜態(tài)編譯。JIT編譯則是這兩種方式的混合,在運行時(shí)將源代碼翻譯成機器碼(這一點(diǎn)與解釋執行類(lèi)似),但是會(huì )保存已翻譯的機器代碼,下次執行同一代碼段時(shí)無(wú)需再翻譯(這又與靜態(tài)編譯類(lèi)似)?! ‰m然對于一般的運算,Direct Threading方式已經(jīng)很好了,但是對性能的追求永無(wú)之境,沒(méi)有最好,只有更好。Direct Threading方式曾經(jīng)是解釋器(不僅僅JavaScript引擎)非常有效的分派方式。但是有研究和數據顯示在現代體系結構下,這種方式也有很大的局限性(The structure and perfromance of efficient interpreters)。 現在的微處理器大量應用流水線(xiàn)構架來(lái)達到提高性能的目的。要讓流水線(xiàn)總是保持滿(mǎn)負荷運轉,微處理器有一個(gè)專(zhuān)門(mén)的硬件設備“分支預測器”來(lái)預判分支的目標地址。這樣在執行一條指令時(shí),能提前將接下來(lái)可能執行的其他指令放入流水線(xiàn)中,上一條指令執行一結束,接下來(lái)的指令都已經(jīng)完成取值和解碼階段,就可以直接執行。所以如果分支預測正確,將會(huì )大大提高處理器的性能。但是如果分支預測失敗,那么就需要清空整個(gè)流水線(xiàn),重新加載新的指令,而這會(huì )導致很?chē)乐氐男阅軗p耗。 分支預測是通過(guò)利用PC寄存器和分支目標的相關(guān)性來(lái)進(jìn)行預測。而從圖7的Direct Threading執行過(guò)程,可以看到分支跳轉的目標(如goto *vpc++),是與vPC相關(guān),而不是與實(shí)際的硬件PC寄存器相關(guān)。所以分支預測器沒(méi)有足夠的信息來(lái)進(jìn)行有效的預測,這就導致的大量的分支預測失敗。舉個(gè)例子,圖7中,當運行到add處理函數的尾部,在運行'goto *vpc++'之前,“分支預測器”是無(wú)法判斷實(shí)際的跳轉目標是在哪里,而只有等到執行完這句的時(shí)候才能準確知道要跳轉到哪,而這樣就會(huì )導致分支預測的時(shí)候不能把正確的后續指令推入流水線(xiàn)。數據表明30%~40%的執行時(shí)間會(huì )消耗在這種由于分支預測失敗引起的額外處理上。所以要有效地降低分支預測的失敗概率,就要給“分支預測器”提供足夠的上下文信息。Context threading技術(shù)就是以此得名。

圖8 Context Threading執行過(guò)程
可以看到最左邊的圖還是原來(lái)那個(gè)字節碼數組,Context Threading是在這個(gè)表的基礎上又增加了一個(gè)表Context Threading Table(從這個(gè)表中可以看到已經(jīng)開(kāi)始有即時(shí)編譯了)。將字節碼數組中的每條指令編譯成一條條的本地調用(call指令)。上圖與圖7相比,除了多一個(gè)Context Threading Table外,在每個(gè)處理函數的結尾(右圖紅色標示)是一個(gè)與call 指令對應的ret指令?,F代CPU對本地調用的返回地址提供一個(gè)非常有效的預測機制,從而就能避免大量的分支預測失敗。如上圖CPU在執行到“call add”時(shí),在進(jìn)入add的處理函數之前,會(huì )將返回地址(在這里就是下一條指令“call sub”指令的地址)保存在棧中,然后進(jìn)入add的處理函數,當執行到add處理函數末尾ret時(shí),“分支預測器”這時(shí)當然可以預測到要跳到哪了,就是剛才進(jìn)入add處理函數之前保存的返回地址。所以在實(shí)際執行ret之前就可以準確的判斷到將要執行的指令。而之前的Direct Threading方式無(wú)法做到這一點(diǎn)?! ∈欠裼斜匾獙⒆止澊a數組中的每條指令都編譯成一條條的本地調用呢?是的,沒(méi)有必要。在實(shí)際的JavaScript引擎實(shí)現中,對于簡(jiǎn)單的指令,如mov,就直接即時(shí)編譯,inline到機器碼中;而對于復雜的指令,如add指令(因為JavaScript是動(dòng)態(tài)語(yǔ)言,是無(wú)類(lèi)型的,所以在運行add指令時(shí)需要做一系列的類(lèi)型判斷,如操作數都是數值類(lèi)型,那么就進(jìn)行數值加運算;如果操作數都是字符串類(lèi)型,就進(jìn)行字符串加運算,還有其他各種類(lèi)型,所以add指令也算是一個(gè)比較復雜的指令),也會(huì )對它的常用方式(如操作數都是數值,或都是字符串)直接生成對應的機器碼,對于add的其他不常用情況(如一個(gè)操作數是數值,另一個(gè)是字符串,或者發(fā)生溢出了等等情況)則是生成一條call本地調用?! ≡谏衔闹刑岬綇恼Z(yǔ)法樹(shù)生成字節碼會(huì )消耗執行時(shí)間,而將字節碼編譯成本地機器碼(JIT的過(guò)程)也需要消耗執行時(shí)間。在生成機器碼過(guò)程中,實(shí)施越多的優(yōu)化,生成的機器碼質(zhì)量越高,同時(shí)延遲時(shí)間也越長(cháng),所以需要權衡延遲的時(shí)間與生成的代碼質(zhì)量。 所以一般情況下,JavaScript引擎并不是對所有代碼都會(huì )生成機器碼,而是只對熱點(diǎn)(hot spot)片段進(jìn)行即時(shí)編譯,同時(shí)在運行中會(huì )隨時(shí)跟蹤熱點(diǎn)的狀態(tài),如果熱點(diǎn)的程度越高(被執行得越頻繁),實(shí)施的優(yōu)化也越激進(jìn)。 以FireFox3.5 JavaScript引擎為例(FireFox的JavaScript引擎叫SpiderMonkey, 在FireFox3.5中,他的JIT編譯器叫TraceMonkey),在開(kāi)始執行時(shí),將源代碼生成字節碼,然后解釋執行字節碼,在執行過(guò)程中,如果發(fā)現一條路徑多次執行(比如一個(gè)循環(huán)體),那么就標記為“HOT”,同時(shí)將這條路徑上的代碼即時(shí)編譯成機器碼,當下次再運行到這條路徑時(shí),就直接運行機器碼。 在FireFox4.0中,引入了新的JIT編譯器JaegerMonkey替換原來(lái)的TraceMonkey,JaegerMonke實(shí)際上是TraceMonkey的加強版,它除了跟蹤熱點(diǎn)路徑外,同時(shí)又加入的熱點(diǎn)函數的跟蹤。 如下圖:

圖9 FireFox4.0的JavaScript引擎執行過(guò)程
在上圖判斷熱點(diǎn)的虛框中,如果一個(gè)路徑被執行了超過(guò)16次(比如“循環(huán)”迭代了超過(guò)16次),或一個(gè)函數被調用超過(guò)16次,那么就進(jìn)行即時(shí)編譯;否則解釋執行。以這種方式,在JavaScript代碼運算強度越大時(shí)JavaScript引擎性能提高得越明顯,因為對于越頻繁執行的代碼,不僅是已經(jīng)被編譯成機器碼了,同時(shí)執行的編譯時(shí)優(yōu)化和運行時(shí)優(yōu)化也越充分。目前JIT編譯已經(jīng)是主流瀏覽器中JavaScript引擎的標配了?! ‰S著(zhù)JavaScript引擎性能顯著(zhù)提升,現在網(wǎng)站開(kāi)發(fā)人員就可以在服務(wù)器端或者客戶(hù)端上,充分利用JavaScript語(yǔ)言來(lái)完成一些繁重的運算任務(wù)。如果說(shuō)AJAX讓JavaScript煥發(fā)了第二春,那么HTML5的普及,則是進(jìn)一步鞏固了JavaScript在Web前端技術(shù)中的地位。所以JavaScript引擎的性能,一直會(huì )是各個(gè)瀏覽器廠(chǎng)商之間相互較量的重要利器?! ∵@篇文章雖然只是關(guān)于JavaScript引擎發(fā)展的其中一條線(xiàn)索,但是只要抓住一條繩子,應該就能順勢摘到其它想要的桃子。