2012-10-12 22 views
13

我正在使用以下代碼來測試try塊的速度。令我驚訝的是,try塊使其更快。爲什麼?爲什麼添加一個try塊會使程序更快?

public class Test { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     int i; 
     long l; 
     Test t = new Test(); 

     l = System.currentTimeMillis(); 
     t.reset(); 
     for (i = 1; i < 100000000; i++) { 
      t.method1(i); 
     } 
     l = System.currentTimeMillis() - l; 
     System.out.println("method1 took " + l + " ms, result was " 
       + t.getValue()); 

     // using a try block 
     l = System.currentTimeMillis(); 
     t.reset(); 
     for (i = 1; i < 100000000; i++) { 
      try { 
       t.method1(i); 
      } catch (Exception e) { 

      } 
     } 

     l = System.currentTimeMillis() - l; 
     System.out.println("method1 with try block took " + l + " ms, result was " 
       + t.getValue()); 
    } 
} 

我的機器正在運行64位Windows 7和64位JDK7。我得到了以下結果:

method1 took 914 ms, result was 2 
method1 with try block took 789 ms, result was 2 

而且我已經運行的代碼很多次,每一次我得到了幾乎相同的結果。

更新:

這裏是運行測試在MacBook Pro上十倍的結果,爪哇6的try-catch使得該方法更快,相同的窗口。

method1 took 895 ms, result was 2 
method1 with try block took 783 ms, result was 2 
-------------------------------------------------- 
method1 took 943 ms, result was 2 
method1 with try block took 803 ms, result was 2 
-------------------------------------------------- 
method1 took 867 ms, result was 2 
method1 with try block took 745 ms, result was 2 
-------------------------------------------------- 
method1 took 856 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 862 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 859 ms, result was 2 
method1 with try block took 765 ms, result was 2 
-------------------------------------------------- 
method1 took 937 ms, result was 2 
method1 with try block took 767 ms, result was 2 
-------------------------------------------------- 
method1 took 861 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 858 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 858 ms, result was 2 
method1 with try block took 749 ms, result was 2 
+1

這是出問題的範圍,但您應該使用'System.nanoTime'來比較數據。閱讀[System.currentTimeMillis vs System.nanoTime](http://stackoverflow.com/q/351565/1065197)。 –

+2

@RahulAgrawal我交換了代碼並得到了相同的結果。 –

+2

我對OP的代碼做了很多測試,我確認了他的發現。 –

回答

2

我做了一些試驗。

首先,我完全確認OP的發現。即使刪除第一個循環,或將異常更改爲完全不相關的循環,只要不通過重新拋出異常來添加分支,try catch就會使代碼更快。如果它真的需要捕獲一個異常(如果循環從0開始而不是1開始),代碼仍然更快。

我的「解釋」是JIT是野生優化機器,有時他們的表現會比其他一些時候更好,這種方式在JIT級別沒有非常具體的研究時通常是無法理解的。有很多可能的事情可以改變(例如使用寄存器)。

This is globally what was found in a very similar case with a C# JIT.

在任何情況下,Java正在try-catch代碼進行了優化。由於總是有可能出現異常,所以添加try-catch並不會增加太多分支,所以沒有發現第二個循環比第一個循環更長並不奇怪。

19

如果在同一方法中有多個長時間運行的循環,則可以在第二個循環中觸發整個方法的優化併產生不可預知的結果。避免這種情況的一種方法是;

  • 給每個循環它自己的方法
  • 運行測試多次查詢的結果重新可生產
  • 運行測試爲2 - 10秒。

你會看到一些變化,有時結果是不確定的。即變化高於差異。

public class Test { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     Test t = new Test(); 
     for (int i = 0; i < 5; i++) { 
      testWithTryCatch(t); 
      testWithoutTryCatch(t); 
     } 
    } 

    private static void testWithoutTryCatch(Test t) { 
     t.reset(); 
     long l = System.currentTimeMillis(); 
     for (int j = 0; j < 10; j++) 
      for (int i = 1; i <= 100000000; i++) 
       t.method1(i); 

     l = System.currentTimeMillis() - l; 
     System.out.println("without try/catch method1 took " + l + " ms, result was " + t.getValue()); 
    } 

    private static void testWithTryCatch(Test t) { 
     t.reset(); 
     long l = System.currentTimeMillis(); 
     for (int j = 0; j < 10; j++) 
      for (int i = 1; i <= 100000000; i++) 
       try { 
        t.method1(i); 
       } catch (Exception ignored) { 
       } 

     l = System.currentTimeMillis() - l; 
     System.out.println("with try/catch method1 took " + l + " ms, result was " + t.getValue()); 
    } 
} 

打印

with try/catch method1 took 9723 ms, result was 2 
without try/catch method1 took 9456 ms, result was 2 
with try/catch method1 took 9672 ms, result was 2 
without try/catch method1 took 9476 ms, result was 2 
with try/catch method1 took 8375 ms, result was 2 
without try/catch method1 took 8233 ms, result was 2 
with try/catch method1 took 8337 ms, result was 2 
without try/catch method1 took 8227 ms, result was 2 
with try/catch method1 took 8163 ms, result was 2 
without try/catch method1 took 8565 ms, result was 2 

從這些結果,它可能會出現與try/catch語句是稍微慢一些,但並非總是如此。在Windows 7

運行,至強E5450與Java 7的更新7.

+0

在我的機器上,'testWithoutTryCatch()'總是會從第三次變快。 –

+0

你有哪些Java 7的更新? –

+0

64位Java 7更新7 - 「1.7.0_07」。 –

5

我試過了用卡尺微基準,我真的看不出差別。

下面的代碼:

public class TryCatchBenchmark extends SimpleBenchmark { 

    private int value; 

    public void setUp() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public void timeWithoutTryCatch(int reps) { 
     for (int i = 1; i < reps; i++) { 
      this.method1(i); 
     } 
    } 

    public void timeWithTryCatch(int reps) { 
     for (int i = 1; i < reps; i++) { 
      try { 
       this.method1(i); 
      } catch (Exception ignore) { 
      } 
     } 
    } 

    public static void main(String[] args) { 
     new Runner().run(TryCatchBenchmark.class.getName()); 
    } 
} 

這裏是結果:

 
0% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,23 ns; σ=0,03 ns @ 3 trials 
50% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,13 ns; σ=0,03 ns @ 3 trials 

     benchmark ns linear runtime 
WithoutTryCatch 8,23 ============================== 
    WithTryCatch 8,13 ============================= 

如果我換的功能順序(讓他們以相反的順序執行)的結果是:

 
0% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,21 ns; σ=0,05 ns @ 3 trials 
50% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,14 ns; σ=0,03 ns @ 3 trials 

     benchmark ns linear runtime 
    WithTryCatch 8,21 ============================== 
WithoutTryCatch 8,14 ============================= 

我會說他們基本上是一樣的。

+0

+1使用Caliper Microbenchmark。 – Ryan

1

爲了避免任何可以由JVM和OS執行的隱藏優化或緩存,我首先開發了兩個種子java程序TryBlockNoTryBlock,其中它們的區別在於使用try塊還是不使用try塊。這兩個種子程序將用於生成不同的程序來禁止JVM或OS執行隱藏優化。在每個測試中,都會生成並編譯一個新的Java程序,並重複測試10次。

根據我的實驗,沒有嘗試塊的運行平均需要9779.3 ms,而使用try塊運行需要9775.9ms:平均運行時間差3.4ms(或0.035%),可以將其視爲噪聲。這表明使用void try塊(通過void,我指的不是空指針異常,不存在任何可能的異常)或者對運行時間沒有影響。

測試運行在相同的linux機器上(cpu 2392MHz)和Java版本「1.6.0_24」下。

下面是我基於種子計劃生成測試程序腳本:

for i in `seq 1 10`; do 
    echo "NoTryBlock$i" 
    cp NoTryBlock.java NoTryBlock$i.java 
    find . -name "NoTryBlock$i.java" -print | xargs sed -i "s/NoTryBlock/NoTryBlock$i/g"; 
    javac NoTryBlock$i.java; 
    java NoTryBlock$i 
    rm NoTryBlock$i.* -f; 
done 

for i in `seq 1 10`; do 
    echo "TryBlock$i" 
    cp TryBlock.java TryBlock$i.java 
    find . -name "TryBlock$i.java" -print | xargs sed -i "s/TryBlock/TryBlock$i/g"; 
    javac TryBlock$i.java; 
    java TryBlock$i 
    rm TryBlock$i.* -f; 
done 

這裏有種子的程序,首先是NoTryBlock.java

import java.util.*; 
import java.lang.*; 

public class NoTryBlock { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     int i, j; 
     long l; 
     NoTryBlock t = new NoTryBlock(); 

     // using a try block 
     l = System.currentTimeMillis(); 
     t.reset(); 
     for (j = 1; j < 10; ++j) { 
      for (i = 1; i < 100000000; i++) { 
       t.method1(i); 
      } 
     } 
     l = System.currentTimeMillis() - l; 
     System.out.println(
      "method1 with try block took " + l + " ms, result was " 
       + t.getValue()); 
    } 
} 

二是TryBlock.java,它使用方法函數調用上的一個嘗試塊:

import java.util.*; 
import java.lang.*; 

public class TryBlock { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     int i, j; 
     long l; 
     TryBlock t = new TryBlock(); 

     // using a try block 
     l = System.currentTimeMillis(); 
     t.reset(); 
     for (j = 1; j < 10; ++j) { 
      for (i = 1; i < 100000000; i++) { 
      try { 
       t.method1(i); 
      } catch (Exception e) { 
      } 
      } 
     } 
     l = System.currentTimeMillis() - l; 
     System.out.println(
      "method1 with try block took " + l + " ms, result was " 
       + t.getValue()); 
    } 
} 

下面是我的兩個種子計劃的差異,你可以除了類名看,try塊是他們唯一的區別是:

$ diff TryBlock.java NoTryBlock.java 
4c4 
<  public class TryBlock { 
--- 
>  public class NoTryBlock { 
27c27 
<    TryBlock t = new TryBlock(); 
--- 
>    NoTryBlock t = new NoTryBlock(); 
34d33 
<     try { 
36,37d34 
<     } catch (Exception e) { 
<     } 
42c39 
<     "method1 with try block took " + l + " ms, result was " 
--- 
>     "method1 without try block took " + l + " ms, result was " 

下面是輸出:

method1 without try block took,9732,ms, result was 2 
method1 without try block took,9756,ms, result was 2 
method1 without try block took,9845,ms, result was 2 
method1 without try block took,9794,ms, result was 2 
method1 without try block took,9758,ms, result was 2 
method1 without try block took,9733,ms, result was 2 
method1 without try block took,9763,ms, result was 2 
method1 without try block took,9893,ms, result was 2 
method1 without try block took,9761,ms, result was 2 
method1 without try block took,9758,ms, result was 2 

method1 with try block took,9776,ms, result was 2 
method1 with try block took,9751,ms, result was 2 
method1 with try block took,9767,ms, result was 2 
method1 with try block took,9726,ms, result was 2 
method1 with try block took,9779,ms, result was 2 
method1 with try block took,9797,ms, result was 2 
method1 with try block took,9845,ms, result was 2 
method1 with try block took,9784,ms, result was 2 
method1 with try block took,9787,ms, result was 2 
method1 with try block took,9747,ms, result was 2 
相關問題