1. 程式人生 > 實用技巧 >jvm之方法內聯優化

jvm之方法內聯優化

前言

在日常中工作中,我們時不時會程式碼進行一些優化,比如用新的演算法,簡化計算邏輯,減少計算量等。對於java程式來說,除了開發者本身對程式碼優化之外,還有一個"人"也在背後默默的優化我們的程式碼,這個"人"就是jvm。jvm會幫我們分析出熱點程式碼,優化程式碼邏輯。其中jvm最常做的優化之一就是:方法內聯優化。

方法內聯

什麼是方法內聯?又可以叫做函式內聯,java中方法可等同於其它語言中的函式。關於方法內聯維基百科上面解釋是:

在電腦科學中,行內函數(有時稱作線上函式或編譯時期展開函式)是一種程式語言結構,用來建議編譯器對一些特殊函式進行內聯擴充套件(有時稱作線上擴充套件);也就是說建議編譯器將指定的函式體插入並取代每一處呼叫該函式的地方(上下文),從而節省了每次呼叫函式帶來的額外時間開支。

簡單通俗的講就是把方法內部呼叫的其它方法的邏輯,嵌入到自身的方法中去,變成自身的一部分,之後不再呼叫該方法,從而節省呼叫函式帶來的額外開支。

函式呼叫開銷

之所以出現方法內聯是因為函式呼叫除了執行自身邏輯的開銷外,還有一些不為人知的額外開銷。這部分額外的開銷主要來自方法棧幀的生成、引數欄位的壓入、棧幀的彈出、還有指令執行地址的跳轉。比如有下面這樣程式碼:

public static void function_A(int a, int b){
        //do something
        function_B(a,b);
    }
    
    public static
void function_B(int c, int d){ //do something } public static void main(String[] args){ function_A(1,2); }

則程式碼的執行過程如下:

所以如果java中方法呼叫巢狀過多或者方法過多,這種額外的開銷就越多。

試想一下想get/set這種方法呼叫:

public int getI() {
        return i;
    }

public void setI(int i) {
        this.i = i;
    }

很可能自身執行邏輯的開銷還比不上為了呼叫這個方法的額外開鎖。如果類似的方法被頻繁的呼叫,則真正相對執行效率就會很低,雖然這類方法的執行時間很短。這也是為什麼jvm會在熱點程式碼中執行方法內聯的原因,這樣的話就可以省去呼叫呼叫函式帶來的額外開支。

這裡舉個內聯的可能形式:

public int  add(int a, int b , int c, int d){
          return add(a, b) + add(c, d);
    }
    
    public int add(int a, int b){
        return a + b;
    }

內聯之後:

public int  add(int a, int b , int c, int d){
          return a + b + c + d;
    }

這樣除了本身的相加邏輯的開銷,比內聯前減少了二次呼叫函式帶來的額外開銷。

內聯條件

一個方法如果滿足以下條件就很可能被jvm內聯。

1、熱點程式碼。 如果一個方法的執行頻率很高就表示優化的潛在價值就越大。那程式碼執行多少次才能確定為熱點程式碼?這是根據編譯器的編譯模式來決定的。如果是客戶端編譯模式則次數是1500,服務端編譯模式是10000。次數的大小可以通過-XX:CompileThreshold來調整。

2、方法體不能太大。jvm中被內聯的方法會編譯成機器碼放在code cache中。如果方法體太大,則能快取熱點方法就少,反而會影響效能。

3、如果希望方法被內聯,儘量用private、static、final修飾,這樣jvm可以直接內聯。如果是public、protected修飾方法jvm則需要進行型別判斷,因為這些方法可以被子類繼承和覆蓋,jvm需要判斷內聯究竟內聯是父類還是其中某個子類的方法。

所以瞭解jvm方法內聯機制之後,會有助於我們工作中寫出能讓jvm更容易優化的程式碼,有助於提升程式的效能。