2015-06-15 149 views
11

鑑於以下代碼:爲什麼Java重複相同的代碼會更快?

public class Test{ 

    static int[] big = new int [10000]; 

    public static void main(String[] args){ 
     long time; 
     for (int i = 0; i < 16; i++){ 
      time = System.nanoTime(); 
      getTimes(); 
      System.out.println(System.nanoTime() - time); 
     }  
    } 
    public static void getTimes(){ 
     int d; 
     for (int i = 0; i < 10000; i++){ 
      d = big[i]; 
     }  
    } 
} 

輸出顯示遞減時間的趨勢:

171918 
167213 
165930 
165502 
164647 
165075 
203991 
70563 
45759 
43193 
45759 
44476 
45759 
52601 
47897 
48325 

爲什麼在getTimes相同的代碼被在時間少於三分之一執行它已經經過執行了8次或更多? (編輯:它並不總是發生在第8次,但從第5次到第10次)

+0

你能始終如一地重現此問題? – CodeNewbie

+0

我經常這樣做,總會有一點讓時間變得更小。 – MarkusK96

+2

看起來像JIT給我。關於爲什麼你不應該自己對Java進行基準測試,而是使用諸如[jmh](http://openjdk.java.net/projects/code-tools/jmh/)等適當的微型平臺工具的一個非常尖銳的示範。 –

回答

9

您看到的是一些JIT優化的結果應該清楚看到您收到的所有評論。但是真正發生了什麼以及爲什麼代碼在外部for的迭代次數相同後幾乎被優化?

我會盡量回答這兩個問題,但請記住,這裏解釋的所有內容都是相對於Oracle熱點虛擬機的只有。沒有定義JVM JIT應該如何表現的Java規範。首先,讓我們看看JIT在運行該測試程序時使用一些附加標誌(純JVM足以運行該程序,無需加載調試共享庫,這對於某些UnlockDiagnosticVMOptions選項是必需的) :

java -XX:+PrintCompilation Test 

執行這個輸出完畢(除開頭的幾行,顯示其它方法正在整理):

[...] 
195017 
184573 
184342 
184262 
183491 
189494 
    131 51%  3  Test::getTimes @ 2 (22 bytes) 
245167 
    132 52  3  Test::getTimes (22 bytes) 
165144 

65090 
    132 53  1  java.nio.Buffer::limit (5 bytes) 
59427 
    132 54%  4  Test::getTimes @ 2 (22 bytes) 
75137 
48110  
    135 51%  3  Test::getTimes @ -2 (22 bytes) made not entrant 

    142 55  4  Test::getTimes (22 bytes) 
150820 
86951 
90012 
91421 

你的代碼中printlns交錯帶DIAGNOST與JIT正在執行的編譯有關的信息。 尋找在一個單一的線路:

131 51%  3  Test::getTimes @ 2 (22 bytes) 

每一列具有以下含義:

  1. 時間戳
  2. 編譯ID(與如果需要額外的屬性)
  3. 分層彙編級
  4. 方法短名稱(如果有的話,@osr_bci
  5. 編譯m ethod大小

只保留相關getTimes行:

131 51%  3  Test::getTimes @ 2 (22 bytes) 
    132 52  3  Test::getTimes (22 bytes) 
    132 54%  4  Test::getTimes @ 2 (22 bytes)  
    135 51%  3  Test::getTimes @ -2 (22 bytes) made not entrant 
    142 55  4  Test::getTimes (22 bytes) 

很明顯,getTimes正在編譯不止一次,但每次都在以不同的方式編譯。

%符號表示堆棧上替換(OSR)已經被執行,這意味着包含在getTimes的10K環已編譯從方法的其餘部分,該JVM分離置換該第編譯版本的方法代碼。 osr_bci是一個指向這個新編譯的代碼塊的索引。

下編譯是一個典型的JIT編譯,編譯所有的getTimes方法(大小仍然是相同的,因爲沒有別的比環路其他該方法)。

第三次執行另一個OSR,但處於不同的分層級別。在Java7中添加了分層編譯,並且基本上允許JVM在運行時選擇客戶端服務器 JIT模式,在必要時在兩者之間自由切換。客戶端模式執行更簡單的一組優化策略,而服務器模式能夠應用更復雜的優化,另一方面,編譯時花費更大。

我不會去到有關不同模式或約分層編制的詳細信息,如果您需要更多的信息,我由斯科特·奧克斯建議Java Performance: The Definitive Guide並檢查this question解釋水平之間有什麼變化。

回到PrintCompilation的輸出,這裏的要點是,從某個時間點開始,一系列複雜度增加的編譯被執行,直到方法變得明顯穩定(即JIT不再編譯它)。

那麼,爲什麼所有這些都是在主循環5-10次迭代後的某個時間點開始的呢?

因爲內部getTimes循環已成爲「熱」。

的熱點VM,通常定義了「熱」的那些方法已被調用的至少10K倍(這是歷史默認閾值,可以使用-XX:CompileThreshold=<num>被改變,以分層彙編現在有多個閾值),但在OSR的例子我猜測它是在代碼塊被認爲「足夠熱」的情況下執行的,就絕對或相對執行時間而言,該方法包含它。

其他參考

PrintCompilation Guide通過克里斯塔爾莫

Java Performance: The Definitive Guide

+0

我第二推薦,Java性能:無論你真的需要從你的應用程序中擠出更多的性能還是你只是八卦,TPG的確是無價的。 – biziclop

4

虛擬機的JIT(即時)編譯器優化了Java字節代碼的解釋。例如,如果您有一個if()語句,在大約99%的情況下爲false,那麼jit會優化您的代碼以處理這個假情況,這會使您的真實情況最終變慢。對不起,英文不好。

+2

您能否提供一些參考? –

+1

https://en.wikipedia.org/wiki/Just-in-time_compilation –

+0

Bill Venners在Java 2虛擬機內閱讀。雖然java版本比較老,但它會給你JVM內部工作的好主意。 – User27854

0

例子:代碼優化前

class A { 
    B b; 
    public void newMethod() { 
    y = b.get(); //calling get() function 
    ...do stuff... 
    z = b.get(); // calling again 
    sum = y + z; 
    } 
} 
class B { 
    int value; 
    final int get() { 
     return value; 
    } 
} 

例子:代碼後的Optim化

class A { 
B b; 
public void newMethod() { 
    y = b.value; 
    ...do stuff... 
    sum = y + y; 
} 
} 
class B { 
    int value; 
    final int get() { 
     return value; 
    } 
} 

最初,代碼包含兩次調用b.get()方法。在 優化之後,兩個方法調用被優化爲一個單一的 可變副本操作;也就是說,優化的代碼不需要 執行方法調用來獲取B類的字段值。

Read more

相關問題