拜讀了David的《Know Your Engines: How to Make Your JavaScript Fast》,David是Mozilla的JS引擎工程師,文章主要介紹了JIT與GC原理,以及如何根據(jù)某些基本原理,優(yōu)化js代碼的執(zhí)行效率,雖然是老文了,但對我來說仍受益匪淺。這里,我根據(jù)上文整理了本文,同時,大家也可以從側(cè)面了解下JIT。
近5年來,在主流瀏覽器上,Javascript的運行速度有10-100倍的提升,這要歸功于Javascript新引擎JIT。但在深入了解JIT前,我們先看看Javascript的一個最重要的特性:untyped(無類型)。
一. 無類型:
Javascript是個無類型的語言,這導致了 x = y +z這種表達式,可以有很多含義。比如:
(1)y,z是數(shù)字,則+表示加法。
(2)y,z是字符串,則+表示字符串連接。
……
而JS引擎內(nèi)部則使用“細粒度”的類型,比如:32-bit* integer, 64-bit* floating-point,如圖:
這就要求js類型-js引擎類型,需要做“boxed/unboxed(裝箱/解箱)”,在處理一次x = y + z這種計算,需要經(jīng)過的步驟如下:
(1)從內(nèi)存,讀取 x = y + z的操作符。
(2)從內(nèi)存,讀取 y,z。
(3)檢查y,z類型,確定操作的行為。
(4)unbox y,z。
(5)執(zhí)行 操作符 的行為(唯一有效的步驟……)。
(6)box x。
(7)把x寫入內(nèi)存。
只有(5)是真正有效的操作,其他都是為(5)做準備/收尾的,效率之低可見。javascript的untyped特性很好用,但也為此付出了很大的性能代價。
二. 對象屬性
function f(obj) { return obj.a + 1; }
在Js里,對象屬性的訪問是比較慢的。至于原因,要從Javascript對象存儲說起,這里借用其他文章的一個圖:
如上圖,訪問對象屬性,需要先從本地變量表找到對象,然后遍歷屬性,如果在本對象的屬性列表里沒找到,再得從prototype里面一層層的找。不能直接索引,只能遍歷,這就慢的原因。
二. 2006版-Javascript引擎
這版引擎在執(zhí)行x = y + z時,就是執(zhí)行了以上流程。它模塊圖如下:
三. 2011新版-Javascript引擎
模塊圖如下:
可以看到,除了老版的解析器外,新引擎增加了JIT,以及Type-specializing JIT。
1. JIT
先看看JIT對untyped的優(yōu)化,在JIT下,執(zhí)行x = y + z流程:
(1)從內(nèi)存,讀取 x = y + z的操作符。
(2)從內(nèi)存,讀取 y,z。
(3)檢查y,z類型,確定操作的行為。
(4)unbox y,z。
(5)執(zhí)行 操作符 的行為(唯一有效的步驟……)。
(6)box x。
(7)把x寫入內(nèi)存。
其中,(1),(2) CPU幫我們搞定;(7)JIT把結(jié)果保存在寄存器里。
但可惜不是所有情況都能使用JIT,上面看到,F(xiàn)ront-end有3條分支,“一般的情況”可以走JIT分支,比如:number + number;string + string …,但特殊情況,比如:number + undefined就不行了,只能走舊解析器。
除了針對untyped的優(yōu)化,新引擎還對“對象屬性”訪問做了優(yōu)化,解決方案叫:inline caching,俗稱:IC。簡單的說,就是做cache。優(yōu)化流程直接看圖:
這個相當于遍歷cache list了,如果當list很大時,這種方案反而影響效率。下圖是評測:
2. Type-specializing JIT
從名稱上可以猜到,這個引擎是處理typed類型(聲明類型)變量的。厄……但Javascript都是untype類型的……
Type-specializing JIT的解決方案是:
(1)先通過掃描,監(jiān)測類型。
(2)通過編譯優(yōu)化(當然,他的優(yōu)化對象不僅僅只是“類型”,還包括對JS代碼的優(yōu)化,但類型優(yōu)化是核心的。),生成類型變量。
(3)再做后續(xù)計算。
來看看Type-specializing JIT的執(zhí)行x = y + z流程吧:
(1)從內(nèi)存,讀取 x = y + z的操作符。
(2)從內(nèi)存,讀取 y,z。
(3)檢查y,z類型,確定操作的行為。
(4)unbox y,z。
(5)執(zhí)行 操作符 的行為。
(6)box x。
(7)把x寫入內(nèi)存。
高效的優(yōu)化啊……當然,這也是有代價的,代價就是:前置的掃描類型,編譯優(yōu)化。所以Type-specializing JIT的應(yīng)用是有選擇性,選擇使用這個引擎的場景包括:
(1)熱點代碼。
(2)通過啟發(fā)式算法估算出來的有價值的代碼……
另外,有2點也需要注意:
(1)當 變量類型 發(fā)生變化時,引擎有2種處理方式:
【1】少量變更,重編譯,再執(zhí)行。
【2】大量變更……還是交給JIT執(zhí)行吧。
(2)數(shù)組,object properties,閉包變量不在優(yōu)化范疇之列。