在有關如何優化代碼的一些最近的討論,有人告訴我,斷碼成很多小方法可以顯著提高性能,因爲JIT編譯器不喜歡大的優化方法。這是真的,有很多小方法可以幫助JIT編譯器優化?
我對此並不確定,因爲看起來JIT編譯器本身能夠識別自包含的代碼段,而不管它們是否在自己的方法中。
任何人都可以證實或反駁這種說法?
在有關如何優化代碼的一些最近的討論,有人告訴我,斷碼成很多小方法可以顯著提高性能,因爲JIT編譯器不喜歡大的優化方法。這是真的,有很多小方法可以幫助JIT編譯器優化?
我對此並不確定,因爲看起來JIT編譯器本身能夠識別自包含的代碼段,而不管它們是否在自己的方法中。
任何人都可以證實或反駁這種說法?
我真的不明白它是如何工作的,但基於the link AurA provided,我猜測如果相同的位被重用,JIT編譯器將不得不編譯更少的字節碼,而不必編譯類似的不同字節碼跨越不同的方法。
除此之外,您可以將代碼分解爲更多的代碼,您將從代碼中獲得更多的重用,並且這將允許優化運行它的虛擬機(您正在提供更多架構來處理)。
但是我懷疑如果你沒有提供任何代碼重用的意義,那麼你的代碼就會失效。
如果你採用完全相同的代碼,並將它們分解成許多小方法,那根本不會有助於JIT。
一個更好的方式來說,現代的HotSpot JVM不會因爲寫很多小的方法而懲罰你。他們得到積極內聯,所以在運行時,你真的不支付函數調用的成本。即使對於invokevirtual調用,如調用接口方法的調用也是如此。
幾年前,我做了一個blog post,描述瞭如何看到JVM是內聯方法。該技術仍然適用於現代JVM。我還發現它有用看看到invokedynamic相關的討論,其中如何現代熱點的JVM編譯Java字節代碼被廣泛討論。
的熱點JIT僅內聯方法,這些方法小於特定(可配置的)大小。所以使用更小的方法可以實現更多的內聯,這很好。
請參閱this page上的各種內聯選項。
編輯
要詳細一點:
例(完整的代碼具有相同的行號,如果你嘗試)
package javaapplication27;
public class TestInline {
private int count = 0;
public static void main(String[] args) throws Exception {
TestInline t = new TestInline();
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += t.m();
}
System.out.println(sum);
}
public int m() {
int i = count;
if (i % 10 == 0) {
i += 1;
} else if (i % 10 == 1) {
i += 2;
} else if (i % 10 == 2) {
i += 3;
}
i += count;
i *= count;
i++;
return i;
}
}
運行此代碼與以下JVM標誌:-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:FreqInlineSize=50 -XX:MaxInlineSize=50 -XX:+PrintInlining
(是的,我已經使用的值是證明我case:m
太大,但重構的m
和m2
都低於閾值 - 其他值可能會得到不同的輸出)。
你會看到m()
和main()
被編譯,但m()
不會被內聯:
56 1 javaapplication27.TestInline::m (62 bytes)
57 1 % javaapplication27.TestInline::main @ 12 (53 bytes)
@ 20 javaapplication27.TestInline::m (62 bytes) too big
您還可以檢查生成的程序集,以確認m
沒有內聯(我用這些JVM標誌:-XX:+PrintAssembly -XX:PrintAssemblyOptions=intel
) - 它看起來就像這樣:
0x0000000002780624: int3 ;*invokevirtual m
; - javaapplication27.TestInline::[email protected] (line 10)
如果重構這樣的代碼(我已經提取的if/else在一個單獨的方法):
public int m() {
int i = count;
i = m2(i);
i += count;
i *= count;
i++;
return i;
}
public int m2(int i) {
if (i % 10 == 0) {
i += 1;
} else if (i % 10 == 1) {
i += 2;
} else if (i % 10 == 2) {
i += 3;
}
return i;
}
你會看到下面的編輯操作:
60 1 javaapplication27.TestInline::m (30 bytes)
60 2 javaapplication27.TestInline::m2 (40 bytes)
@ 7 javaapplication27.TestInline::m2 (40 bytes) inline (hot)
63 1 % javaapplication27.TestInline::main @ 12 (53 bytes)
@ 20 javaapplication27.TestInline::m (30 bytes) inline (hot)
@ 7 javaapplication27.TestInline::m2 (40 bytes) inline (hot)
所以m2
被聯到m
,你所期望的,所以我們又回到了原來的情景。但是當main
得到編譯時,它實際上是整個事情。在裝配層面,這意味着您再也找不到任何invokevirtual
說明。你會發現這樣的線:
0x00000000026d0121: add ecx,edi ;*iinc
; - javaapplication27.TestInline::[email protected] (line 33)
; - javaapplication27.TestInline::[email protected] (line 24)
; - javaapplication27.TestInline::[email protected] (line 10)
其中基本常見的指令是「相互」。
結論
我不是說這個例子是代表,但它似乎證明幾點:使用更小的方法
最後:如果你的代碼的一部分是表現真正的關鍵,這些因素無所謂,你應該檢查JIT輸出微調你的代碼和重要的前後輪廓。
相關的選項是'-XX:InlineSmallCode = n' – Raedwald
「所以使用較小的方法可以實現更多的內聯,這是很好的」,但是如果內聯的方法僅僅因爲一個大方法被拆分爲幾個方法而存在,得到了。 – Raedwald
@Raedwald「FreqInlineSize」和「MaxInlineSize」也是相關的,具體取決於您嘗試實現的內容。關於你的第二個評論:這不是那麼簡單 - 如果m1()調用'm2()'且兩者都在內聯限制內,整個'm1 + m2'可能會在調用站點中被內聯(如果它經常被稱爲等等)。另一方面,如果你將兩個方法合併成一個超過極限的'm()',則沒有任何內聯,你失去了這個優化。我會稍後嘗試添加示例。 – assylias
一般JIT編譯過程由以下steps..http的://publib.boulder.ibm.com/infocenter/java7sdk/v7r0/index.jsp話題=%2Fcom.ibm.java.win.70.doc %2Fdiag%2瞭解%2Fjit_overview.html,但它沒有提到jit如何處理大或小的模塊 – AurA