2013-04-02 29 views
27

在有關如何優化代碼的一些最近的討論,有人告訴我,斷碼成很多小方法可以顯著提高性能,因爲JIT編譯器不喜歡大的優化方法。這是真的,有很多小方法可以幫助JIT編譯器優化?

我對此並不確定,因爲看起來JIT編譯器本身能夠識別自包含的代碼段,而不管它們是否在自己的方法中。

任何人都可以證實或反駁這種說法?

+0

一般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

回答

1

我真的不明白它是如何工作的,但基於the link AurA provided,我猜測如果相同的位被重用,JIT編譯器將不得不編譯更少的字節碼,而不必編譯類似的不同字節碼跨越不同的方法。

除此之外,您可以將代碼分解爲更多的代碼,您將從代碼中獲得更多的重用,並且這將允許優化運行它的虛擬機(您正在提供更多架構來處理)。

但是我懷疑如果你沒有提供任何代碼重用的意義,那麼你的代碼就會失效。

7

如果你採用完全相同的代碼,並將它們分解成許多小方法,那根本不會有助於JIT。

一個更好的方式來說,現代的HotSpot JVM不會因爲寫很多小的方法而懲罰你。他們得到積極內聯,所以在運行時,你真的不支付函數調用的成本。即使對於invokevirtual調用,如調用接口方法的調用也是如此。

幾年前,我做了一個blog post,描述瞭如何看到JVM是內聯方法。該技術仍然適用於現代JVM。我還發現它有用看看到invokedynamic相關的討論,其中如何現代熱點的JVM編譯Java字節代碼被廣泛討論。

+0

「*如果您採用完全相同的代碼並將其分解爲許多小方法,那麼這不會有助於JIT 「*」=>我不認爲這是準確的(至少在熱點上)。 – assylias

+0

我這樣說的原因基本上與@Raewald在另一個答案中所寫的相同,引用「但是如果它內聯的方法存在只是因爲一個大的方法已經被分成幾種方法,那麼什麼都沒有得到」。你會詳細說明爲什麼你認爲它不準確嗎? –

+0

請參閱我提供的示例 - 使用一種大方法:不內聯,使用兩個較小的方法執行完全相同的操作,兩者都被內聯。 – assylias

19

的熱點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太大,但重構的mm2都低於閾值 - 其他值可能會得到不同的輸出)。

你會看到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輸出微調你的代碼和重要的前後輪廓。

+1

相關的選項是'-XX:InlineSmallCode = n' – Raedwald

+0

「所以使用較小的方法可以實現更多的內聯,這是很好的」,但是如果內聯的方法僅僅因爲一個大方法被拆分爲幾個方法而存在,得到了。 – Raedwald

+1

@Raedwald「FreqInlineSize」和「MaxInlineSize」也是相關的,具體取決於您嘗試實現的內容。關於你的第二個評論:這不是那麼簡單 - 如果m1()調用'm2()'且兩者都在內聯限制內,整個'm1 + m2'可能會在調用站點中被內聯(如果它經常被稱爲等等)。另一方面,如果你將兩個方法合併成一個超過極限的'm()',則沒有任何內聯,你失去了這個優化。我會稍後嘗試添加示例。 – assylias