競速(二): JavaScript編譯器如何工作

伯樂線上讀者發表於2013-06-10

伯樂線上注:英文原文:John Dalziel,感謝夫妻檔@Meg-天然呆 和 @GG死米大 的熱心翻譯。如果其他朋友也有不錯的原創或譯文,可以嘗試推薦給伯樂線上。以下是譯文。

————————————————————–

當我們談論JavaScript引擎的時候,通常是指它的編譯器,一個把人類可讀的原始碼(本文中指JavaScript程式碼)翻譯成機器可讀的指令的程式。如果你還沒考慮過你的程式碼在執行時會發生什麼,那麼這聽起來可能相當神奇,但編譯本質上只是一個翻譯練習,讓程式碼執行的快才是神奇的。

 

簡單編譯器是怎麼工作的

JavaScript被認為是高階語言,這意味著它是人類可讀的並且具有高度的靈活性。編譯器的工作是把高階語言轉換成計算機本地指令。

一個簡單的編譯程式有四個處理過程:詞法分析器、解析器、翻譯器、直譯器。

  • 1. 詞法分析器(或者說是掃描器,分詞器),掃描原始碼並把它轉換為原子單位,稱為記號。最常見的實現是使用正規表示式進行模式匹配。
  • 2.  被標記化之後的程式碼被傳入解析器,解析器對程式碼結構和作用範圍進行識別和編碼,生成語法樹。
  • 3. 這種類似圖的結構之後被傳入翻譯器翻譯成位元組碼。其中最簡單的實現是把一個龐大的switch語句標記對映成等價的位元組碼。
  • 4. 然後位元組碼被傳入位元組碼直譯器,被轉換為本機程式碼。

這是經典的編譯器設計,已經存在了很多年。但是桌面程式和瀏覽器的要求有很大不同。這種經典的結構在多個方面都有缺陷。解決這些問題的創新方式,是瀏覽器的速度競賽故事。

 

快速、輕量、正確

JavaScript語言是非常靈活和具有相容性的程式結構。那麼你怎麼寫這種後期繫結、弱型別、動態語言的編譯器呢?在你使它變快之前,必須先使它變精確,或者像Brendan Eich說的,

“快速、輕量、正確。任意選擇兩個,只要(結果)是正確的”

一種創新的測試編譯器正確性的方式是“模糊測試”。Mozilla的Jesse Ruderman建立的jsfunfuzz正是這個目的。Brendan稱它為“JavaScript 嘲弄產生器”,因為它的目的是創造怪異但是語法有效的結構,然後看編譯器能否處理。這種工具在驗證編譯錯誤和邊界問題上非常有幫助。

 

JIT 編譯器

經典結構的原則性問題是執行時的位元組碼翻譯非常慢。在編譯過程中,將位元組碼翻譯成機器程式碼時增加一個步驟可以帶來效能提升。不幸的是停留幾分鐘在網頁上等待它完全編譯是不會讓你的瀏覽器流行的。

解決方案是由JIT提出的“懶編譯”,或者叫實時編譯。顧名思義,它只將你用到的這部分程式碼實時編譯成機器程式碼。JIT編譯器有多種多樣,各自有各自的優化策略。比如正規表示式編譯器致力於優化單個任務,而其它的編譯器可能優化像迴圈或函式這些常見操作。現代化的JavaScript引擎會用到多種編譯器,分工合作,從而你程式碼的效能得到提升。

 

JavaScript JIT 編譯器

第一個JavaScript JIT編譯器是Mozilla的TraceMonkey。這是一個“跟蹤JIT”,因為它的跟蹤路徑是從你的程式碼中尋找常見的可執行程式碼段。然後這些“常見程式碼段”被編譯成機器程式碼。和以前的引擎相比,Mozilla的這種優化可以帶來20%-40%的效能提升。

在TraceMonkey推出後不久,谷歌就釋出了擁有全新V8引擎的Chrome瀏覽器。V8引擎是為速度而生。一個關鍵的設計是它完全跳過了位元組碼生成,取而代之的是由翻譯器產生本地機器程式碼。V8團隊在一年之內已經實現了暫存器分配、改善快取記憶體、重寫正則引擎,使其比原來快了10倍。他們JavaScript整體執行速度被提高了150%。速度競賽才剛剛開始。

最近瀏覽器廠商都紛紛推出了含有一個附加步驟的優化編譯器。在定向流圖(DFG)或語法樹生成之後,編譯器可以使用這方面知識,在機器程式碼產生之前進一步優化效能。Mozilla的IonMonkey和Google的Crankshaft就是DFG編譯器的例子。

所有這些別具匠心的設計,其巨集偉的目標就是使Javascript程式碼執行的和本地C程式碼一樣快。這個目標在幾年前聽起來好像是在搞笑,現在已經越來越近。在第三部分,我們將看到編譯器的設計者使用多種策略,開發速度更快的Javascript編譯器。

 

英文原文:John Dalziel,編譯:@Meg-天然呆 和 @GG死米大

譯文連結:http://blog.jobbole.com/41184/

【非特殊說明,轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊,謝謝合作!】

相關文章