2012-05-11 44 views
4

在這個問題中,我想問一下關於如何測試Java代碼性能的知識。通常的方法沿着這些線路的工作原理:有效的性能測量

long start = System.nanoTime(); 

for(int i=0; i<SOME_VERY_LARGE_NUMBER; i++) { 
    ...do something... 
} 

long duration = System.nanoTime() - start; 
System.out.println("Performance: " 
    + new BigDecimal(duration).divide( 
     new BigDecimal(SOME_VERY_LARGE_NUMBER, 3, RoundingMode.HALF_UP))); 

「優化」版本的移動呼叫System.nanoTime()進入死循環,因爲System.nanoTime()增長的誤差需要更長的時間(和方式難以預料在運行時的行爲)比i ++和比較。

我的批評是:

這給了我的平均運行時間,但這個值取決於一個因素,我並不真正感興趣的:如系統負載,同時測試迴路正在運行或跳躍時,JIT/GC踢。

在大多數情況下這種方法不會(好多)更好嗎?

  1. 運行代碼來衡量往往不夠給力JIT編譯
  2. 運行在一個循環的代碼和測量的執行時間。記住最小值並在此值穩定時中止循環。

我的理由是我通常想知道某些代碼可以有多快(下界)。由於外部事件(鼠標移動,顯卡中斷,因爲桌面上有模擬時鐘,交換網絡數據包......),任何代碼都可能變得任意慢,但大多數情況下,我只想知道速度有多快我的代碼可以在完美的情況下。因爲我不必在幾秒鐘或幾分鐘內運行代碼(以消除不需要的效果),它還會使性能測量速度快得多。

有人可以確認/揭穿這個?

+0

我經常做類似的事情,但手動。我多次重複測試,並以最低的數字進行測試。我有和你一樣的推理。當然,如果您的替代解決方案在GC的壓力數量上有所不同,那麼不應該考慮這個因素。 –

+0

爲什麼「優化」版本將*調入*循環?沒有編譯器會這樣做。 –

+0

@IraBaxter:我的意思是人類「優化」以獲得更好的「精確」(因爲新的測量不會計算循環)。但像往常一樣,這裏的直覺是錯誤的。 –

回答

1

我會運行SOME_VERY_LARGE_NUMBER循環50次並計算最佳執行循環的平均值。這是通常在其他基準測試中完成的,而不僅僅是代碼微基準。

我還認爲GC引發的性能問題通常是代碼的一部分。你可能不應該將GC排除在外,因爲分配大量內存的例程應該爲此付出一定的價格基準。如果您選擇了足夠大的SOME_VERY_LARGE_NUMBER,則建議的方法將以每次通話的平均GC成本爲因子。

關於您的建議:所有計時器都具有有限的精度,所以很可能是在零時鐘週期內完成一個短程序。這意味着你的算法會發現程序在零時間運行。這顯然是不正確的。

+1

重新計時器精度:預熱後,我運行測量方法幾次,直到我發現運行它的頻率以獲得運行時間> 10'000ns。 –

3

我想你建議是相當合理的,有一些調整:

1)我將報告中位數 - 或一串百分位數的 - 而不是最低。如果你的代碼給垃圾回收器帶來了很大的壓力,那麼簡單地取最小值可能很容易失敗(這隻需要一次迭代就可以適應兩次連續的GC暫停)。

2)在許多情況下,測量CPU時間而不是掛鐘時間是有意義的。這需要處理其他代碼在同一個框上運行的一些影響。

3)一些基準測試工具使用兩個級別的循環:內部循環重複執行操作,外部循環查看內部循環之前和之後的時鐘。然後將觀察結果彙總在外部循環的迭代中。

最後,下面給出的JVM的具體問題一個很好的概述,以做到心中有數:How do I write a correct micro-benchmark in Java?

2

可以使用-XX:CompileThreshold JVM選項來指定JIT踢然後你就可以「熱身」你在運行定時循環之前通過運行大於CompileThreshold的循環進行測試。