編譯器優化:方法內聯

方法內聯的思想是,把目標方法的代碼複製代發起調用的方法之中,避免發生真實的方法調用。

public class InlineTest {
    private static int add1(int x1, int x2, int x3, int x4) {
        return add2(x1, x2) + add2(x3, x4);
    }

    private static int add2(int x1, int x2) {
        return x1 + x2;
    }
}

如上代碼,我們知道線程執行方法時,會向虛擬機棧壓入棧幀,add1方法中調用了兩次add2方法會壓入兩次add2的棧幀。頻繁出入棧操作,消耗內存和時間。

JVM可以對上面的操作進行方法內聯優化,優化爲下面代碼。

private static int add1(int x1, int x2, int x3, int x4) {
    return x1 + x2 + x3 + x4;
}

方法內聯的條件有兩個:

  1. 方法體足夠小。

    1. 熱點方法,如果方法體小於325字節會嘗試內聯,可以使用 -XX:FreqInlineSize修改大小。
    2. 非熱點方法,如果方法體小於35字節嘗試內聯, -XX:MaxInlineSize
  2. 被調用的方法在運行時的實現可以被唯一確認。

    1. static、private、final方法,JIT可以唯一確認具體的實現代碼。
    2. public實例方法,指向的實現可能是自身、父類、子類的代碼(多態),只有當JIT唯一確認方法實現時,纔有可能內聯。

內聯可能帶來的問題:會導致方法變大,使得CodeCache溢出,導致JVM退化成解釋執行模式。

一般情況,使用默認JVM參數就好。

測試方法內聯

@Slf4j
public class InlineTest {
    private static int add1(int x1, int x2, int x3, int x4) {
        return add2(x1, x2) + add2(x3, x4);
    }

    private static int add2(int x1, int x2) {
        return x1 + x2;
    }

    private static long compute() {
        long start = System.currentTimeMillis();
        int result = 0;
        Random random = new Random();
        for (int i = 0; i < 10000000; i++) {
            result = add1(random.nextInt(), random.nextInt(), random.nextInt(), random.nextInt());
        }
        long end = System.currentTimeMillis();
        return end - start;
    }

    public static void main(String[] args) {
        long compute = compute();
        log.info("花費{}ms", compute);
    }
}

花費362ms
花費483ms

設置JVM參數,打印內聯日誌,開關內聯(通過設置內聯閾值)。

-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:FreqInlineSize=1

默認開啓方法內聯,比直接關掉運行更快。

JVM參數備註

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章