160

正如博文Beware of System.nanoTime() in Java 中所述,在 x86 系统上,Java 的 System.nanoTime() 使用CPU特定的计数器返回时间值。现在考虑以下我用来测量通话时间的案例:

long time1= System.nanoTime();
foo();
long time2 = System.nanoTime();
long timeSpent = time2-time1;

现在在多核系统中,可能是在测量time1之后,线程被调度到另一个处理器,其计数器小于前一个CPU的计数器。因此,我们可以在 time2 中得到一个小于time1 的值。因此,我们会在 timeSpent 中得到一个负值。

考虑到这种情况,是不是 System.nanotime 现在几乎没用了?

我知道更改系统时间不会影响纳米时间。这不是我上面描述的问题。问题是每个 CPU 将保持不同的计数器,因为它被打开。与第一个 CPU 相比,第二个 CPU 上的此计数器可能更低。由于线程可以在获取time1后被操作系统调度到第二个CPU,所以timeSpent的值可能不正确,甚至是负数。

4

14 回答 14

212

这个答案是在 2011 年写的,从当时在操作系统上运行的 Sun JDK 实际做了什么的角度来看。那是很久以前的事!leventov 的回答提供了一个更新的视角。

那个帖子是错误的,并且nanoTime是安全的。这篇文章有一条评论链接到Sun 的实时并发专家David Holmes 的博客文章。它说:

System.nanoTime() 是使用 QueryPerformanceCounter/QueryPerformanceFrequency API 实现的 [...] QPC 使用的默认机制由硬件抽象层 (HAL) 确定 [...] 此默认值不仅会跨硬件更改,还会跨操作系统更改版本。例如,Windows XP Service Pack 2 改变了使用电源管理计时器 (PMTimer) 而不是处理器时间戳计数器 (TSC) 的问题,因为 TSC 在 SMP 系统中的不同处理器上不同步,并且由于其频率的事实可以根据电源管理设置而变化(因此它与经过时间的关系)。

因此,在 Windows 上,在 WinXP SP2 之前这一个问题,但现在不是了。

我找不到关于其他平台的第二部分(或更多),但那篇文章确实包含了 Linux 遇到并以相同方式解决相同问题的评论,并提供了指向clock_gettime(CLOCK_REALTIME) 的常见问题解答的链接,其中说:

  1. 所有处理器/内核的clock_gettime(CLOCK_REALTIME) 是否一致?(拱门重要吗?例如 ppc、arm、x86、amd64、sparc)。

应该或被认为是错误的。

但是,在 x86/x86_64 上,可能会看到不同步或可变频率 TSC 导致时间不一致。2.4 内核确实对此没有任何保护,早期的 2.6 内核在这里也做得不太好。从 2.6.18 及更高版本开始,检测这一点的逻辑会更好,我们通常会退回到安全的时钟源。

ppc 总是有一个同步的时基,所以这应该不是问题。

因此,如果 Holmes 的链接可以被解读为暗示nanoTime调用clock_gettime(CLOCK_REALTIME),那么它在 x86 上的内核 2.6.18 上是安全的,并且始终在 PowerPC 上(因为 IBM 和摩托罗拉,与英特尔不同,实际上知道如何设计微处理器)。

遗憾的是,没有提到 SPARC 或 Solaris。当然,我们不知道 IBM JVM 做了什么。但是现代 Windows 和 Linux 上的 Sun JVM 做到了这一点。

编辑:这个答案基于它引用的来源。但我仍然担心它实际上可能是完全错误的。一些更新的信息将非常有价值。我刚刚看到一篇关于 Linux 时钟的四年新文章的链接,这可能很有用。

于 2011-01-03T21:36:46.703 回答
35

我做了一些搜索,发现如果一个人是迂腐的,那么是的,它可能被认为是无用的......在特定情况下......这取决于您的要求对时间的敏感程度......

看看Java Sun 网站上的这句话

实时时钟和 System.nanoTime() 都基于相同的系统调用,因此是相同的时钟。

使用 Java RTS,所有基于时间的 API(例如,计时器、定期线程、截止日期监控等)都基于高分辨率计时器。并且,与实时优先级一起,它们可以确保在正确的时间执行适当的代码以实现实时约束。相比之下,普通的 Java SE API 只提供了一些能够处理高分辨率时间的方法,并且不能保证在给定时间执行。在代码中的各个点之间使用 System.nanoTime() 来执行经过时间的测量应该总是准确的。

Java 还对 nanoTime()方法有一个警告:

此方法只能用于测量经过的时间,与系统或挂钟时间的任何其他概念无关。返回的值表示自某个固定但任意时间以来的纳秒(可能在将来,因此值可能为负数)。此方法提供纳秒精度,但不一定提供纳秒精度。不保证值的变化频率。 由于数值溢出,超过大约 292.3 年(2 63纳秒)的连续调用的差异将无法准确计算经过的时间。

似乎可以得出的唯一结论是 nanoTime() 不能作为准确值依赖。因此,如果您不需要测量仅相隔纳秒的时间,那么即使返回的结果为负值,此方法也足够好。但是,如果您需要更高的精度,他们似乎建议您使用 JAVA RTS。

所以要回答你的问题...... no nanoTime() 不是没用的......它只是不是在每种情况下使用的最谨慎的方法。

于 2009-02-04T11:21:17.303 回答
27

从 Java 7 开始,System.nanoTime()JDK 规范保证是安全的。 System.nanoTime()的 Javadoc清楚地表明,在 JVM 中(即跨所有线程)观察到的所有调用都是单调的:

返回的值表示自某个固定但任意的原始时间以来的纳秒(可能在将来,因此值可能为负数)。在 Java 虚拟机实例中,此方法的所有调用都使用相同的来源;其他虚拟机实例可能使用不同的来源。

JVM/JDK 实现负责消除调用底层操作系统实用程序时可能观察到的不一致(例如汤姆安德森的回答中提到的那些)。

这个问题的大多数其他旧答案(写于 2009-2012 年)都表达了 FUD,它可能与 Java 5 或 Java 6 相关,但不再与现代版本的 Java 相关。

然而,值得一提的是,尽管 JDK 保证nanoTime()了安全,但 OpenJDK 中存在一些错误,使其在某些平台或某些情况下不支持此保证(例如JDK-8040140JDK-8184271)。目前 OpenJDK wrt 中没有公开的(已知的)错误nanoTime(),但发现新的此类错误或 OpenJDK 较新版本中的回归不应该让任何人感到震惊。

考虑到这一点,用于定时阻塞、间隔等待、超时等的代码nanoTime()最好将负时间差(超时)视为零,而不是抛出异常。这种做法也是可取的,因为它与 中的所有类中的所有定时等待方法的行为一致java.util.concurrent.*,例如Semaphore.tryAcquire()Lock.tryLock()BlockingQueue.poll()等。

尽管如此,nanoTime()仍然应该首选实现定时阻塞、间隔等待、超时等,currentTimeMillis()因为后者是“时间倒退”现象的对象(例如由于服务器时间校正),即currentTimeMillis()不适合测量时间间隔一点也不。有关更多信息,请参阅此答案

最好使用专门的基准测试框架和分析器,而不是nanoTime()直接用于代码执行时间测量,例如JMHasync-profiler in wall-clock profiling mode

于 2019-02-07T05:35:40.617 回答
18

无需辩论,只需使用来源即可。在这里,Linux 的 SE 6,得出你自己的结论:

jlong os::javaTimeMillis() {
  timeval time;
  int status = gettimeofday(&time, NULL);
  assert(status != -1, "linux error");
  return jlong(time.tv_sec) * 1000  +  jlong(time.tv_usec / 1000);
}


jlong os::javaTimeNanos() {
  if (Linux::supports_monotonic_clock()) {
    struct timespec tp;
    int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
    assert(status == 0, "gettime error");
    jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
    return result;
  } else {
    timeval time;
    int status = gettimeofday(&time, NULL);
    assert(status != -1, "linux error");
    jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
    return 1000 * usecs;
  }
}
于 2011-04-01T17:07:25.093 回答
7

Linux 纠正了 CPU 之间的差异,但 Windows 没有。我建议您假设 System.nanoTime() 仅精确到 1 微秒左右。获得更长时间的一种简单方法是调用 foo() 1000 次或更多次,然后将时间除以 1000。

于 2009-02-05T23:17:48.290 回答
5

绝对不是没用的。计时爱好者正确地指出了多核问题,但在实际应用程序中,它通常比 currentTimeMillis() 好得多。

当计算帧刷新中的图形位置时,nanoTime() 会导致我的程序中的运动更加平滑。

而且我只在多核机器上测试。

于 2010-03-31T13:27:09.373 回答
5

我看到使用 System.nanoTime() 报告了一个负的经过时间。需要明确的是,有问题的代码是:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

并且变量“elapsedNanos”具有负值。(我很肯定中间调用也用了不到 293 年,这是存储在 long 中的 nanos 的溢出点:)

这发生在运行 AIX 的 IBM P690(多核)硬件上使用 IBM v1.5 JRE 64 位。我只见过这个错误发生一次,所以它似乎非常罕见。我不知道原因——它是特定于硬件的问题,还是 JVM 缺陷——我不知道。我也不知道一般 nanoTime() 准确性的含义。

为了回答最初的问题,我不认为 nanoTime 是无用的——它提供了亚毫秒级的时间,但是你需要考虑到它不准确的实际(不仅仅是理论上的)风险。

于 2011-01-03T20:23:10.717 回答
2

不,它不是......它只取决于你的 CPU,检查高精度事件计时器,了解如何/为什么根据 CPU 对事物进行不同的处理。

基本上,阅读 Java 的源代码并检查您的版本对该函数的作用,如果它对 CPU 有效,您将在其上运行它。

IBM 甚至建议您将其用于性能基准测试(2008 年的一篇文章,但已更新)。

于 2009-02-04T08:13:25.763 回答
2

这在运行 Windows XP 和 JRE 1.5.0_06 的 Core 2 Duo 上似乎不是问题。

在三个线程的测试中,我没有看到 System.nanoTime() 倒退。处理器都很忙,线程偶尔会进入睡眠状态以激发移动线程。

[编辑] 我猜它只发生在物理上独立的处理器上,即计数器对于同一个芯片上的多个内核是同步的。

于 2009-02-04T10:05:59.263 回答
2

我正在链接到本质上与彼得劳里提供了一个很好的答案的讨论相同的讨论。 为什么我使用 System.nanoTime() 得到一个负的经过时间?

许多人提到在 Java System.nanoTime() 中可能会返回负时间。我为重复其他人已经说过的话而道歉。

  1. nanoTime() 不是时钟而是 CPU 周期计数器。
  2. 返回值除以频率看起来像时间。
  3. CPU 频率可能会波动。
  4. 当您的线程被安排在另一个 CPU 上时,有可能会得到 nanoTime() ,这会导致负差异。这是合乎逻辑的。跨 CPU 的计数器不同步。
  5. 在许多情况下,您可能会得到相当误导的结果,但您无法判断,因为 delta 不是负数。想想看。
  6. (未经证实)我认为如果重新排序指令,即使在同一个 CPU 上,您也可能会得到否定的结果。为了防止这种情况,您必须调用一个内存屏障来序列化您的指令。

如果 System.nanoTime() 在它执行的地方返回 coreID,那就太酷了。

于 2016-02-20T02:03:14.023 回答
1

Java 是跨平台的,nanoTime 是平台相关的。如果你使用 Java - 什么时候不要使用 nanoTime。我发现这个函数在不同的 jvm 实现中存在真正的错误。

于 2011-09-21T10:30:32.627 回答
0

Java 5 文档还建议将此方法用于相同目的。

此方法只能用于测量经过的时间,与系统或挂钟时间的任何其他概念无关。

Java 5 API 文档

于 2009-02-04T08:31:06.053 回答
0

此外,System.currentTimeMillies()当您更改系统时钟时会发生变化,而System.nanoTime()不会发生变化,因此后者更安全地测量持续时间。

于 2015-08-20T12:14:12.437 回答
-3

nanoTime时间安排非常不安全。我在我的基本素数测试算法上进行了尝试,它给出的答案对于相同的输入实际上相差一秒。不要使用那种荒谬的方法。我需要比 get time millis 更准确和精确的东西,但不如nanoTime.

于 2012-04-24T04:46:18.713 回答