2012-01-19 46 views
12

我正在玩一些代碼,計算計算某些Java代碼以獲得某些Java功能的效率或低效率所需的時間。這樣做,我現在卡住了一些非常奇怪的效果,我無法解釋自己。也許你的某個人可以幫助我理解它。Java效率

public class PerformanceCheck { 

public static void main(String[] args) { 
    List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>(); 

    int maxTimes = 1000000000; 

    for (int i=0;i<10;i++) { 
     long time = System.currentTimeMillis(); 

     for (int times=0;times<maxTimes;times++) { 
      // PERFORMANCE CHECK BLOCK START 

      if (removeList.size() > 0) { 
       testFunc(3); 
      } 

      // PERFORMANCE CHECK BLOCK END 
     } 

     long timeNow = System.currentTimeMillis(); 
     System.out.println("time: " + (timeNow - time)); 
    } 
} 

private static boolean testFunc(int test) { 
    return 5 > test; 
} 

} 

開始這導致在一個相對長的計算時間(記住removeList是空的,所以testFunc甚至不叫):

time: 2328 
time: 2223 
... 

雖然更換removeList.size的組合的任何東西()> 0和testFunc(3)都有更好的結果。例如:

... 
if (removeList.size() == 0) { 
    testFunc(3); 
} 
... 

結果(testFunc被稱爲每一次):

time: 8 
time: 7 
time: 0 
time: 0 

即使調用兩個功能獨立於在較低計算時間彼此的結果:

... 
if (removeList.size() == 0); 
    testFunc(3); 
... 

結果:

time: 6 
time: 5 
time: 0 
time: 0 
... 

在我最初的例子中,只有這個特定的組合需要很長時間。這令我很不快,我真的很想理解它。這有什麼特別之處?

謝謝。

增加:

在第一個例子

if (removeList.size() > 0) { 
       testFunc(times); 
} 

別的東西改變testFunc(),像

private static int testFunc2(int test) { 
    return 5*test; 
} 

會導致快速再之中。

+0

而且你不止一次運行這個測試,並且以不同的順序對吧?順序應該不重要,但只是爲了確保。另外,你是否得到(約)與nanoTime相同的結果如下所示? – prelic

+5

這種微型基準測試是一個非常糟糕的主意。也意識到JIT可以在任意時間做出各種決定,並完全優化您的代碼,只要它意識到實際上沒有發生任何事情。 –

+1

您應該使用[System.nanoTime()](http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#nanoTime%28%29)在Java中測量代碼執行。它更精確。更多討論[在這個問題](http://stackoverflow.com/questions/351565/system-currenttimemillis-vs-system-nanotime) – paislee

回答

0

由於編譯器非常聰明,因此這些基準很難實現。一個猜測:由於testFunc()的結果被忽略,編譯器可能會完全優化它。添加計數器,像

if (testFunc(3)) 
    counter++; 

而且,只爲徹底性,在年底做System.out.println(counter)

+0

但是,這會使第一個版本更快,但事實並非如此。 –

+2

可能是按@prelic建議的順序。出於某種原因,JIT並沒有第一次優化掉這個電話,而是第二次計算出來。 – user949300

+0

我確實在本地運行它,它不會改變事情。 –

3

這真是令人驚訝。生成的字節碼除條件外相同,即ifleifne

如果您關閉JIT -Xint,結果將更加明智。第二個版本慢了2倍。所以這與JIT優化有關。

我認爲它可以在第二種情況下優化出支票,但不是第一種情況(無論出於何種原因)。即使它意味着它執行了函數的工作,但是錯過了條件會使事情變得更快。它避免了管道堵塞等等。

+0

事情是,它也取決於testFunc()。第一個版本的testFunc()與其他任何東西交換,例如: \t private static int testFunc2(int test){ \t \t return 5 * test; \t} 這將導致較低的計算時間。 –

1

那麼,我很高興沒有處理Java性能優化。我用Java JDK 7 64位自己試了一下。結果是任意的;)。在我使用的列表中,或者如果在進入循環之前緩存size()的結果,這沒有什麼區別。同樣完全消除測試函數幾乎沒有區別(所以它不能作爲分支預測命中)。 優化標誌提高了性能,但是是任意的。

這裏唯一合乎邏輯的結果是JIT編譯器有時能夠優化掉語句(這並不難),但它看起來相當隨意。我喜歡C++這樣的語言的許多原因之一,其行爲至少是確定性的,即使它有時是任意的。

BTW在最新的Eclipse一樣,它始終是在Windows上運行通過IDE的「運行」(沒有調試)這個代碼是不是從控制檯運行它慢10倍,這麼多關於...

+0

確定性的,但任意的 –

1

當運行時編譯器可以計算testFunc評估爲一個常量,我相信它不評估循環,這就解釋了加速。

當條件是removeList.size() == 0函數testFunc(3)被評估爲常量。當條件爲removeList.size() != 0時,內部代碼永遠不會被評估,因此無法加速。您可以按如下修改代碼:

for (int times = 0; times < maxTimes; times++) { 
      testFunc(); // Removing this call makes the code slow again! 
      if (removeList.size() != 0) { 
       testFunc(); 
      } 
     } 

private static boolean testFunc() { 
    return testFunc(3); 
} 

testFunc()最初不叫,運行時編譯器不知道,testFunc()計算爲一個常數,因此它不能改善循環。

private static int testFunc2(int test) { 
    return 5*test; 
} 

某些功能編譯器可能試圖預優化(執行前),但顯然不是一個參數的情況下被傳遞在爲一個整數和在條件評估。

你基準返回時間像

time: 107 
time: 106 
time: 0 
time: 0 
... 

暗示,它需要2次迭代外環路爲運行時編譯器來完成優化。編譯-server標誌可能會返回基準中的所有0。

2

雖然與這個問題沒有直接關係,但這是如何正確使用Caliper對代碼進行基準測試的。下面是您的代碼的修改版本,以便它可以與Caliper一起運行。內部循環必須修改一些,以便虛擬機不會優化它們。在意識到沒有任何事情發生的時候,這是驚人的聰明。

在對Java代碼進行基準測試時,也存在很多細微差別。我寫了一些我在Java Matrix Benchmark遇到的問題,比如過去的歷史如何影響當前的結果。您將通過使用Caliper避免許多這些問題。

  1. http://code.google.com/p/caliper/
  2. Benchmarking issues with Java Matrix Benchmark

    public class PerformanceCheck extends SimpleBenchmark { 
    
    public int timeFirstCase(int reps) { 
        List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>(); 
        removeList.add(new PerformanceCheck()); 
        int ret = 0; 
    
        for(int i = 0; i < reps; i++) { 
         if (removeList.size() > 0) { 
          if(testFunc(i)) 
           ret++; 
         } 
        } 
    
        return ret; 
    } 
    
    public int timeSecondCase(int reps) { 
        List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>(); 
        removeList.add(new PerformanceCheck()); 
        int ret = 0; 
    
        for(int i = 0; i < reps; i++) { 
         if (removeList.size() == 0) { 
          if(testFunc(i)) 
           ret++; 
         } 
        } 
    
        return ret; 
    } 
    
    private static boolean testFunc(int test) { 
        return 5 > test; 
    } 
    
    public static void main(String[] args) { 
        Runner.main(PerformanceCheck.class, args); 
    } 
    } 
    

OUTPUT:

0% Scenario{vm=java, trial=0, benchmark=FirstCase} 0.60 ns; σ=0.00 ns @ 3 trials 
50% Scenario{vm=java, trial=0, benchmark=SecondCase} 1.92 ns; σ=0.22 ns @ 10 trials 

benchmark ns linear runtime 
FirstCase 0.598 ========= 
SecondCase 1.925 ============================== 

vm: java 
trial: 0 
+0

+1我不認爲問題是如何微觀基準,但解釋觀察到的行爲。但是你一路打到了;這是JIT以意想不到的方式優化某些東西。 –

+0

是的,我同意。我想我可能會指出正確的方式,因爲即使在解釋代碼之後,由於基準設置的方式,仍然會出現奇怪的波動。 –

+0

感謝您與卡尺的提示。我將來會使用它。問題仍然是Java爲什麼會這樣。在你的例子中,你通過添加一個對象到列表來切換行爲。現在,'timeSecondCase'的if子句返回false。而且它仍然是testFunc()不被調用的較慢版本。 –

1

時代在每次迭代不切實際的快。這意味着JIT已經檢測到你的代碼沒有做任何事情並且已經消除了它。微妙的變化可能會混淆JIT,並且它不能確定代碼沒有做任何事情,並且需要一些時間。

如果您更改測試以做一些有用的事情,差異將會消失。