即時編譯

一种执行计算机代码的方法

電腦技術中,即時編譯(英語:Just-in-time compilation,縮寫為JIT;又譯及時編譯[1]即時編譯[2]),也稱為動態翻譯執行時編譯[3],是一種執行電腦代碼的方法,這種方法設計在程式執行過程中(在執行期)而不是在執行之前進行編譯[4]通常,這包括原始碼或更常見的位元組碼機器碼的轉換,然後直接執行。實現 JIT 編譯器的系統通常會不斷地分析正在執行的代碼,並確定代碼中可被即時編譯加速的部分,在這些部分中,由編譯或重新編譯帶來的效能提高將超過編譯該代碼的開銷。

不同類型的即時編譯實現

JIT編譯是兩種傳統的機器碼翻譯方法——提前編譯英語ahead-of-time compilation(AOT)和直譯器——的結合,它結合了兩者的優點和缺點。[4]大致來說,JIT編譯,以直譯器的開銷以及編譯和連結(解釋之外)的開銷,結合了編譯代碼的速度與解釋的靈活性。JIT編譯是動態編譯的一種形式,允許自適應最佳化英語adaptive optimization,比如動態重編譯和特定於微架構的加速[nb 1][5]——因此,在理論上,JIT編譯比靜態編譯能夠產生更快的執行速度。解釋和JIT編譯特別適合於動態程式語言,因為執行時系統可以處理後期繫結英語Late binding的資料類型並實施安全保證。

應用

編輯

JIT編譯可以應用於某些程式,也可以用於某些能力,特別是動態能力,如正規表示式。例如,一個文字編輯器可以把執行時提供的正規表示式編譯成機器碼,從而更快地進行匹配——這不能提前完成,因為pattern只在執行時提供。一些現代的執行時環境依賴JIT編譯來實現高速代碼執行,包括大多數Java實現,以及微軟.NET框架。類似地,許多正規表示式庫都具有對正規表示式進行JIT編譯的功能,可以編譯成位元組碼,也可以編譯成機器碼。JIT編譯也用於一些模擬器中,以便將機器碼從一個CPU體系結構轉換到另一個CPU體系結構。

JIT編譯的一個常見實現是首先進行AOT編譯,把原始碼編譯成位元組碼(虛擬機器代碼),稱為位元組碼編譯,然後將JIT編譯為機器碼(動態編譯),而不是解釋位元組碼。與解釋相比,這提高了執行時效能,但代價是編譯造成的延遲。與直譯器一樣,JIT編譯器不斷地進行翻譯,但是對編譯後的代碼進行快取可以最大限度地減少在給定執行期間將來執行相同代碼的延遲。

概述

編輯

在位元組碼編譯的系統中,原始碼被轉換為稱為位元組碼的中間表示形式。位元組碼不是任何特定電腦的機器碼,可以在電腦架構之間移植。然後可以在虛擬機器上解釋或執行位元組碼。JIT編譯器在許多部分(或全部、很少)讀取位元組碼,並將它們動態編譯成機器碼,以便程式能夠更快地執行。這可以針對每個檔案、每個函式甚至任何任意代碼片段進行編譯; 代碼可以在即將執行時進行編譯(因此稱為「即時」),然後快取並在以後重用,無需重新編譯。

相比之下,傳統的解釋型虛擬機器只解釋位元組碼,通常效能要低得多。有些直譯器甚至不需要首先編譯成位元組碼就可以解釋原始碼,但效能更差。靜態編譯的代碼本地代碼在部署之前編譯。動態編譯環境是在執行期間可以使用編譯器的環境。 使用JIT技術的一個共同目標是達到或超過靜態編譯的效能,同時保持位元組碼解釋的優勢:解析原始原始碼和執行基本最佳化的許多「繁重工作」通常是在編譯時處理的,在部署之前:從位元組碼編譯到機器碼要比從原始碼編譯快得多。與本地代碼不同,部署的位元組碼是可移植的。由於執行時可以控制編譯,比如解釋位元組碼,所以它可以在安全的沙箱中執行。從位元組碼到機器碼的編譯器更容易編寫,因為可攜式位元組碼編譯器已經完成了大部分工作。

JIT代碼通常比直譯器效能更好。另外,在某些情況下,它的效能可以比靜態編譯更好,因為許多最佳化只在執行時可行:[6][7]

  1. 編譯可以針對目標CPU和應用程式執行的作業系統模型進行最佳化。例如,JIT可以在檢測到CPU支援SSE2向量CPU指令時選擇它們。要使用靜態編譯器獲得這種最佳化級別的特殊性,必須為每個預期的平台/體系結構編譯一個二進制檔案,或者在一個二進制檔案中包含多個版本的部分代碼。
  2. 該系統能夠收集關於程式在其所在環境中實際執行情況的統計資訊,並且能夠重新排列和重新編譯以獲得最佳效能。但一些靜態編譯器也可以將概要資訊作為輸入。
  3. 該系統可以進行全域代碼最佳化(例如行內庫函式),同時不失去動態連結的優點,也不會失去靜態編譯器和連結器原生的開銷。具體來說,在進行全域行內替換時,靜態編譯過程可能需要執行時檢查,並確保如果對象的實際類重寫了行內方法,就會發生虛擬呼叫,並且對陣列訪問的邊界條件檢查可能需要在迴圈中處理。在許多情況下,使用即時編譯,這種處理可以從迴圈中移出,通常會大大提高速度。
  4. 儘管使用靜態編譯的垃圾收集語言可以做到這一點,但位元組碼系統可以更容易地重新排列執行的代碼,以獲得更好的快取利用率。

由於JIT必須在執行時呈現和執行本地二進制映像,因此真正的機器碼JIT需要允許在執行時執行資料的平台,這使得在基於哈佛結構的機器上使用這種JIT成為不可能的事情——對於某些作業系統和虛擬機器也是如此。然而,一種特殊類型的「JIT」可能並不針對物理機器的CPU體系結構,而是一種最佳化的VM位元組碼,在這種情況下,對原始機器碼的限制占了上風,特別是在位元組碼的VM最終將JIT用於本機代碼的情況下。[8]

啟動延遲和最佳化

編輯

由於載入和編譯位元組碼所需的時間,JIT在應用程式的初始執行中會導致輕微到明顯的延遲。有時這種延遲被稱為「啟動時間延遲」或「預熱時間」。一般來說,JIT執行的最佳化越多,生成的代碼就越好,但是初始延遲也會增加。因此,JIT編譯器必須在編譯時間和希望生成的代碼品質之間進行權衡。除了JIT編譯之外,IO繫結操作也會增加啟動時間:例如,JVM的「rt.jar」類資料檔案為40 MB,JVM必須在這個巨大的上下文檔案中尋找大量資料。[9]

Sun的HotSpot Java虛擬機器使用的一種可能的最佳化方法是將解釋和JIT編譯結合起來。應用程式代碼最初是被解釋的,但JVM監視哪些位元組碼序列經常被執行,並將它們轉換為機器碼,以便在硬體上直接執行。對於只執行幾次的位元組碼,這節省了編譯時間並減少了初始延遲;對於頻繁執行的位元組碼,JIT編譯用於在緩慢解釋的初始階段之後以高速執行。此外,由於程式花費大量時間執行的其實只是一小部分代碼,因此減少的編譯時間非常重要。最後,在初始代碼解釋期間,可以在編譯之前收集執行統計資訊,這有助於執行更好的最佳化。[10]

正確的權衡可以根據具體情況而變化。例如,Sun的Java虛擬機器有兩種主要模式: 客戶機和伺服器。在客戶端模式下,執行最小程度的編譯和最佳化,以減少啟動時間。在伺服器模式下,將執行大量的編譯和最佳化,以犧牲啟動時間來最大限度地提高應用程式執行時的效能。其他Java即時編譯器使用一個方法執行次數的執行時度量,結合方法的位元組碼大小作為一種啟發式方法來決定何時編譯。[11]還有的使用執行的次數與檢測迴圈相結合。[12]一般來說,在短期執行的應用程式中準確預測要最佳化的方法要比在長期執行的應用程式中準確得多。[13]

微軟本地鏡像生成器英語Native Image Generator(Ngen)是另一種減少初始延遲的方法。[14]Ngen將通用中間語言映像英語Common Intermediate Language中的位元組碼預編譯成機器本機代碼。因此,不需要執行時編譯。Visual Studio 2005附帶的.NET Framework 2.0在安裝之後立即在所有微軟庫dll上執行Ngen。預JIT提供了一種提高啟動時間的方法。但是,它生成的代碼品質可能不如JIT生成的代碼品質好,原因與靜態編譯的代碼(沒有按組態最佳化英語profile-guided optimization)在極端情況下不如JIT編譯的代碼的原因相同:缺乏分析資料來驅動,例如,行內快取。[15]

還有一些Java實現將AOT編譯器與JIT編譯器(Excelsior JET)或直譯器(GNU Compiler for Java)結合起來。

歷史

編輯

最早發布的JIT編譯器通常歸功於約翰·麥卡錫在1960年對LISP的研究。[16]在他的重要論文《符號表達式的遞迴函式及其在機器上的計算》(Recursive functions of symbolic expressions and their computation by machine, Part I)第一部分中,他提到了在執行時被轉換的函式,因此不需要儲存編譯器輸出來打孔卡[17](雖然更準確的說法是「編譯並執行系統英語Compile and go system」)。另一個早期的應用來自肯·湯普遜,他在文字編輯器QED正規表示式模式匹配中使用了JIT。[18]為了提高速度,Thompson在相容分時系統上通過JIT到IBM 7090代碼實現了正規表示式匹配。[16]1970年,Mitchell首創了一種有影響力的從解釋中取得編譯代碼的技術,他在實驗語言LC²中實現了這種技術。[19][20]

Smalltalk(1983年)開創了JIT編譯的新領域。例如,按需翻譯為機器碼,快取結果以供以後使用。當主記憶體不足時,系統會刪除部分代碼,並在需要時重新生成。[4][21]Sun的Self語言廣泛地改進了這些技術,一度是世界上速度最快的Smalltalk系統;運用完全物件導向的語言實現了高達最佳化C語言一半的速度。[22]

Self被Sun拋棄了,但是研究轉向了Java語言。「即時編譯」這個術語是從製造術語「及時」中借來的,並由Java普及,James Gosling從1993年開始使用這個術語。[23]目前,大多數Java虛擬機器的實現都使用JIT技術,因為HotSpot建立在這個研究基礎之上,而且使用廣泛。

HP的專案Dynamo[24]是一個實驗性的JIT編譯器,其位元組碼格式和機器碼格式是相同的;該系統將PA-6000機器碼轉換為PA-8000機器碼。與直覺相反,這導致了速度的提高,在某些情況下是30%,因為這樣做允許在機器碼級別進行最佳化,例如,行內代碼以更好地使用快取,最佳化對動態庫的呼叫,以及許多其他常規編譯器無法嘗試的執行時最佳化。[25][26]

2019年3月30日,PHP宣布JIT將於2021年加入PHP 8[27][28]

安全

編輯

JIT編譯從根本上使用可執行資料,因此帶來了安全挑戰和可能的漏洞。

JIT編譯的實現包括將原始碼或位元組碼編譯成機器碼並執行它。這通常是直接在主記憶體中完成的——JIT編譯器將機器碼直接輸出到主記憶體中並立即執行,而不是像通常的提前編譯那樣將其輸出到磁碟,然後作為單獨的程式呼叫代碼。在現代的體系結構中,由於可執行空間保護英語executable space protection,這會遇到一個問題——無法在任意主記憶體裡執行程式。在任意主記憶體裡執行程式存在潛在的安全漏洞。因此,必須將主記憶體標記為可執行;出於安全原因,應在代碼寫入主記憶體並標記為唯讀之後執行,因為可寫/可執行主記憶體是一個安全漏洞(參見W^X英語W^X)。[29]例如 Firefox 中的 JavaScript JIT 編譯器在 Firefox 46 版本中引入了這種保護。[30]

JIT噴射英語JIT spraying是一種利用漏洞利用的技術,它使用JIT編譯進行堆噴射英語heap spraying——生成的主記憶體然後是可執行的,如果執行可以移動到堆積中,這就允許利用。

參見

編輯

注釋

編輯
  1. ^ 提前編譯器也可以針對特定的微體系結構,但AOT和JIT之間的區別在於可移植性。JIT可以在執行時生成為當前執行的CPU量身客製化的代碼,而AOT必須事先知道目標CPU,而不是為微架構的廣義子集進行最佳化,這樣的代碼不僅不能在其他CPU類型上執行,而且可能完全不穩定。

參考文獻

編輯

參照

編輯
  1. ^ 丁宇新, 梅嘉, & 程虎. (1999). 國產開放系統平台 Java 及時編譯器的設計與實現 (Doctoral dissertation). http://www.cnki.com.cn/Article/CJFDTotal-JSJX199912010.htm頁面存檔備份,存於網際網路檔案館
  2. ^ 微軟語言門戶術語搜尋頁面存檔備份,存於網際網路檔案館)中英語Just-in-time compilation到簡體中文
  3. ^ Languages, Compilers, and Runtime Systems, University of Michigan, Computer Science and Engineering, [2018-03-15], (原始內容存檔於2018-03-26) 
  4. ^ 4.0 4.1 4.2 Aycock 2003.
  5. ^ Does the JIT take advantage of my CPU?. David Notario's WebLog. [2018-12-03]. (原始內容存檔於2018-12-04). 
  6. ^ Croce, Louis. Just in Time Compilation (PDF). Columbia University. (原始內容 (PDF)存檔於2018-05-03). 
  7. ^ What are the advantages of JIT vs. AOT compilation. Stack Overflow. 2010-01-21. (原始內容存檔於2020-04-24). 
  8. ^ javascript - Compile a JIT based lang to Webassembly. Stack Overflow. [2018-12-04]. (原始內容存檔於2019-12-14). 
  9. ^ Haase, Chet. Consumer JRE: Leaner, Meaner Java Technology. Sun Microsystems. May 2007 [2007-07-27]. (原始內容存檔於2007-08-12). At the OS level, all of these megabytes have to be read from disk, which is a very slow operation. Actually, it's the seek time of the disk that's the killer; reading large files sequentially is relatively fast, but seeking the bits that we actually need is not. So even though we only need a small fraction of the data in these large files for any particular application, the fact that we're seeking all over within the files means that there is plenty of disk activity.  
  10. ^ The Java HotSpot Performance Engine Architecture. Oracle.com. [2013-07-05]. (原始內容存檔於2013-07-21). 
  11. ^ Schilling, Jonathan L. The simplest heuristics may be the best in Java JIT compilers (PDF). SIGPLAN Notices. February 2003, 38 (2): 36–46 [2020-04-18]. doi:10.1145/772970.772975. (原始內容 (PDF)存檔於2015-09-24). 
  12. ^ Toshio Suganuma, Toshiaki Yasue, Motohiro Kawahito, Hideaki Komatsu, Toshio Nakatani, "A dynamic optimization framework for a Java just-in-time compiler", Proceedings of the 16th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications (OOPSLA '01), pp. 180–195, October 14–18, 2001.
  13. ^ Matthew Arnold, Michael Hind, Barbara G. Ryder, "An Empirical Study of Selective Optimization", Proceedings of the 13th International Workshop on Languages and Compilers for Parallel Computing-Revised Papers, pp. 49–67, August 10–12, 2000.
  14. ^ Native Image Generator (Ngen.exe). Msdn2.microsoft.com. [2013-07-05]. (原始內容存檔於2008-04-05). 
  15. ^ Matthew R. Arnold, Stephen Fink, David P. Grove, Michael Hind, and Peter F. Sweeney, "A Survey of Adaptive Optimization in Virtual Machines頁面存檔備份,存於網際網路檔案館)", Proceedings of the IEEE, 92(2), February 2005, pp. 449–466.
  16. ^ 16.0 16.1 Aycock 2003,2. JIT Compilation Techniques, 2.1 Genesis, p. 98.
  17. ^ McCarthy, J. Recursive functions of symbolic expressions and their computation by machine, Part I. Communications of the ACM. April 1960, 3 (4): 184–195. CiteSeerX 10.1.1.111.8833 . doi:10.1145/367177.367199. 
  18. ^ Thompson 1968.
  19. ^ Aycock 2003,2. JIT Compilation Techniques, 2.2 LC², p. 98–99.
  20. ^ Mitchell, J.G. The design and construction of flexible and efficient interactive programming systems. 1970. 
  21. ^ Deutsch, L.P.; Schiffman, A.M. Efficient implementation of the Smalltalk-80 system (PDF). POPL '84: Proceedings of the 11th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages. 1984: 297–302 [2020-04-18]. ISBN 0-89791-125-3. doi:10.1145/800017.800542. (原始內容 (PDF)存檔於2004-06-18). 
  22. ^ 存档副本. [2020-04-18]. (原始內容存檔於2006-11-24). 
  23. ^ Aycock, 2003 & 2.14 Java, p. 107, footnote 13.
  24. ^ "Dynamo: A Transparent Dynamic Optimization System"頁面存檔備份,存於網際網路檔案館) Vasanth Bala, Evelyn Duesterwald, Sanjeev Banerjia - PLDI '00 Proceedings of the ACM SIGPLAN 2000 conference on Programming language design and implementation - pages 1 to 12 - doi:10.1145/349299.349303. Retrieved March 28, 2012
  25. ^ John Jannotti. HP's Dynamo - Page 1 - (3/2000). Ars Technica. [2013-07-05]. (原始內容存檔於2012-02-05). 
  26. ^ The HP Dynamo Project. [2016-04-12]. (原始內容存檔於2002-10-19). 
  27. ^ PHP Gr8. [2020-04-18]. (原始內容存檔於2020-02-23). 
  28. ^ PHP 8 and 7.4 to come with Just-in-time (JIT) to make most CPU-intensive workloads run significantly faster. April 2019 [2020-04-18]. (原始內容存檔於2020-10-07). 
  29. ^ "How to JIT – an introduction頁面存檔備份,存於網際網路檔案館)", Eli Bendersky, November 5th, 2013 at 5:59 am
  30. ^ De Mooij, Jan. W^X JIT-code enabled in Firefox. Jan De Mooij. [2016-05-11]. (原始內容存檔於2016-05-14). 

來源

編輯

外部連結

編輯