成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java代碼的靜態(tài)編譯和動(dòng)態(tài)編譯中的問(wèn)題比較

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

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

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

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

執(zhí)行 Java 程序

Java 程序最初是通過(guò) Java SDK 的 javac程序編譯成本地的與平臺(tái)無(wú)關(guān)的格式(類文件)。可將此格式看作 Java 平臺(tái),因?yàn)樗x了執(zhí)行 Java 程序所需的所有信息。Java 程序執(zhí)行引擎,也稱作 Java 運(yùn)行時(shí)環(huán)境(JRE),包含了為特定的本地平臺(tái)實(shí)現(xiàn) Java 平臺(tái)的虛擬機(jī)。例如,基于 Linux 的 Intel x86 平臺(tái)、Sun Solaris 平臺(tái)和 AIX 操作系統(tǒng)上運(yùn)行的 IBM System p 平臺(tái),每個(gè)平臺(tái)都擁有一個(gè) JRE。這些 JRE 實(shí)現(xiàn)實(shí)現(xiàn)了所有的本地支持,從而可以正確執(zhí)行為 Java 平臺(tái)編寫(xiě)的程序。

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

Java 平臺(tái)程序表示的一個(gè)重要部分是字節(jié)碼序列,它描述了 Java 類中每個(gè)方法所執(zhí)行的操作。字節(jié)碼使用一個(gè)理論上無(wú)限大的操作數(shù)堆棧來(lái)描述計(jì)算。這個(gè)基于堆棧的程序表示提供了平臺(tái)無(wú)關(guān)性,因?yàn)樗灰蕾嚾魏翁囟ū镜仄脚_(tái)的 CPU 中可用寄存器的數(shù)目。可在操作數(shù)堆棧上執(zhí)行的操作的定義都獨(dú)立于所有本地處理器的指令集。Java 虛擬機(jī)(JVM)規(guī)范定義了這些字節(jié)碼的執(zhí)行(參見(jiàn) 參考資料)。執(zhí)行 Java 程序時(shí),用于任何特定本地平臺(tái)的任何 JRE 都必須遵守 JVM 規(guī)范中列出的規(guī)則。

因?yàn)榛诙褩5谋镜仄脚_(tái)很少(Intel X87 浮點(diǎn)數(shù)協(xié)處理器是一個(gè)明顯的例外),所以大多數(shù)本地平臺(tái)不能直接執(zhí)行 Java 字節(jié)碼。為了解決這個(gè)問(wèn)題,早期的 JRE 通過(guò)解釋字節(jié)碼來(lái)執(zhí)行 Java 程序。即 JVM 在一個(gè)循環(huán)中重復(fù)操作:

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

◆解碼;

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

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

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

這種方法的優(yōu)點(diǎn)是其簡(jiǎn)單性:JRE 開(kāi)發(fā)人員只需編寫(xiě)代碼來(lái)處理每種字節(jié)碼即可。并且因?yàn)橛糜诿枋霾僮鞯淖止?jié)碼少于 255 個(gè),所以實(shí)現(xiàn)的成本比較低。當(dāng)然,缺點(diǎn)是性能:這是一個(gè)早期造成很多人對(duì) Java 平臺(tái)不滿的問(wèn)題,盡管擁有很多其他優(yōu)點(diǎn)。

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

編譯 Java 代碼

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

圖 1. JIT 編譯器

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

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

因?yàn)檫@個(gè) JIT 編譯過(guò)程在程序執(zhí)行時(shí)發(fā)生,所以能夠保持平臺(tái)無(wú)關(guān)性:發(fā)布的仍然是中立的 Java 平臺(tái)代碼。C 和 C++ 之類的語(yǔ)言缺乏這種優(yōu)點(diǎn),因?yàn)樗鼈冊(cè)诔绦驁?zhí)行前進(jìn)行本地編譯;發(fā)布給(本地平臺(tái))執(zhí)行環(huán)境的是本地代碼。

#p#

挑戰(zhàn)

盡管通過(guò) JIT 編譯保持了平臺(tái)無(wú)關(guān)性,但是付出了一定代價(jià)。因?yàn)樵诔绦驁?zhí)行時(shí)進(jìn)行編譯,所以編譯代碼的時(shí)間將計(jì)入程序的執(zhí)行時(shí)間。任何編寫(xiě)過(guò)大型 C 或 C++ 程序的人都知道,編譯過(guò)程往往較慢。

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

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

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

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

動(dòng)態(tài)編譯的優(yōu)點(diǎn)

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

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

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

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

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

缺點(diǎn)

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

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

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

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

AOT Java 編譯

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

挑戰(zhàn)

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

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

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

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

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

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

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

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

AOT 代碼因此必須在沒(méi)有解析每個(gè)靜態(tài)、字段、類和方法引用的情況下生成。執(zhí)行時(shí),每個(gè)這些引用必須利用當(dāng)前運(yùn)行時(shí)環(huán)境的正確值進(jìn)行更新。這個(gè)過(guò)程可能直接影響第一次執(zhí)行的性能,因?yàn)樵诘谝淮螆?zhí)行時(shí)將解析所有引用。當(dāng)然,后續(xù)執(zhí)行將從修補(bǔ)代碼中獲益,從而可以更直接地引用實(shí)例、靜態(tài)字段或方法目標(biāo)。

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

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

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

圖 2. jxeinajar

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

#p#

優(yōu)點(diǎn)

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

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

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

優(yōu)缺點(diǎn)統(tǒng)計(jì)

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

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

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

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

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

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

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

圖 3. AOT 和 JIT 的性能對(duì)比

使用 JIT 編譯器的初始階段性能很低,因?yàn)橐紫冉忉尫椒ākS著編譯方法的增多及 JIT 執(zhí)行編譯所需時(shí)間的縮短,性能曲線逐漸升高最后達(dá)到性能峰值。另一方面,AOT 編譯代碼啟動(dòng)時(shí)的性能比解釋的性能高很多,但是無(wú)法達(dá)到 JIT 編譯器所能達(dá)到的最高性能。將靜態(tài)代碼綁定到 JVM 實(shí)例中會(huì)產(chǎn)生一些開(kāi)銷,因此開(kāi)始時(shí)的性能比穩(wěn)定狀態(tài)的性能值低,但是能夠比使用 JIT 編譯器更快地達(dá)到穩(wěn)定狀態(tài)的性能水平。

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

結(jié)束語(yǔ)

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

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

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

【編輯推薦】

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

2017-02-20 13:54:14

Java代碼編譯

2009-08-04 18:05:37

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

2011-06-29 17:00:26

QT 靜態(tài)編譯 Debug

2021-07-06 06:39:22

Java靜態(tài)代理動(dòng)態(tài)代理

2009-08-18 10:54:17

C#事件和委托

2011-06-21 16:51:21

Qt 靜態(tài) 編譯

2021-03-07 16:31:35

Java編譯反編譯

2020-10-26 11:33:45

編程語(yǔ)言編譯器軟件

2009-12-23 15:08:38

Fedora gcc編

2009-07-10 17:16:39

MyEclipse不編

2010-03-23 11:17:16

Python 動(dòng)態(tài)編譯

2010-01-11 15:47:37

C++編譯

2022-03-24 23:04:37

linux靜態(tài)庫(kù)動(dòng)態(tài)庫(kù)

2022-06-09 09:54:45

編譯軟件開(kāi)發(fā)

2022-01-18 08:40:31

Javascript 高階函數(shù)前端

2010-01-06 17:12:39

華為交換機(jī)vlan配置

2022-01-19 08:00:00

靜態(tài)代碼動(dòng)態(tài)代碼開(kāi)發(fā)

2010-01-18 10:34:21

C++編譯器

2022-11-04 08:22:14

編譯代碼C語(yǔ)言

2024-04-01 08:23:20

代碼Javajavascript
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 国产高清91| 免费午夜视频 | 亚洲精品在线免费观看视频 | 亚洲欧洲成人av每日更新 | 免费观看a级毛片在线播放 黄网站免费入口 | 国产高清精品一区二区三区 | 一区二区在线免费播放 | 美女天天操 | 国内精品免费久久久久软件老师 | 国产欧美精品一区二区 | 成年人精品视频在线观看 | 久久久精品一区二区三区四季av | 日韩图区| 国产欧美精品一区二区三区 | 久久国产精品一区二区三区 | 久久av一区 | 亚洲免费一区二区 | 亚洲精视频 | 水蜜桃亚洲一二三四在线 | 欧美亚洲国产一区 | 欧美久久一区二区三区 | 久久免费观看视频 | 久久精品国产清自在天天线 | 国产一区二区三区免费 | 欧美性tv | 在线视频成人 | 成人在线观看黄 | 中文字幕一区二区不卡 | 国产精品久久久久久久久久久久久 | 精品国产乱码一区二区三区a | 一区二区三区精品在线视频 | 成人精品鲁一区一区二区 | 国产综合久久 | 综合激情av| 精品欧美 | 国产午夜精品视频 | 岛国av在线免费观看 | 精品二| 毛片视频网址 | 色888www视频在线观看 | 97精品一区二区 |