2012-08-25 158 views
9

這個問題與此相同 Two loop bodies or one (result identical) 但在我的情況下,我使用Java。一個循環中的兩個操作與兩個循環每個循環執行相同的操作

我有兩個循環運行10億次。

int a = 188, b = 144, aMax = 0, bMax = 0; 

for (int i = 0; i < 1000000000; i++) { 
    int t = a^i; 
    if (t > aMax) 
    aMax = t;  
} 

for (int i = 0; i < 1000000000; i++) { 
    int t = b^i; 
    if (t > bMax) 
    bMax = t;  
} 

在我的機器上運行這兩個循環所用的時間約爲4秒。當我將這兩個環路融合成一個單一的環路並執行單個環路中的所有操作時,它將在2秒內運行。正如你所看到的,微不足道的操作構成了循環內容,因此需要時間不變。

我的問題是我在哪裏獲得這種性能改進?

我猜測,在兩個獨立的循環中性能受到影響的唯一可能的地方是,它會增加i並檢查我是否將10億次循環融合在一起,而如果我只將10億次乘以10億次。那裏有其他事情嗎?

謝謝!

+0

我會假設這是因爲你正在做1B更多的增量,1B更多的比較,和1B更跳躍... – verdesmarald

+1

在循環內部移動'int t;'聲明並僅在循環內執行賦值't = a^i;'或't = b^i;'會產生什麼影響? – barrowc

+0

@barrowc它不會有任何效果。 JIT的第一個階段之一是將AST圖轉換爲單一分配表示,爲了更好地進行壽命分析,這將消除這種混疊。 – ddimitrov

回答

5

如果你不這樣做運行一個預熱階段,有可能第一個循環得到優化和編譯,但不是第二個循環,而當你合併它們時,整個合併循環被編譯。此外,使用server選項和您的代碼,大多數會得到優化,因爲您不使用結果。

我已經運行了下面的測試,將每個循環以及合併循環放入他們自己的方法中,然後對JVM進行加熱以確保所有內容都被編譯。

結果(JVM選項:-server -XX:+PrintCompilation):

  • 環1 = 500ms的
  • 環2 = 900毫秒
  • 合併循環= 1300毫秒

所以合併循環是略微更快,但不是那麼多。

public static void main(String[] args) throws InterruptedException { 

    for (int i = 0; i < 3; i++) { 
     loop1(); 
     loop2(); 
     loopBoth(); 
    } 

    long start = System.nanoTime(); 

    loop1(); 

    long end = System.nanoTime(); 
    System.out.println((end - start)/1000000); 

    start = System.nanoTime(); 
    loop2(); 
    end = System.nanoTime(); 
    System.out.println((end - start)/1000000); 

    start = System.nanoTime(); 
    loopBoth(); 
    end = System.nanoTime(); 
    System.out.println((end - start)/1000000); 
} 

public static void loop1() { 
    int a = 188, aMax = 0; 
    for (int i = 0; i < 1000000000; i++) { 
     int t = a^i; 
     if (t > aMax) { 
      aMax = t; 
     } 
    } 
    System.out.println(aMax); 
} 

public static void loop2() { 
    int b = 144, bMax = 0; 
    for (int i = 0; i < 1000000000; i++) { 
     int t = b^i; 
     if (t > bMax) { 
      bMax = t; 
     } 
    } 
    System.out.println(bMax); 
} 

public static void loopBoth() { 
    int a = 188, b = 144, aMax = 0, bMax = 0; 

    for (int i = 0; i < 1000000000; i++) { 
     int t = a^i; 
     if (t > aMax) { 
      aMax = t; 
     } 
     int u = b^i; 
     if (u > bMax) { 
      bMax = u; 
     } 
    } 
    System.out.println(aMax); 
    System.out.println(bMax); 
} 
1

在我看來,在一個循環的情況下,JIT 可能選擇做循環展開,結果表現稍好

1

你使用-server?如果不是,你應該 - 客戶JIT不是可預測的,也不是好的。如果您真的對究竟發生了什麼感興趣,可以使用UnlockDiagnostic + LogCompilation來檢查在兩種情況下(一直到生成的程序集)應用的優化方式。不管你是否做了熱身,無論你是否爲同一個JVM運行一次或多次測試,也不管你做了幾次運行(不同的JVM)。無論您是考慮最佳,平均還是中位時間,您是否會拋出異常值?

這裏是編寫Java微基準測試的主題一個很好的鏈接:http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html

編輯:還有一個microbenchmarking尖,謹防上的堆棧上替換:http://www.azulsystems.com/blog/cliff/2011-11-22-what-the-heck-is-osr-and-why-is-it-bad-or-good

+0

感謝您提供微基準測試的鏈接。我的代碼中沒有循環熱身。結果2秒/ 4秒是從相同的JVM獲得的,這是許多試驗的平均值。我也沒有使用-server標誌。 – Bala

2

總之,CPU可以並行執行合併循環中的指令,使性能提高一倍。

它也有可能第二個循環未被有效優化。這是因爲第一個循環將觸發整個方法進行編譯,第二個循環將被編譯爲沒有任何可能擾亂第二個循環時序的度量標準。我會把每個循環放在一個單獨的方法中,以確保不是這種情況。

CPU可以並行執行大量的獨立操作(depth 10 on Pentium III and 20 in the Xeon)。它試圖並行執行的一個操作是使用分支預測的分支,但是如果它幾乎每次都不採用同一分支。

我帶環懷疑展開你的循環看起來更像以下(在這種情況下,可能更多的循環展開)

for (int i = 0; i < 1000000000; i += 2) { 
    // this first block is run almost in parallel 
    int t1 = a^i; 
    int t2 = b^i; 
    int t3 = a^(i+1); 
    int t4 = b^(i+1); 
    // this block run in parallel 
    if (t1 > aMax) aMax = t1;  
    if (t2 > bMax) bMax = t2;  
    if (t3 > aMax) aMax = t3;  
    if (t4 > bMax) bMax = t4;  
}