【问题标题】:Why is my System.nanoTime() broken?为什么我的 System.nanoTime() 坏了?
【发布时间】:2010-07-18 08:56:56
【问题描述】:

我自己和另一位开发人员最近从工作中的 Core 2 Duo 机器转移到了新的 Core 2 Quad 9505;都运行带有 JDK 1.6.0_18 的 Windows XP SP3 32 位。

这样做后,我们对一些计时/统计/指标聚合代码的一些自动化单元测试立即开始失败,因为 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

运行 100 倍会产生实际睡眠时间的 33% 到 60% 之间的纳米结果;不过通常在 40% 左右。

我了解 Windows 中计时器准确性方面的弱点,并阅读过像 Is System.nanoTime() consistent across threads? 这样的相关主题,但我的理解是 System.nanoTime() 正是为了我们使用它的目的而设计的:- 测量经过的时间;比 currentTimeMillis() 更准确。

有谁知道为什么它会返回如此疯狂的结果?这可能是硬件架构问题(唯一改变的主要是这台机器上的 CPU/主板)?我当前的硬件存在 Windows HAL 问题? JDK问题?我应该放弃 nanoTime() 吗?我应该在某处记录错误,还是对如何进一步调查有任何建议?

更新 19/07 03:15 UTC:在尝试了下面 finnw 的测试用例之后,我又进行了一些谷歌搜索,遇到了诸如 bugid:6440250 之类的条目。这也让我想起了周五晚些时候我注意到的其他一些奇怪的行为,其中 ping 恢复为负数。所以我将 /usepmtimer 添加到我的 boot.ini 中,现在所有测试都按预期运行。我的 ping 也正常。

我有点困惑为什么这仍然是一个问题;根据我的阅读,我认为 TSC 与 PMT 的问题在 Windows XP SP3 中得到了很大的解决。可能是因为我的机器原本是 SP2 的,并且被修补到 SP3 而不是最初安装为 SP3 吗?我现在也想知道我是否应该安装像MS KB896256 这样的补丁。也许我应该与公司桌面构建团队一起讨论这个问题?

【问题讨论】:

  • 您是买了一台全新的机器还是升级了当前的机器以保留旧的 Windows 安装?
  • 全新机器;在企业标准构建的基础上重建。
  • 在 Windows 7 64 位最新 JDK 6 下工作正常。
  • 如果有人在 2019 年或之后遇到此问题:这在自 OpenJDK 8u25 或 2019 年 2 月之前发布的任何版本以来的任何操作系统上都不是问题,模数未知的错误可能仍隐藏在某处.见 bugs.openjdk.java.net/browse/JDK-8045438

标签: java timestamp nanotime


【解决方案1】:

通过将 /usepmtimer 添加到我的 C:\boot. ini 字符串;强制 Windows 使用电源管理计时器而不是 TSC。鉴于我在 XP SP3 上,为什么我需要这样做,这是一个悬而未决的问题,因为我知道这是默认设置,但可能是由于我的机器被修补到 SP3 的方式。

【讨论】:

  • 哇 - 我很高兴我找到了这篇文章 - 有一个客户站点,其中 ScheduledExecutorService 完全脱离了轨道(直到下一个计划任务的剩余时间会随机走错方向)。
  • 很高兴它帮助了某人!我为此浪费了很多时间 :) 我还想象随着 XP 的使用越来越少,因为它已经正确 EOLed(尤其是开发人员自己),快速诊断旧客户工具包上此类模糊问题的能力将逐渐减少....
【解决方案2】:

在我的系统上(Windows 7 64 位,Core i7 980X):

nanosTaken=4999902563
millisTaken=5001

System.nanoTime() 使用特定于操作系统的调用,因此我希望您在 Windows/处理器组合中看到错误。

【讨论】:

  • 感谢 mikera,看起来 Windows 使用的计时器样式在我的 Core 2 Quad 上运行不正确。强制它使用电源管理定时器使其再次正常运行;但我不太明白为什么我必须这样做!
【解决方案3】:

您可能想阅读其他堆栈溢出问题的答案:Is System.nanoTime() completely useless?

总而言之,nanoTime 似乎依赖于可能会受到多核 CPU 存在影响的操作系统计时器。因此,nanoTime 在操作系统和 CPU 的某些组合上可能没有那么有用,在您打算在多个目标平台上运行的可移植 Java 代码中使用它时应该小心。网络上似乎有很多关于这个主题的抱怨,但对于有意义的替代方案却没有太多共识。

【讨论】:

  • 这不是一个完全准确的总结。 System.nanoTime 取决于特定于操作系统的计时器。过去有过一两个错误,例如在 Windows 中的 Athlon 64 芯片上,但是在大多数系统上,您可以依靠 nanoTime 很好地工作。我用它来做多核游戏的动画和计时,从来没有遇到过任何问题。
  • 感谢 mikera 的澄清。我已经更新了我的答案以(希望)提高准确性。
  • 谢谢汤姆。正如我在上面更新的问题中提到的那样,我通过强制使用 PMT 设法恢复了“正常”行为。我想我仍然对这是否会像我们在多个内核上所期望的那样表现出一些琐碎的担忧。是的,如果没有有意义的替代方案(没有“回到 currentTimeMillis”),就很难知道如何最好地进行!
【解决方案4】:

很难判断这是一个错误还是只是内核之间的正常计时器变化。

您可以尝试的一个实验是使用本机调用来强制线程在特定内核上运行。

另外,为了排除电源管理影响,尝试循环旋转以替代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);
}

如果您根据内核选择得到非常不同的结果(例如,在同一内核上为 5000 毫秒,但在不同内核上为 2200 毫秒),这表明问题只是内核之间的自然计时器变化。

如果您从睡眠和旋转得到的结果截然不同,则更有可能是由于电源管理减慢了时钟。

如果四个结果中的none接近5000ms,那么可能是一个bug。

【讨论】:

  • 谢谢 finnw,这很有趣。我的结果是:睡眠,不同的核心 nanosTaken=2049217124 millisTaken=4985 睡眠,相同的核心 nanosTaken=1808868148 millisTaken=4985 旋转,不同的核心 nanosTaken=5015172794 millisTaken=5000 旋转,相同的核心 nanosTaken=5015295288 millisTaken=5000 你认为这意味着我的机器上的电源管理坏了?
  • 在阅读更多内容后,由您的测试触发,我尝试在 boot.ini 中使用 /usepmtimer 重新启动我的机器。现在你的测试(和我原来的测试)表现正常。我已经相应地编辑了我的问题。我应该这样做吗?
  • 不一定是“坏掉”,但很明显,TSC 不适合在您的机器上进行高精度计时,使用 PM 计时器可以得到更好的结果。我认为 /usepmtimer 是 XP SP3 上的默认设置,但您的结果表明并非如此。
  • 是的,我的机器实际上是一台 SP2 机器,最近通过某种公司修补程序修补到 SP3,这可能会或可能不会掺入标准的 MS SP3 发行版;或者当 SP3 作为补丁应用时,boot.ini 不会更改。不确定。
猜你喜欢
  • 1970-01-01
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 2013-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多