2010-07-18 121 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

相关问题