2010-07-18 159 views
10

我和我的另一位開發人員最近從工作中的Core 2 Duo機器遷移到新的Core 2 Quad 9505;兩者都使用JDK 1.6.0_18運行Windows XP SP3 32位。爲什麼我的System.nanoTime()被破壞?

一旦這樣做,由於什麼似乎是從System.nanoTime()返回的荒謬的值,我們的一些時間/統計/指標聚合代碼的自動單元測試立即開始失敗。

顯示此問題,可靠地,我的機器上

測試代碼是:

import static org.junit.Assert.assertThat; 

import org.hamcrest.Matchers; 
import org.junit.Test; 

public class NanoTest { 

    @Test 
    public void testNanoTime() throws InterruptedException { 
    final long sleepMillis = 5000; 

    long nanosBefore = System.nanoTime(); 
    long millisBefore = System.currentTimeMillis(); 

    Thread.sleep(sleepMillis); 

    long nanosTaken = System.nanoTime() - nanosBefore; 
    long millisTaken = System.currentTimeMillis() - millisBefore; 

    System.out.println("nanosTaken="+nanosTaken); 
    System.out.println("millisTaken="+millisTaken); 

    // Check it slept within 10% of requested time 
    assertThat((double)millisTaken, Matchers.closeTo(sleepMillis, sleepMillis * 0.1)); 
    assertThat((double)nanosTaken, Matchers.closeTo(sleepMillis * 1000000, sleepMillis * 1000000 * 0.1)); 
    } 

} 

典型輸出:

millisTaken=5001 
nanosTaken=2243785148 

運行它100X產率33%,實際60%之間的納米結果睡覺時間;通常在40%左右。我知道Windows中定時器的精確性的弱點,並且已經閱讀了相關的線程,比如Is System.nanoTime() consistent across threads?,但是我的理解是System.nanoTime()的目的正是爲了我們使用它的目的: - 測量已用時間;比currentTimeMillis()更準確。

有誰知道它爲什麼會返回這樣瘋狂的結果嗎?這可能是一個硬件架構問題(唯一主要的改變是這臺機器上的CPU /主板)? Windows HAL與我目前的硬件有問題? JDK問題?我應該放棄nanoTime()嗎?我應該在什麼地方記錄一個錯誤,或者有關我如何進一步調查的建議?

UPDATE 19/07 03:15 UTC:在嘗試下面的finnw測試用例之後,我做了一些更多的谷歌搜索,碰到一些條目,如bugid:6440250。這也讓我想起了週五晚些時候我注意到的其他一些奇怪的行爲,在那裏ping回來了。所以我在我的boot.ini中加入了/usepmtimer,現在所有的測試都按照預期運行,並且我的ping也是正常的。

我有點困惑,爲什麼這仍然是一個問題,雖然;從我的閱讀中,我認爲TSC vs PMT問題在Windows XP SP3中得到了很大程度的解決。難道是因爲我的機器最初是SP2,並且已經修補到SP3而不是最初安裝爲SP3?我現在也想知道我是否應該安裝像MS KB896256那樣的補丁。也許我應該將這與企業桌面構建團隊一起考慮?

+0

你得到一個完全新的機器或者是在當前計算機升級保留舊的Windows安裝? – 2010-07-18 09:00:58

+0

全新機器;在企業標準構建中重建。 – Chad 2010-07-18 09:02:42

+0

工作正常,我的Windows 7下的64位最新的JDK 6 – TofuBeer 2010-07-18 09:02:48

回答

5

的問題,通過加入/usepmtimerC的最終解決(約nanoTime()在多核系統的適用性一些開放的懷疑!):\ boot.ini的串;迫使Windows使用電源管理計時器而不是TSC。這是一個懸而未決的問題,爲什麼我需要這樣做,因爲我在XP SP3上,因爲我知道這是默認的,但也許是因爲我的機器被修補到SP3的方式。

+0

哇 - 我很高興我發現這篇文章 - 有一個客戶站點,ScheduledExecutorService完全離開軌道(剩下的時間直到下一個計劃的任務將隨機進入錯誤的方向)。 – 2014-05-22 00:56:31

+0

很高興幫助某人!我失去了很多時間來這:)我也想象到,XP現在變得越來越少,現在它已經被正確使用(特別是由開發人員自己),能夠快速診斷舊客戶套件上的這些模糊問題將逐漸減少.... – Chad 2014-06-27 11:33:07

1

您可能想要閱讀其他堆棧溢出問題的答案:Is System.nanoTime() completely useless?

總之,看起來nanoTime依賴於操作系統定時器,這些定時器可能會受多核CPU的影響。因此,nanoTime可能對OS和CPU的某些組合沒有用處,在將其用於可能在多個目標平臺上運行的可移植Java代碼中時應該小心。網上似乎有很多關於這個問題的抱怨,但對於一個有意義的替代方案沒有多少共識。

+2

這不是一個完全準確的總結。 System.nanoTime依賴於特定於操作系統的定時器。過去有一兩個錯誤,例如在Windows的Athlon 64芯片上,然而在大多數系統上,你可以依靠nanoTime工作得很好。我將它用於多核遊戲中的動畫和時序,並且從未遇到任何問題。 – mikera 2010-07-18 10:05:02

+0

感謝您的澄清mikera。我已經更新了我的答案(希望)可以提高準確性。 – Tom 2010-07-18 10:23:54

+0

謝謝湯姆。正如我在上面更新的問題中提到的,我設法通過強制使用PMT來恢復「正常」行爲。我想我仍然有點擔心這是否會按照我們預期的跨越多核的方式行事。是的,如果沒有一個有意義的替代方案(缺少「回到currentTimeMillis」),很難知道如何繼續下去! – Chad 2010-07-19 03:25:10

2

在我的系統(Windows 7的64位,酷睿i7 980X):

nanosTaken=4999902563 
millisTaken=5001 

System.nanoTime()使用操作系統特定的電話,所以我希望您看到的錯誤在你的Windows /處理器組合。

+0

感謝mikera,它看起來像Windows使用的計時器風格,在我的Core 2 Quad上表現不正確。強制它使用電源管理定時器使其再次正常運行;但我不太明白爲什麼我必須這樣做! – Chad 2010-07-19 03:22:23

1

很難判斷這是一個bug還是隻是核心之間的正常計時器變化。

,你可以做一個實驗是用本地調用強制線程上的特定內核上運行。

此外,爲了排除電源管理效果,嘗試旋轉在一個循環中作爲替代sleep()

import com.sun.jna.Native; 
import com.sun.jna.NativeLong; 
import com.sun.jna.platform.win32.Kernel32; 
import com.sun.jna.platform.win32.W32API; 

public class AffinityTest { 

    private static void testNanoTime(boolean sameCore, boolean spin) 
    throws InterruptedException { 
     W32API.HANDLE hThread = kernel.GetCurrentThread(); 
     final long sleepMillis = 5000; 

     kernel.SetThreadAffinityMask(hThread, new NativeLong(1L)); 
     Thread.yield(); 
     long nanosBefore = System.nanoTime(); 
     long millisBefore = System.currentTimeMillis(); 

     kernel.SetThreadAffinityMask(hThread, new NativeLong(sameCore? 1L: 2L)); 
     if (spin) { 
      Thread.yield(); 
      while (System.currentTimeMillis() - millisBefore < sleepMillis) 
       ; 
     } else { 
      Thread.sleep(sleepMillis); 
     } 

     long nanosTaken = System.nanoTime() - nanosBefore; 
     long millisTaken = System.currentTimeMillis() - millisBefore; 

     System.out.println("nanosTaken="+nanosTaken); 
     System.out.println("millisTaken="+millisTaken); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     System.out.println("Sleeping, different cores"); 
     testNanoTime(false, false); 
     System.out.println("\nSleeping, same core"); 
     testNanoTime(true, false); 
     System.out.println("\nSpinning, different cores"); 
     testNanoTime(false, true); 
     System.out.println("\nSpinning, same core"); 
     testNanoTime(true, true); 
    } 

    private static final Kernel32Ex kernel = 
     (Kernel32Ex) Native.loadLibrary(Kernel32Ex.class); 

} 

interface Kernel32Ex extends Kernel32 { 
    NativeLong SetThreadAffinityMask(HANDLE hThread, NativeLong dwAffinityMask); 
} 

如果你得到同樣的核心,但2200ms取決於核心精選截然不同的結果(例如,5000毫秒在不同的核心上),這表明問題只是核心之間的自然計時器變化。

如果你從睡眠與紡紗得到非常不同的結果,但更可能是由於電源管理減慢時鐘。

如果沒有的四個結果都接近5000ms,那麼它可能是一個bug。

+0

謝謝finnw,這很有趣。我的結果是: 睡覺,不同的內核 nanosTaken = 2049217124 millisTaken = 4985 睡覺,相同的核心 nanosTaken = 1808868148 millisTaken = 4985 紡紗,不同的內核 nanosTaken = 5015172794 millisTaken = 5000 紡紗,相同的核心 nanosTaken = 5015295288 millisTaken = 5000 你覺得這意味着什麼用電源管理我的機器壞了? – Chad 2010-07-19 02:30:20

+0

在做了一些更多的閱讀之後,由您的測試觸發,我試着用boot.ini中的/ usepmtimer重啓我的機器。現在你的測試(和我的原始測試)表現正常。 我已經編輯了我的問題。我應該這樣做嗎? – Chad 2010-07-19 02:58:31

+0

它不一定是「破碎」,但很顯然,TSC不適​​合在您的機器上進行高精度定時,並且使用PM定時器可以獲得更好的結果。我認爲/ usepmtimer是XP SP3的默認設置,但您的結果表明不然。 – finnw 2010-07-19 10:15:11

相關問題