要點(diǎn)提煉| 理解JVM之程序編譯&代碼優(yōu)化

本篇將介紹程序編譯時(shí)期的代碼優(yōu)化手段,分成兩個(gè)階段:

  • 概述
  • 早期(編譯期)優(yōu)化
  • 晚期(運(yùn)行期)優(yōu)化

1.概述

a.由于對(duì)Java語(yǔ)言的編譯期理解不同,可以分出幾個(gè)時(shí)期:

  • 前端編譯器
    • 作用:把Java代碼轉(zhuǎn)變成字節(jié)碼
    • 代表:Sun的Javac、Eclipse JDT中的增量式編譯器(ECJ)
    • 該時(shí)期的優(yōu)化主要用于提升程序的編碼效率
  • 后端運(yùn)行期編譯器/JIT編譯器
    • 作用:把字節(jié)碼轉(zhuǎn)變成本地機(jī)器碼
    • 代表:HotSpot VM的C1、C2編譯器
    • 該時(shí)期的優(yōu)化主要用于提升程序的運(yùn)行效率
  • 靜態(tài)提前編譯器/AOT編譯器
    • 作用:直接把Java代碼編譯成本地機(jī)器碼
    • 代表:GNU Compiler for the Java(GCJ)、Excelsior JET

b.Java即時(shí)編譯器與C/C++靜態(tài)編譯器的對(duì)比

  • 即時(shí)編譯器運(yùn)行需要占用程序運(yùn)行時(shí)間,使得優(yōu)化手段受制于編譯成本,否則用戶將在啟動(dòng)程序察覺(jué)到重大延遲;而靜態(tài)編譯器的編譯時(shí)間成本不是重點(diǎn)
  • 靜態(tài)編譯器所有優(yōu)化都在編譯期完成,而即時(shí)編譯器的動(dòng)態(tài)性是把雙刃劍,一方面要求虛擬機(jī)頻繁進(jìn)行動(dòng)態(tài)檢查從而消耗大量運(yùn)行時(shí)間,而且難以全局優(yōu)化、只能以激進(jìn)優(yōu)化來(lái)完成,另一方面擁有運(yùn)行期性能監(jiān)控的優(yōu)化措施,如調(diào)用頻率預(yù)測(cè)、分支頻率預(yù)測(cè)、裁剪未被選擇的分支等
  • Java中使用虛方法的頻率遠(yuǎn)大于C/C++,表示運(yùn)行時(shí)對(duì)方法接收者進(jìn)行多態(tài)選擇的頻率更大,因此在進(jìn)行某些優(yōu)化難度會(huì)更大
  • Java在堆上進(jìn)行對(duì)象的內(nèi)存分配,而C/C++可在堆、棧上分配,減輕了內(nèi)存回收的壓力;且C/C++中主要由用戶程序代碼回收內(nèi)存,不存在無(wú)用對(duì)象的篩選,相比于垃圾收集機(jī)制運(yùn)行效率更高

2.早期(編譯期)優(yōu)化

幾乎所有語(yǔ)言都提供一些語(yǔ)法糖來(lái)方便開(kāi)發(fā),或能提高效率、或能提升語(yǔ)法的嚴(yán)謹(jǐn)性、或能減少編碼出錯(cuò)的機(jī)會(huì),下面是幾種常見(jiàn)語(yǔ)法糖:

  • 泛型與類型擦除
    • C#的泛型是真實(shí)泛型:無(wú)論在程序源碼、編譯后的IL、還是運(yùn)行期的CLR中都是切實(shí)存在的,List<int>和List<String>在系統(tǒng)運(yùn)行期生成,有自己的虛方法表和類型數(shù)據(jù),屬于不同的類型,這種實(shí)現(xiàn)稱為類型膨脹
    • Java的泛型是偽泛型:只在程序源碼中存在,在編譯后的字節(jié)碼文件中就已替換為原生類型,并在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此ArrayList<int>與ArrayList<String>是同一個(gè)類,這種實(shí)現(xiàn)稱為類型擦除
  • 自動(dòng)裝箱、拆箱
  • 遍歷循環(huán)
  • 條件編譯:使用條件為常量的if語(yǔ)句

更多Java語(yǔ)法糖系列


3.晚期(運(yùn)行期)優(yōu)化

a.HotSpot虛擬機(jī)采用解釋器與編譯器并存的架構(gòu),交互情況:

  • 當(dāng)程序需要迅速啟動(dòng)和執(zhí)行時(shí),解釋器可以先發(fā)揮作用,從而省去編譯時(shí)間
  • 程序運(yùn)行后,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用,把更多代碼編譯成本地代碼,從而獲取更高的執(zhí)行效率
  • 如果程序運(yùn)行環(huán)境受內(nèi)存資源限制較大,可以用解釋執(zhí)行節(jié)約內(nèi)存,反之可以用編譯執(zhí)行提升效率
  • 解釋器可作為編譯器激進(jìn)優(yōu)化的逃生門(mén),當(dāng)激進(jìn)優(yōu)化不成立時(shí),如加載新類后類型繼承結(jié)構(gòu)出現(xiàn)變化、出現(xiàn)罕見(jiàn)陷阱,可通過(guò)逆優(yōu)化退回到解釋狀態(tài)繼續(xù)執(zhí)行。如圖:
解釋器與編譯器的交互

有上圖可見(jiàn),HotSpot虛擬機(jī)中內(nèi)置了兩個(gè)即時(shí)編譯器:Client Compiler(C1編譯器和)和Server Compiler(C2編譯器),搭配模式:

  • 混合模式(Mixed Mode):默認(rèn)采用解釋器與其中一個(gè)編譯器進(jìn)行配合工作,虛擬機(jī)會(huì)根據(jù)自身版本與宿主機(jī)器的硬件性能自動(dòng)選擇運(yùn)行模式和編譯器,用戶可以使用-client-server參數(shù)去強(qiáng)制指定虛擬機(jī)運(yùn)行在Client模式或Server模式。
  • 解釋模式(Interpreted Mode):使用參數(shù)-Xint,編譯器不工作,都使用解釋方式執(zhí)行。
  • 編譯模式(Compiled Mode):使用參數(shù)-Xcomp,優(yōu)先采用編譯方式執(zhí)行,但解釋器仍然要在編譯無(wú)法進(jìn)行的情況下介入執(zhí)行過(guò)程。

b.HotSpot即時(shí)編譯器的編譯對(duì)象:熱點(diǎn)代碼

  • 分類:
    • 被多次調(diào)用的方法:采用JIT編譯方式,以整個(gè)方法作為編譯對(duì)象
    • 被多次執(zhí)行的循環(huán)體:采用OSR編譯方式,發(fā)生在方法執(zhí)行過(guò)程中,仍以整個(gè)方法作為編譯對(duì)象
  • 判斷方式:通過(guò)熱點(diǎn)探測(cè)
    • 基于采樣的熱點(diǎn)探測(cè)(Sample Based Hot Spot Detection):周期性檢查各個(gè)線程的棧頂,常出現(xiàn)在棧頂?shù)姆椒ň褪菬狳c(diǎn)方法
      • 好處:實(shí)現(xiàn)簡(jiǎn)單、高效、易于獲取方法調(diào)用關(guān)系
      • 缺點(diǎn):難以精確確認(rèn)某個(gè)方法的熱度、易受到線程阻塞或外界影響而擾亂熱點(diǎn)探測(cè)
    • 基于計(jì)數(shù)器的熱點(diǎn)探測(cè)(Counter Based Hot Spot Detection):為每個(gè)方法建立計(jì)數(shù)器來(lái)統(tǒng)計(jì)方法的執(zhí)行次數(shù),執(zhí)行次數(shù)超過(guò)一定的閾值就是熱點(diǎn)方法
      • 優(yōu)點(diǎn):精確、嚴(yán)謹(jǐn)
      • 缺點(diǎn):實(shí)現(xiàn)較麻煩、不能直接獲取到方法的調(diào)用關(guān)系
      • 計(jì)數(shù)器類型:
        • 方法調(diào)用計(jì)數(shù)器(Invocation Counter):統(tǒng)計(jì)方法被調(diào)用的次數(shù),當(dāng)計(jì)數(shù)器超過(guò)閾值會(huì)觸發(fā)JIT編譯
        • 回邊計(jì)數(shù)器(Back Edge Counter):統(tǒng)計(jì)方法中循環(huán)體代碼執(zhí)行的次數(shù),當(dāng)計(jì)數(shù)器超過(guò)閾值會(huì)觸發(fā)OSR編譯

c.HotSpot即時(shí)編譯器的編譯過(guò)程

  • Client Compiler:主要進(jìn)行局部?jī)?yōu)化、放棄耗時(shí)較長(zhǎng)的全局優(yōu)化。采用簡(jiǎn)單快速的三段式編譯:
    • 第一個(gè)階段:一個(gè)平臺(tái)獨(dú)立的前端把字節(jié)碼構(gòu)造成一種高級(jí)中間代碼表示(HIR),在此之前會(huì)在字節(jié)碼上完成一部分基礎(chǔ)優(yōu)化,如方法內(nèi)聯(lián)、常量傳播等
    • 第二個(gè)階段:一個(gè)平臺(tái)相關(guān)的后端從HIR中產(chǎn)生低級(jí)中間代碼表示(LIR),在此之前會(huì)在HIR上完成另外一些優(yōu)化,如空值檢查消除、范圍檢查消除等,以便讓HIR達(dá)到更高效的代碼表示形式
    • 第三個(gè)階段:平臺(tái)相關(guān)的后端使用線性掃描算法在LIR 上分配寄存器,并在LIR上做窺孔優(yōu)化,然后產(chǎn)生機(jī)器代碼。大致執(zhí)行過(guò)程如圖:
  • Server Compiler:專門(mén)面向服務(wù)端的典型應(yīng)用并且特別為服務(wù)端的性能配置調(diào)整過(guò),是一個(gè)充分優(yōu)化過(guò)的高級(jí)編譯器,體現(xiàn)在:
    • 會(huì)執(zhí)行所有經(jīng)典的優(yōu)化動(dòng)作:如無(wú)用代碼消除、循環(huán)展開(kāi)、循環(huán)表達(dá)式外提、消除公共子表達(dá)式、常量傳播、基本塊重排序等
    • 會(huì)實(shí)施與Java特性密切相關(guān)的優(yōu)化技術(shù):如范圍檢查消除、空值檢查消除等
    • 根據(jù)解釋器或Client Compiler提供的性能監(jiān)控信息可能會(huì)進(jìn)行一些不穩(wěn)定的激進(jìn)優(yōu)化:如守護(hù)內(nèi)聯(lián)、分支頻率預(yù)測(cè)等

另外,Server Compiler的寄存器分配器是一個(gè)全局圖著色分配器,能夠充分利用某些處理器架構(gòu)上的大寄存器集合。雖然Server Compiler的編譯時(shí)間比較緩慢,但是其編譯速度遠(yuǎn)超于傳統(tǒng)的靜態(tài)優(yōu)化編譯器,且比Client Compiler編譯輸出的代碼質(zhì)量更高,能減少本地代碼的執(zhí)行時(shí)間,從而抵消了額外的編譯時(shí)間開(kāi)銷(xiāo)。

d.HotSpot虛擬機(jī)即時(shí)編譯器在生成代碼時(shí)采用的代碼優(yōu)化技術(shù)

其中幾種最有代表性的優(yōu)化技術(shù):

  • 語(yǔ)言無(wú)關(guān)的經(jīng)典優(yōu)化技術(shù)之一:公共子表達(dá)式消除(Common Subexpression Elimination)
    • 含義:若一個(gè)表達(dá)式E已經(jīng)計(jì)算過(guò)且E中所有變量值未發(fā)生任何變化,則稱E為公共子表達(dá)式,此時(shí)沒(méi)必要花時(shí)間再次計(jì)算,直接用之前計(jì)算過(guò)的表達(dá)式結(jié)果代替E即可
    • 類型:
      • 局部公共子表達(dá)式消除:優(yōu)化僅限于程序的基本塊內(nèi)
      • 全局公共子表達(dá)式消除:優(yōu)化的范圍涵蓋了多個(gè)基本塊
  • 語(yǔ)言相關(guān)的經(jīng)典優(yōu)化技術(shù)之一 :數(shù)組邊界檢查消除(Array Bounds Checking Elimination)
    • 若數(shù)組下標(biāo)是個(gè)常量,只要在編譯期根據(jù)數(shù)據(jù)流分析確定這個(gè)數(shù)組的長(zhǎng)度,且判斷得出該數(shù)組下標(biāo)未越界,那么運(yùn)行時(shí)無(wú)需再檢查
    • 若數(shù)組訪問(wèn)發(fā)生在循環(huán)中且使用循環(huán)變量來(lái)進(jìn)行數(shù)組訪問(wèn),只要在編譯期根據(jù)數(shù)據(jù)流分析確定循環(huán)變量的取值范圍永遠(yuǎn)在區(qū)間[0,數(shù)組長(zhǎng)度)內(nèi),那么在整個(gè)循環(huán)中無(wú)需再進(jìn)行多次檢查
  • 最重要的優(yōu)化技術(shù)之一:方法內(nèi)聯(lián)(Method Inlining)
    • 含義:把目標(biāo)方法的代碼復(fù)制到發(fā)起調(diào)用的方法之中,避免發(fā)生真實(shí)的方法調(diào)用
    • 主要目的:去除方法調(diào)用的成本,如建立棧幀等;為其他優(yōu)化建立良好的基礎(chǔ),便于在更大范圍上采取后續(xù)的優(yōu)化手段、獲取更好的優(yōu)化效果
  • 最前沿的優(yōu)化技術(shù)之一:逃逸分析(Escape Analysis)
    • 基本行為:分析對(duì)象動(dòng)態(tài)作用域
    • 類型:
      • 方法逃逸:一個(gè)對(duì)象在方法中被定義后,可能被外部方法所引用。如作為調(diào)用參數(shù)傳遞到其他方法中
      • 線程逃逸:一個(gè)對(duì)象在方法中被定義后,能被外部線程訪問(wèn)到。如賦值給類變量或可以在其他線程中訪問(wèn)的實(shí)例變量
    • 對(duì)能夠證明不會(huì)逃逸到方法或線程之外的對(duì)象可進(jìn)行的優(yōu)化手段:
      • 棧上分配(Stack Allocation):在棧上對(duì)該對(duì)象進(jìn)行內(nèi)存分配,此時(shí)該對(duì)象所占用的內(nèi)存空間會(huì)隨棧幀出棧而銷(xiāo)毀,可減少垃圾收集系統(tǒng)的壓力
      • 同步消除(Synchronization Elimination):在該對(duì)象上不會(huì)有讀寫(xiě)競(jìng)爭(zhēng),可消除掉對(duì)該對(duì)象的同步措施,從而減少資源的消耗
      • 標(biāo)量替換(Scalar Replacement):若該對(duì)象可以進(jìn)一步分解,那么直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來(lái)替換

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容