自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java代碼的靜態(tài)編譯和動態(tài)編譯中的問題比較

開發(fā) 后端
本文考察了 Java 語言的本地代碼編譯所涉及的一些問題。單獨使用動態(tài)(即時)編譯或靜態(tài)(提前)編譯都不能滿足所有 Java 應(yīng)用程序的需求。作者在各種執(zhí)行環(huán)境中對這兩種編譯技術(shù)進行了比較,對二者如何相互補充進行了展示。

Java 應(yīng)用程序的性能經(jīng)常成為開發(fā)社區(qū)中的討論熱點。因為該語言的設(shè)計初衷是使用解釋的方式支持應(yīng)用程序的可移植性目標,早期 Java 運行時所提供的性能級別遠低于 C 和 C++ 之類的編譯語言。盡管這些語言可以提供更高的性能,但是生成的代碼只能在有限的幾種系統(tǒng)上執(zhí)行。在過去的十年中,Java 運行時供應(yīng)商開發(fā)了一些復(fù)雜的動態(tài)編譯器,通常稱作即時(Just-in-time,JIT)編譯器。程序運行時,JIT 編譯器選擇將最頻繁執(zhí)行的方法編譯成本地代碼。運行時才進行本地代碼編譯而不是在程序運行前進行編譯(用 C 或 C++ 編寫的程序正好屬于后一情形),保證了可移植性的需求。有些 JIT 編譯器甚至不使用解釋程序就能編譯所有的代碼,但是這些編譯器仍然通過在程序執(zhí)行時進行一些操作來保持 Java 應(yīng)用程序的可移植性。

由于動態(tài)編譯技術(shù)的多項改進,在很多應(yīng)用程序中,現(xiàn)代的 JIT 編譯器可以產(chǎn)生與 C 或 C++ 靜態(tài)編譯相當?shù)膽?yīng)用程序性能。但是,仍然有很多軟件開發(fā)人員認為 —— 基于經(jīng)驗或者傳聞 —— 動態(tài)編譯可能嚴重干擾程序操作,因為編譯器必須與應(yīng)用程序共享 CPU。一些開發(fā)人員強烈呼吁對 Java 代碼進行靜態(tài)編譯,并且堅信那樣可以解決性能問題。對于某些應(yīng)用程序和執(zhí)行環(huán)境而言,這種觀點是正確的,靜態(tài)編譯可以極大地提高 Java 性能,或者說它是惟一的實用選擇。但是,靜態(tài)地編譯 Java 應(yīng)用程序在獲得高性能的同時也帶來了很多復(fù)雜性。一般的 Java 開發(fā)人員可能并沒有充分地感受到 JIT 動態(tài)編譯器的優(yōu)點。

本文考察了 Java 語言靜態(tài)編譯和動態(tài)編譯所涉及的一些問題,重點介紹了實時 (RT) 系統(tǒng)。簡要描述了 Java 語言解釋程序的操作原理并說明了現(xiàn)代 JIT 編譯器執(zhí)行本地代碼編譯的優(yōu)缺點。介紹了 IBM 在 WebSphere Real Time 中發(fā)布的 AOT 編譯技術(shù)和它的一些優(yōu)缺點。然后比較了這兩種編譯策略并指出了幾種比較適合使用 AOT 編譯的應(yīng)用程序領(lǐng)域和執(zhí)行環(huán)境。要點在于這兩種編譯技術(shù)并不互斥:即使在使用這兩種技術(shù)最為有效的各種應(yīng)用程序中,它們也分別存在一些影響應(yīng)用程序的優(yōu)缺點。

執(zhí)行 Java 程序

Java 程序最初是通過 Java SDK 的 javac程序編譯成本地的與平臺無關(guān)的格式(類文件)??蓪⒋烁袷娇醋?Java 平臺,因為它定義了執(zhí)行 Java 程序所需的所有信息。Java 程序執(zhí)行引擎,也稱作 Java 運行時環(huán)境(JRE),包含了為特定的本地平臺實現(xiàn) Java 平臺的虛擬機。例如,基于 Linux 的 Intel x86 平臺、Sun Solaris 平臺和 AIX 操作系統(tǒng)上運行的 IBM System p 平臺,每個平臺都擁有一個 JRE。這些 JRE 實現(xiàn)實現(xiàn)了所有的本地支持,從而可以正確執(zhí)行為 Java 平臺編寫的程序。

事實上,操作數(shù)堆棧的大小有實際限制,但是編程人員極少編寫超出該限制的方法。JVM 提供了安全性檢查,對那些創(chuàng)建出此類方法的編程人員進行通知。

Java 平臺程序表示的一個重要部分是字節(jié)碼序列,它描述了 Java 類中每個方法所執(zhí)行的操作。字節(jié)碼使用一個理論上無限大的操作數(shù)堆棧來描述計算。這個基于堆棧的程序表示提供了平臺無關(guān)性,因為它不依賴任何特定本地平臺的 CPU 中可用寄存器的數(shù)目??稍诓僮鲾?shù)堆棧上執(zhí)行的操作的定義都獨立于所有本地處理器的指令集。Java 虛擬機(JVM)規(guī)范定義了這些字節(jié)碼的執(zhí)行(參見 參考資料)。執(zhí)行 Java 程序時,用于任何特定本地平臺的任何 JRE 都必須遵守 JVM 規(guī)范中列出的規(guī)則。

因為基于堆棧的本地平臺很少(Intel X87 浮點數(shù)協(xié)處理器是一個明顯的例外),所以大多數(shù)本地平臺不能直接執(zhí)行 Java 字節(jié)碼。為了解決這個問題,早期的 JRE 通過解釋字節(jié)碼來執(zhí)行 Java 程序。即 JVM 在一個循環(huán)中重復(fù)操作:

◆獲取待執(zhí)行的下一個字節(jié)碼;

◆解碼;

◆從操作數(shù)堆棧獲取所需的操作數(shù);

◆按照 JVM 規(guī)范執(zhí)行操作;

◆將結(jié)果寫回堆棧。

這種方法的優(yōu)點是其簡單性:JRE 開發(fā)人員只需編寫代碼來處理每種字節(jié)碼即可。并且因為用于描述操作的字節(jié)碼少于 255 個,所以實現(xiàn)的成本比較低。當然,缺點是性能:這是一個早期造成很多人對 Java 平臺不滿的問題,盡管擁有很多其他優(yōu)點。

解決與 C 或 C++ 之類的語言之間的性能差距意味著,使用不會犧牲可移植性的方式開發(fā)用于 Java 平臺的本地代碼編譯。

編譯 Java 代碼

盡管傳聞中 Java 編程的 “一次編寫,隨處運行” 的口號可能并非在所有情況下都嚴格成立,但是對于大量的應(yīng)用程序來說情況確實如此。另一方面,本地編譯本質(zhì)上是特定于平臺的。那么 Java 平臺如何在不犧牲平臺無關(guān)性的情況下實現(xiàn)本地編譯的性能?答案就是使用 JIT 編譯器進行動態(tài)編譯,這種方法已經(jīng)使用了十年(參見圖 1):

圖 1. JIT 編譯器

使用 JIT 編譯器時,Java 程序按每次編譯一個方法的形式進行編譯,因為它們在本地處理器指令中執(zhí)行以獲得更高的性能。此過程將生成方法的一個內(nèi)部表示,該表示與字節(jié)碼不同但是其級別要高于目標處理器的本地指令。(IBM JIT 編譯器使用一個表達式樹序列表示方法的操作。)編譯器執(zhí)行一系列優(yōu)化以提高質(zhì)量和效率,最后執(zhí)行一個代碼生成步驟將優(yōu)化后的內(nèi)部表示轉(zhuǎn)換成目標處理器的本地指令。生成的代碼依賴運行時環(huán)境來執(zhí)行一些活動,比如確保類型轉(zhuǎn)換的合法性或者對不能在代碼中直接執(zhí)行的某些類型的對象進行分配。JIT 編譯器操作的編譯線程與應(yīng)用程序線程是分開的,因此應(yīng)用程序不需要等待編譯的執(zhí)行。

圖 1 中還描述了用于觀察執(zhí)行程序行為的分析框架,通過周期性地對線程取樣找出頻繁執(zhí)行的方法。該框架還為專門進行分析的方法提供了工具,用來存儲程序的此次執(zhí)行中可能不會改變的動態(tài)值。

因為這個 JIT 編譯過程在程序執(zhí)行時發(fā)生,所以能夠保持平臺無關(guān)性:發(fā)布的仍然是中立的 Java 平臺代碼。C 和 C++ 之類的語言缺乏這種優(yōu)點,因為它們在程序執(zhí)行前進行本地編譯;發(fā)布給(本地平臺)執(zhí)行環(huán)境的是本地代碼。

#p#

挑戰(zhàn)

盡管通過 JIT 編譯保持了平臺無關(guān)性,但是付出了一定代價。因為在程序執(zhí)行時進行編譯,所以編譯代碼的時間將計入程序的執(zhí)行時間。任何編寫過大型 C 或 C++ 程序的人都知道,編譯過程往往較慢。

為了克服這個缺點,現(xiàn)代的 JIT 編譯器使用了下面兩種方法的任意一種(某些情況下同時使用了這兩種方法)。第一種方法是:編譯所有的代碼,但是不執(zhí)行任何耗時多的分析和轉(zhuǎn)換,因此可以快速生成代碼。由于生成代碼的速度很快,因此盡管可以明顯觀察到編譯帶來的開銷,但是這很容易就被反復(fù)執(zhí)行本地代碼所帶來的性能改善所掩蓋。第二種方法是:將編譯資源只分配給少量的頻繁執(zhí)行的方法(通常稱作熱方法)。低編譯開銷更容易被反復(fù)執(zhí)行熱代碼帶來的性能優(yōu)勢掩蓋。很多應(yīng)用程序只執(zhí)行少量的熱方法,因此這種方法有效地實現(xiàn)了編譯性能成本的最小化。

動態(tài)編譯器的一個主要的復(fù)雜性在于權(quán)衡了解編譯代碼的預(yù)期獲益使方法的執(zhí)行對整個程序的性能起多大作用。一個極端的例子是,程序執(zhí)行后,您非常清楚哪些方法對于這個特定的執(zhí)行的性能貢獻最大,但是編譯這些方法毫無用處,因為程序已經(jīng)完成。而在另一個極端,程序執(zhí)行前無法得知哪些方法重要,但是每種方法的潛在受益都最大化了。大多數(shù)動態(tài)編譯器的操作介于這兩個極端之間,方法是權(quán)衡了解方法預(yù)期獲益的重要程度。

Java 語言需要動態(tài)加載類這一事實對 Java 編譯器的設(shè)計有著重要的影響。如果待編譯代碼引用的其他類還沒有加載怎么辦?比如一個方法需要讀取某個尚未加載的類的靜態(tài)字段值。Java 語言要求第一次執(zhí)行類引用時加載這個類并將其解析到當前的 JVM 中。直到第一次執(zhí)行時才解析引用,這意味著沒有地址可供從中加載該靜態(tài)字段。編譯器如何處理這種可能性?編譯器生成一些代碼,用于在沒有加載類時加載并解析類。類一旦被解析,就會以一種線程安全的方式修改原始代碼位置以便直接訪問靜態(tài)字段的地址,因為此時已獲知該地址。

IBM JIT 編譯器中進行了大量的努力以便使用安全而有效率的代碼補丁技術(shù),因此在解析類之后,執(zhí)行的本地代碼只加載字段的值,就像編譯時已經(jīng)解析了字段一樣。另外一種方法是生成一些代碼,用于在查明字段的位置以前一直檢查是否已經(jīng)解析字段,然后加載該值。對于那些由未解析變成已解析并被頻繁訪問的字段來說,這種簡單的過程可能帶來嚴重的性能問題。

動態(tài)編譯的優(yōu)點

動態(tài)地編譯 Java 程序有一些重要的優(yōu)點,甚至能夠比靜態(tài)編譯語言更好地生成代碼,現(xiàn)代的 JIT 編譯器常常向生成的代碼中插入掛鉤以收集有關(guān)程序行為的信息,以便如果要選擇方法進行重編譯,就可以更好地優(yōu)化動態(tài)行為。

關(guān)于此方法的一個很好的例子是收集一個特定 arraycopy操作的長度。如果發(fā)現(xiàn)每次執(zhí)行操作時該長度基本不變,則可以為最頻繁使用的 arraycopy長度生成專門的代碼,或者可以調(diào)用調(diào)整為該長度的代碼序列。由于內(nèi)存系統(tǒng)和指令集設(shè)計的特性,用于復(fù)制內(nèi)存的最佳通用例程的執(zhí)行速度通常比用于復(fù)制特定長度的代碼慢。例如,復(fù)制 8 個字節(jié)的對齊的數(shù)據(jù)可能需要一到兩條指令直接復(fù)制,相比之下,使用可以處理任意字節(jié)數(shù)和任意對齊方式的一般復(fù)制循環(huán)可能需要 10 條指令來復(fù)制同樣的 8 個字節(jié)。但是,即使此類專門的代碼是為某個特定的長度生成的,生成的代碼也必須正確地執(zhí)行其他長度的復(fù)制。生成代碼只是為了使常見長度的操作執(zhí)行得更快,因此平均下來,性能得到了改進。此類優(yōu)化對大多數(shù)靜態(tài)編譯語言通常不實用,因為所有可能的執(zhí)行中長度恒定的操作比一個特定程序執(zhí)行中長度恒定的操作要少得多。

此類優(yōu)化的另一個重要的例子是基于類層次結(jié)構(gòu)的優(yōu)化。例如,一個虛方法調(diào)用需要查看接收方對象的類調(diào)用,以便找出哪個實際目標實現(xiàn)了接收方對象的虛方法。研究表明:大多數(shù)虛調(diào)用只有一個目標對應(yīng)于所有的接收方對象,而 JIT 編譯器可以為直接調(diào)用生成比虛調(diào)用更有效率的代碼。通過分析代碼編譯后類層次結(jié)構(gòu)的狀態(tài),JIT 編譯器可以為虛調(diào)用找到一個目標方法,并且生成直接調(diào)用目標方法的代碼而不是執(zhí)行較慢的虛調(diào)用。當然,如果類層次結(jié)構(gòu)發(fā)生變化,并且出現(xiàn)另外的目標方法,則 JIT 編譯器可以更正最初生成的代碼以便執(zhí)行虛調(diào)用。在實踐中,很少需要作出這些更正。另外,由于可能需要作出此類更正,因此靜態(tài)地執(zhí)行這種優(yōu)化非常麻煩。

因為動態(tài)編譯器通常只是集中編譯少量的熱方法,所以可以執(zhí)行更主動的分析來生成更好的代碼,使編譯的回報更高。事實上,大部分現(xiàn)代的 JIT 編譯器也支持重編譯被認為是熱方法的方法??梢允褂渺o態(tài)編譯器(不太強調(diào)編譯時間)中常見的非常主動的優(yōu)化來分析和轉(zhuǎn)換這些頻繁執(zhí)行的方法,以便生成更好的代碼并獲得更高的性能。

這些改進及其他一些類似的改進所產(chǎn)生的綜合效果是:對于大量的 Java 應(yīng)用程序來說,動態(tài)編譯已經(jīng)彌補了與 C 和 C++ 之類語言的靜態(tài)本地編譯性能之間的差距,在某些情況下,甚至超過了后者的性能。

缺點

但是,動態(tài)編譯確實具有一些缺點,這些缺點使它在某些情況下算不上一個理想的解決方案。例如,因為識別頻繁執(zhí)行的方法以及編譯這些方法需要時間,所以應(yīng)用程序通常要經(jīng)歷一個準備過程,在這個過程中性能無法達到其最高值。在這個準備過程中出現(xiàn)性能問題有幾個原因。首先,大量的初始編譯可能直接影響應(yīng)用程序的啟動時間。不僅這些編譯延遲了應(yīng)用程序達到穩(wěn)定狀態(tài)的時間(想像 Web 服務(wù)器經(jīng)歷一個初始階段后才能夠執(zhí)行實際有用的工作),而且在準備階段中頻繁執(zhí)行的方法可能對應(yīng)用程序的穩(wěn)定狀態(tài)的性能所起的作用也不大。如果 JIT 編譯會延遲啟動又不能顯著改善應(yīng)用程序的長期性能,則執(zhí)行這種編譯就非常浪費。雖然所有的現(xiàn)代 JVM 都執(zhí)行調(diào)優(yōu)來減輕啟動延遲,但是并非在所有情況下都能夠完全解決這個問題。

其次,有些應(yīng)用程序完全不能忍受動態(tài)編譯帶來的延遲。如 GUI 接口之類交互式應(yīng)用程序就是這樣的例子。在這種情況下,編譯活動可能對用戶使用造成不利影響,同時又不能顯著地改善應(yīng)用程序的性能。

最后,用于實時環(huán)境并具有嚴格的任務(wù)時限的應(yīng)用程序可能無法忍受編譯的不確定性性能影響或動態(tài)編譯器本身的內(nèi)存開銷。

因此,雖然 JIT 編譯技術(shù)已經(jīng)能夠提供與靜態(tài)語言性能相當(甚至更好)的性能水平,但是動態(tài)編譯并不適合于某些應(yīng)用程序。在這些情況下,Java 代碼的提前(Ahead-of-time,AOT)編譯可能是合適的解決方案。

AOT Java 編譯

大致說來,Java 語言本地編譯應(yīng)該是為傳統(tǒng)語言(如 C++ 或 Fortran)而開發(fā)的編譯技術(shù)的一個簡單應(yīng)用。不幸的是,Java 語言本身的動態(tài)特性帶來了額外的復(fù)雜性,影響了 Java 程序靜態(tài)編譯代碼的質(zhì)量。但是基本思想仍然是相同的:在程序執(zhí)行前生成 Java 方法的本地代碼,以便在程序運行時直接使用本地代碼。目的在于避免 JIT 編譯器的運行時性能消耗或內(nèi)存消耗,或者避免解釋程序的早期性能開銷。

挑戰(zhàn)

動態(tài)類加載是動態(tài) JIT 編譯器面臨的一個挑戰(zhàn),也是 AOT 編譯的一個更重要的問題。只有在執(zhí)行代碼引用類的時候才加載該類。因為是在程序執(zhí)行前進行 AOT 編譯的,所以編譯器無法預(yù)測加載了哪些類。就是說編譯器無法獲知任何靜態(tài)字段的地址、任何對象的任何實例字段的偏移量或任何調(diào)用的實際目標,甚至對直接調(diào)用(非虛調(diào)用)也是如此。在執(zhí)行代碼時,如果證明對任何這類信息的預(yù)測是錯誤的,這意味著代碼是錯誤的并且還犧牲了 Java 的一致性。

因為代碼可以在任何環(huán)境中執(zhí)行,所以類文件可能與代碼編譯時不同。例如,一個 JVM 實例可能從磁盤的某個特定位置加載類,而后面一個實例可能從不同的位置甚至網(wǎng)絡(luò)加載該類。設(shè)想一個正在進行 bug 修復(fù)的開發(fā)環(huán)境:類文件的內(nèi)容可能隨不同的應(yīng)用程序的執(zhí)行而變化。此外,Java 代碼可能在程序執(zhí)行前根本不存在:比如 Java 反射服務(wù)通常在運行時生成新類來支持程序的行為。

缺少關(guān)于靜態(tài)、字段、類和方法的信息意味著嚴重限制了 Java 編譯器中優(yōu)化框架的大部分功能。內(nèi)聯(lián)可能是靜態(tài)或動態(tài)編譯器應(yīng)用的最重要的優(yōu)化,但是由于編譯器無法獲知調(diào)用的目標方法,因此無法再使用這種優(yōu)化。

內(nèi)聯(lián)

內(nèi)聯(lián)是一種用于在運行時生成代碼避免程序開始和結(jié)束時開銷的技術(shù),方法是將函數(shù)的調(diào)用代碼插入到調(diào)用方的函數(shù)中。但是內(nèi)聯(lián)最大的益處可能是優(yōu)化方可見的代碼的范圍擴大了,從而能夠生成更高質(zhì)量的代碼。下面是一個內(nèi)聯(lián)前的代碼示例:

int foo() { int x=2, y=3; return bar(x,y); }final int bar(int a, int b) { return a+b; }

如果編譯器可以證明這個 bar就是 foo()中調(diào)用的那個方法,則 bar中的代碼可以取代 foo()中對 bar()的調(diào)用。這時,bar()方法是 final類型,因此肯定是 foo()中調(diào)用的那個方法。甚至在一些虛調(diào)用例子中,動態(tài) JIT 編譯器通常能夠推測性地內(nèi)聯(lián)目標方法的代碼,并且在絕大多數(shù)情況下能夠正確使用。編譯器將生成以下代碼:

int foo() { int x=2, y=3; return x+y; }

在這個例子中,簡化前名為值傳播的優(yōu)化可以生成直接返回 5的代碼。如果不使用內(nèi)聯(lián),則不能執(zhí)行這種優(yōu)化,產(chǎn)生的性能就會低很多。如果沒有解析 bar()方法(例如靜態(tài)編譯),則不能執(zhí)行這種優(yōu)化,而代碼必須執(zhí)行虛調(diào)用。運行時,實際調(diào)用的可能是另外一個執(zhí)行兩個數(shù)字相乘而不是相加的 bar方法。所以不能在 Java 程序的靜態(tài)編譯期間直接使用內(nèi)聯(lián)。

AOT 代碼因此必須在沒有解析每個靜態(tài)、字段、類和方法引用的情況下生成。執(zhí)行時,每個這些引用必須利用當前運行時環(huán)境的正確值進行更新。這個過程可能直接影響第一次執(zhí)行的性能,因為在第一次執(zhí)行時將解析所有引用。當然,后續(xù)執(zhí)行將從修補代碼中獲益,從而可以更直接地引用實例、靜態(tài)字段或方法目標。

另外,為 Java 方法生成的本地代碼通常需要使用僅在單個 JVM 實例中使用的值。例如,代碼必須調(diào)用 JVM 運行時中的某些運行時例程來執(zhí)行特定操作,如查找未解析的方法或分配內(nèi)存。這些運行時例程的地址可能在每次將 JVM 加載到內(nèi)存時變化。因此 AOT 編譯代碼需要綁定到 JVM 的當前執(zhí)行環(huán)境中,然后才能執(zhí)行。其他的例子有字符串的地址和常量池入口的內(nèi)部位置。

在 WebSphere Real Time 中,AOT 本地代碼編譯通過 jxeinajar工具(參見圖 2)來執(zhí)行。該工具對 JAR 文件中所有類的所有方法應(yīng)用本地代碼編譯,也可以選擇性地對需要的方法應(yīng)用本地代碼編譯。結(jié)果被存儲到名為 Java eXEcutable (JXE) 的內(nèi)部格式中,但是也可輕松地存儲到任意的持久性容器中。

您可能認為對所有的代碼進行靜態(tài)編譯是最好的方法,因為可以在運行時執(zhí)行最大數(shù)量的本地代碼。但是此處可以作出一些權(quán)衡。編譯的方法越多,代碼占用的內(nèi)存就越多。編譯后的本地代碼大概比字節(jié)碼大 10 倍:本地代碼本身的密度比字節(jié)碼小,而且必須包含代碼的附加元數(shù)據(jù),以便將代碼綁定到 JVM 中,并且在出現(xiàn)異?;蛘埱蠖褩8檿r正確執(zhí)行代碼。構(gòu)成普通 Java 應(yīng)用程序的 JAR 文件通常包含許多很少執(zhí)行的方法。編譯這些方法會消耗內(nèi)存卻沒有什么預(yù)期收益。相關(guān)的內(nèi)存消耗包括以下過程:將代碼存儲到磁盤上、從磁盤取出代碼并裝入 JVM,以及將代碼綁定到 JVM。除非多次執(zhí)行代碼,否則這些代價不能由本地代碼相對解釋的性能優(yōu)勢來彌補。

圖 2. jxeinajar

跟大小問題相違背的一個事實是:在編譯過的方法和解釋過的方法之間進行的調(diào)用(即編譯過的方法調(diào)用解釋過的方法,或者相反)可能比這兩類方法各自內(nèi)部之間進行的調(diào)用所需的開銷大。動態(tài)編譯器通過最終編譯所有由 JIT 編譯代碼頻繁調(diào)用的那些解釋過的方法來減少這項開銷,但是如果不使用動態(tài)編譯器,則這項開銷就不可避免。因此如果是選擇性地編譯方法,則必須謹慎操作以使從已編譯方法到未編譯方法的轉(zhuǎn)換最小化。為了在所有可能的執(zhí)行中都避免這個問題而選擇正確的方法會非常困難。

#p#

優(yōu)點

雖然 AOT 編譯代碼具有上述的缺點和挑戰(zhàn),但是提前編譯 Java 程序可以提高性能,尤其是在不能將動態(tài)編譯器作為有效解決方案的環(huán)境中。

可以通過謹慎地使用 AOT 編譯代碼加快應(yīng)用程序啟動,因為雖然這種代碼通常比 JIT 編譯代碼慢,但是卻比解釋代碼快很多倍。此外,因為加載和綁定 AOT 編譯代碼的時間通常比檢測和動態(tài)編譯一個重要方法的時間少,所以能夠在程序執(zhí)行的早期達到那樣的性能。類似地,交互式應(yīng)用程序可以很快地從本地代碼中獲益,無需使用引起較差響應(yīng)能力的動態(tài)編譯。

RT 應(yīng)用程序也能從 AOT 編譯代碼中獲得重要的收益:更具確定性的性能超過了解釋的性能。WebSphere Real Time 使用的動態(tài) JIT 編譯器針對在 RT 系統(tǒng)中的使用進行了專門的調(diào)整。使編譯線程以低于 RT 任務(wù)的優(yōu)先級操作,并且作出了調(diào)整以避免生成帶有嚴重的不確定性性能影響的代碼。但是,在一些 RT 環(huán)境中,出現(xiàn) JIT 編譯器是不可接受的。此類環(huán)境通常需要最嚴格的時限管理控制。在這些例子中,AOT 編譯代碼可以提供比解釋過的代碼更好的原始性能,又不會影響現(xiàn)有的確定性。消除 JIT 編譯線程甚至消除了啟動更高優(yōu)先級 RT 任務(wù)時發(fā)生的線程搶占所帶來的性能影響。

優(yōu)缺點統(tǒng)計

動態(tài)(JIT)編譯器支持平臺中立性,并通過利用應(yīng)用程序執(zhí)行的動態(tài)行為和關(guān)于加載的類及其層次結(jié)構(gòu)的信息來生成高質(zhì)量的代碼。但是 JIT 編譯器具有一個有限的編譯時預(yù)算,而且會影響程序的運行時性能。另一方面,靜態(tài)(AOT)編譯器則犧牲了平臺無關(guān)性和代碼質(zhì)量,因為它們不能利用程序的動態(tài)行為,也不具有關(guān)于加載的類或類層次結(jié)構(gòu)的信息。AOT 編譯擁有有效無限制的編譯時預(yù)算,因為 AOT 編譯時間不會影響運行時性能,但是在實踐中開發(fā)人員不會長期等待靜態(tài)編譯步驟的完成。

表 1 總結(jié)了本文討論的 Java 語言動態(tài)和靜態(tài)編譯器的一些特性:

表 1. 比較編譯技術(shù)

兩種技術(shù)都需要謹慎選擇編譯的方法以實現(xiàn)最高的性能。對動態(tài)編譯器而言,編譯器自身作出決策,而對于靜態(tài)編譯器,由開發(fā)人員作出選擇。讓 JIT 編譯器選擇編譯的方法是不是優(yōu)點很難說,取決于編譯器在給定情形中推斷能力的好壞。在大多數(shù)情況下,我們認為這是一種優(yōu)點。

因為它們可以最好地優(yōu)化運行中的程序,所以 JIT 編譯器在提供穩(wěn)定狀態(tài)性能方面更勝一籌,而這一點在大量的生產(chǎn) Java 系統(tǒng)中最為重要。靜態(tài)編譯可以產(chǎn)生最佳的交互式性能,因為沒有運行時編譯行為來影響用戶預(yù)期的響應(yīng)時間。通過調(diào)整動態(tài)編譯器可以在某種程度上解決啟動和確定性性能問題,但是靜態(tài)編譯在需要時可提供最快的啟動速度和最高級別的確定性。表 2 在四種不同的執(zhí)行環(huán)境中對這兩種編譯技術(shù)進行了比較:

表 2. 使用這些技術(shù)的最佳環(huán)境

圖 3 展示了啟動性能和穩(wěn)定狀態(tài)性能的總體趨勢:

圖 3. AOT 和 JIT 的性能對比

使用 JIT 編譯器的初始階段性能很低,因為要首先解釋方法。隨著編譯方法的增多及 JIT 執(zhí)行編譯所需時間的縮短,性能曲線逐漸升高最后達到性能峰值。另一方面,AOT 編譯代碼啟動時的性能比解釋的性能高很多,但是無法達到 JIT 編譯器所能達到的最高性能。將靜態(tài)代碼綁定到 JVM 實例中會產(chǎn)生一些開銷,因此開始時的性能比穩(wěn)定狀態(tài)的性能值低,但是能夠比使用 JIT 編譯器更快地達到穩(wěn)定狀態(tài)的性能水平。

沒有一種本地代碼編譯技術(shù)能夠適合所有的 Java 執(zhí)行環(huán)境。某種技術(shù)所擅長的通常正是其他技術(shù)的弱項。出于這個原因,需要同時使用這兩種編譯技術(shù)以滿足 Java 應(yīng)用程序開發(fā)人員的要求。事實上,可以結(jié)合使用靜態(tài)和動態(tài)編譯以便提供最大可能的性能提升 —— 但是必須具備平臺無關(guān)性,它是 Java 語言的主要賣點,因此不成問題。

結(jié)束語

本文探討了 Java 語言本地代碼編譯的問題,主要介紹了 JIT 編譯器形式的動態(tài)編譯和靜態(tài) AOT 編譯,比較了二者的優(yōu)缺點。

雖然動態(tài)編譯器在過去的十年里實現(xiàn)了極大的成熟,使大量的各種 Java 應(yīng)用程序可以趕上或超過靜態(tài)編譯語言(如 C++ 或 Fortran)所能夠達到的性能。但是動態(tài)編譯在某些類型的應(yīng)用程序和執(zhí)行環(huán)境中仍然不太合適。雖然 AOT 編譯號稱動態(tài)編譯缺點的萬能解決方案,但是由于 Java 語言本身的動態(tài)特性,它也面臨著提供本地編譯全部潛能的挑戰(zhàn)。

這兩種技術(shù)都不能解決 Java 執(zhí)行環(huán)境中本地代碼編譯的所有需求,但是反過來又可以在最有效的地方作為工具使用。這兩種技術(shù)可以相互補充。能夠恰當?shù)厥褂眠@兩種編譯模型的運行時系統(tǒng)可以使很大范圍內(nèi)的應(yīng)用程序開發(fā)環(huán)境中的開發(fā)人員和用戶受益。

【編輯推薦】

  1. 透視手機終端Java技術(shù)發(fā)展
  2. 成為Java高手的25個學(xué)習(xí)要點
  3. Java EE開發(fā)三劍客現(xiàn)狀及發(fā)展淺析
責(zé)任編輯:楊鵬飛 來源: javaeye
相關(guān)推薦

2017-02-20 13:54:14

Java代碼編譯

2009-08-04 18:05:37

動態(tài)編譯ASP.NET

2011-06-29 17:00:26

QT 靜態(tài)編譯 Debug

2021-07-06 06:39:22

Java靜態(tài)代理動態(tài)代理

2011-06-21 16:51:21

Qt 靜態(tài) 編譯

2021-03-07 16:31:35

Java編譯反編譯

2009-08-18 10:54:17

C#事件和委托

2020-10-26 11:33:45

編程語言編譯器軟件

2009-07-10 17:16:39

MyEclipse不編

2010-03-23 11:17:16

Python 動態(tài)編譯

2009-12-23 15:08:38

Fedora gcc編

2010-01-11 15:47:37

C++編譯

2022-03-24 23:04:37

linux靜態(tài)庫動態(tài)庫

2022-06-09 09:54:45

編譯軟件開發(fā)

2022-01-18 08:40:31

Javascript 高階函數(shù)前端

2010-01-06 17:12:39

華為交換機vlan配置

2022-01-19 08:00:00

靜態(tài)代碼動態(tài)代碼開發(fā)

2010-01-18 10:34:21

C++編譯器

2024-06-06 12:24:18

預(yù)編譯代碼編譯

2011-06-21 16:40:58

Qt 靜態(tài)編譯
點贊
收藏

51CTO技術(shù)棧公眾號