編譯器優化:方法內聯

Awecoder發表於2021-12-26

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

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

相關文章