2011-03-09 72 views
14

我有這樣的代碼,該代碼測試Calendar.getInstance().getTimeInMillis() VS System.currentTimeMilli()如何確保沒有JVM和編譯器優化出現

long before = getTimeInMilli(); 
for (int i = 0; i < TIMES_TO_ITERATE; i++) 
{ 
    long before1 = getTimeInMilli(); 
    doSomeReallyHardWork(); 
    long after1 = getTimeInMilli(); 
} 
long after = getTimeInMilli(); 
System.out.println(getClass().getSimpleName() + " total is " + (after - before)); 

我想,以確保沒有JVM或編譯器的優化情況,所以測試將是有效的,實際上會表現出差異。

如何確定?

編輯:我改變了代碼示例,所以它會更清晰。我在這裏檢查的是在不同的實現中調用getTimeInMilli()需要多少時間 - Calendar vs System。

回答

24

我認爲你需要禁用JIT。添加到您的運行命令下一個選項:

-Djava.compiler=NONE 
+6

謝謝;這正是我需要讓Java「準確」展示O(1),O(n)和O(n^2)算法之間效率差異的一個課堂演示:-)在Eclipse中,您可以將在運行配置/參數/虛擬機設置。 – 2014-01-02 00:13:32

4

很抱歉,但你正在嘗試做的意義不大。

如果關閉JIT編譯,那麼您只需要測量在JIT編譯關閉的情況下調用該方法需要多長時間。這不是有用的信息......因爲它告訴你什麼時候JIT編譯打開時會發生什麼。

JIT開啓和關閉之間的時間可能因巨大因素而不同。 JIT關閉後,您不可能想要在生產中運行任何內容。

更好的方法來做到這一點:

long before1 = getTimeInMilli(); 
for (int i = 0; i < TIMES_TO_ITERATE; i++) { 
    doSomeReallyHardWork(); 
} 
long after1 = getTimeInMilli(); 

...和/或使用納秒時鐘。


如果你試圖測量調用getTimeInMillis()兩個版本所花費的時間,那麼我不明白您的通話點doSomeReallyHardWork()。一個更靈敏的基準將是這樣的:

public long test() { 
    long before1 = getTimeInMilli(); 
    long sum = 0; 
    for (int i = 0; i < TIMES_TO_ITERATE; i++) { 
     sum += getTimeInMilli(); 
    } 
    long after1 = getTimeInMilli(); 
    System.out.println("Took " + (after - before) + " milliseconds"); 
    return sum; 
} 

......並且調用多次,直到打印時間穩定。無論哪種方式,我的主要觀點仍然存在,轉向JIT編譯和/或優化將意味着你正在測量一些對知道無用的東西,而不是你真正想要發現的東西。 (除非,那就是,你打算在JIT關閉的情況下運行你的應用程序,我覺得很難相信......)

+0

你可能想念我。我想測試的是getTimeInMilli()的調用需要多少。一旦使用日曆和一次使用系統。 – oshai 2011-03-09 07:17:49

11

想要優化發生,因爲它會在現實生活中 - 如果JVM沒有像在真實情況下那樣對其進行優化,測試將無效。

但是,如果您要確保JVM不會刪除可能會認爲無操作的呼叫,其中一種選擇是使用結果 - 因此,如果您重複呼叫System.currentTimeMillis(),則可以總結所有返回值,然後在最後顯示總和。

請注意,儘管您可能仍有一些偏見 - 例如,如果JVM可以便宜地確定自上次調用System.currentTimeMillis()以來只有很少的時間已經過去,那麼它可以使用緩存值。我不是說實際上是這裏的情況,但這是你需要考慮的事情。最終,基準只能真正測試你給他們的負載。

另一個要考慮的事情是:假設你想對代碼運行的真實世界情況建模lot,你應該在採取任何時機之前運行代碼 - 因爲Hotspot JVM會逐步優化,並且可能你關心重度優化的版本,並且不要想要測量JITting的時間和代碼的「慢」版本。

斯蒂芬提到的,你幾乎可以肯定應採取定時環......不要忘記真正使用的結果...

2

你正在做什麼樣子的標杆,你可以閱讀Robust Java benchmarking以獲得一些有關如何使其正確的良好背景。簡而言之,您不需要關閉它,因爲它不會在生產服務器上發生的情況。相反,您需要了解「真實」時間估算/性能的可能性。優化之前,您需要「熱身」你的代碼,它看起來像:

// warm up 
for (int j = 0; j < 1000; j++) { 
    for (int i = 0; i < TIMES_TO_ITERATE; i++) 
    { 
     long before1 = getTimeInMilli(); 
     doSomeReallyHardWork(); 
     long after1 = getTimeInMilli(); 
    } 
} 

// measure time 
long before = getTimeInMilli(); 
for (int j = 0; j < 1000; j++) { 
    for (int i = 0; i < TIMES_TO_ITERATE; i++) 
    { 
     long before1 = getTimeInMilli(); 
     doSomeReallyHardWork(); 
     long after1 = getTimeInMilli(); 
    } 
} 
long after = getTimeInMilli(); 

System.out.prinltn("What to expect? " + (after - before)/1000); // average time 

當我們衡量我們的代碼中使用這種方法的性能,它給我們帶來更多更小的實時我們的代碼需要工作。更好地測量分離方法中的代碼:

public void doIt() { 
    for (int i = 0; i < TIMES_TO_ITERATE; i++) 
    { 
     long before1 = getTimeInMilli(); 
     doSomeReallyHardWork(); 
     long after1 = getTimeInMilli(); 
    } 
} 

// warm up 
for (int j = 0; j < 1000; j++) { 
    doIt() 
} 

// measure time 
long before = getTimeInMilli(); 
for (int j = 0; j < 1000; j++) { 
    doIt(); 
} 
long after = getTimeInMilli(); 

System.out.prinltn("What to expect? " + (after - before)/1000); // average time 

第二種方法更精確,但它也取決於VM。例如。 HotSpot可以執行"on-stack replacement",這意味着如果方法的某些部分經常執行,它將被VM優化,並且在執行方法時,舊版本的代碼將與優化後的代碼進行交換。當然,它需要VM端的額外操作。 JRockit沒有這樣做,優化後的代碼版本只有在再次執行該方法時纔會使用(所以沒有'運行時'優化...我的意思是在我的第一個代碼示例中,所有的時間都會執行舊代碼...除了doSomeReallyHardWork內部 - 它們不屬於這種方法,所以優化將很好地工作)。

更新:有問題的代碼,而我回答被編輯;)