System.nanoTime()完全没用吗?

时间:2009-02-04 08:03:42

标签: java nanotime

如博客文章 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中得到一个值 less 而不是time1。因此,我们将在timeSpent中得到负值。

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

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

15 个答案:

答案 0 :(得分:202)

这个答案是在2011年从当时在操作系统上运行的Sun JDK实际上所做的事情的角度编写的。那是很久以前的事! leventov's answer提供了更新的观点。

该帖子错误,nanoTime是安全的。有一条评论链接到a blog post by David Holmes,这是Sun的实时和并发人员。它说:

  

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

因此,在Windows上,这个 在WinXP SP2之前出现了问题,但现在还没有。

我找不到谈论其他平台的第二部分(或更多部分),但该文章确实包含了Linux遇到的注释,并以相同的方式解决了同样的问题,并带有{{3的链接,}说:

  
      
  1. 所有处理器/核心的clock_gettime(CLOCK_REALTIME)是否一致? (拱形物是否重要?例如ppc,arm,x86,amd64,sparc)。
  2.         

    或者它被认为是错误的。

         

    但是,在x86 / x86_64上,可以看到未同步或变量的频率TSC导致时间不一致。 2.4内核确实没有针对此的保护,早期的2.6内核也没有做得太好。从2.6.18开始,用于检测的逻辑更好,我们通常会回到安全的时钟源。

         

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

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

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

编辑:这个答案是基于它引用的来源。但我仍然担心它可能实际上是完全错误的。一些更新的信息将非常有价值。我刚刚看到了FAQ for clock_gettime(CLOCK_REALTIME)的链接,这可能很有用。

答案 1 :(得分:35)

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

从Java Sun站点查看this quote

  

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

     

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

Java还有一个caveat for the nanoTime()方法:

  

此方法只能用于   测量经过的时间而不是   与任何其他系统概念有关   或挂钟时间。返回的值   从某些方面起代表纳秒   固定但任意的时间(也许是在   未来,所以价值观可能如此   负)。这种方法提供   纳秒精度,但不是   必须达到纳秒精度。没有   保证是如何做的   经常改变价值观。差异   在连续的呼叫中跨越更大   大约292。3年(2 63   纳秒)不准确   计算由数字引起的经过时间   溢出。

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

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

答案 2 :(得分:18)

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

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;
  }
}

答案 3 :(得分:10)

免责声明:我是此图书馆的开发者

您可能更喜欢这样:

http://juliusdavies.ca/nanotime/

但是它将DLL或Unix .so(共享对象)文件复制到当前用户的主目录中,以便它可以调用JNI。

我的网站上提供了一些背景信息:

http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html

答案 4 :(得分:8)

自Java 7起,{J {1}}被保证是安全的。 System.nanoTime()'s Javadoc明确指出,JVM中所有观察到的调用(即跨所有线程) )是单调的:

  

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

JVM / JDK实现负责消除调用底层OS实用程序(例如,Tom Anderson's answer中提到的那些)时可能观察到的不一致。

此问题的其他大多数旧答案(写于2009–2012年)表示FUD可能与Java 5或Java 6有关,但与现代Java版本不再相关。

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

请记住,使用nanoTime()进行定时阻塞,间隔等待,超时等的代码最好将负时间差(超时)视为零,而不是引发异常。这种做法也是可取的,因为它与nanoTime()中所有类(例如Semaphore.tryAcquire()java.util.concurrent.*BlockingQueue.poll()等)中所有定时等待方法的行为一致。 / p>

尽管如此,对于Lock.tryLock()实施定时阻塞,间隔等待,超时等,仍应首选nanoTime(),因为currentTimeMillis()会受到“时间倒退”现象的影响(例如,由于服务器时间校正),i。 e。 currentTimeMillis()根本不适合测量时间间隔。有关更多信息,请参见this answer

最好不要使用专门的基准测试框架和分析器,例如,JMH中的async-profilerwall-clock profiling mode,而不是直接使用nanoTime()来测量代码执行时间。

答案 5 :(得分:6)

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

答案 6 :(得分:5)

绝对没有用武之地。时间爱好者正确地指出了多核问题,但在实用词应用程序中,它通常比currentTimeMillis()好得多。

在帧刷新中计算图形位置时,nanoTime()可以在我的程序中实现更平滑的运动。

我只测试多核机器。

答案 7 :(得分:4)

我看到使用System.nanoTime()报告的已过去时间为负。需要说明的是,相关代码是:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

和变量'elapsedNanos'的值为负值。 (我很肯定中间调用也花了不到293年,这是存储在longs中的nanos的溢出点:)

这是在运行AIX的IBM P690(多核)硬件上使用IBM v1.5 JRE 64位时发生的。我只看到过这种错误,所以看起来非常罕见。我不知道原因 - 它是一个特定于硬件的问题,一个JVM缺陷 - 我不知道。我也不知道对nanoTime()的准确性的影响。

要回答原始问题,我认为nanoTime没有用处 - 它提供亚毫秒级时序,但实际(不仅仅是理论上)风险是不准确的,您需要考虑。

答案 8 :(得分:2)

不,它不是......它只取决于你的CPU,检查High Precision Event Timer如何/为什么根据CPU对事物进行不同的处理。

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

IBM even suggests您将其用于性能基准测试(2008年发布,但已更新)。

答案 9 :(得分:2)

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

在使用三个线程的测试中,我没有看到System.nanoTime()向后移动。处理器都很忙,并且线程偶尔会睡觉以引起移动的线程。

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

答案 10 :(得分:2)

我正在联系Peter Laurerey提供一个好答案的基本相同的讨论。 Why I get a negative elapsed time using System.nanoTime()?

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

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

答案 11 :(得分:1)

Java是跨平台的,而nanoTime与平台有关。如果使用Java - 何时不使用nanoTime。我用这个函数在不同的jvm实现中发现了真正的错误。

答案 12 :(得分:0)

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

  

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

Java 5 API Doc

答案 13 :(得分:0)

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

答案 14 :(得分:-3)

nanoTime对时间安排非常不安全。我在我的基本素数测试算法上尝试了它,并给出了相同输入的相隔一秒的答案。不要使用那种荒谬的方法。我需要的东西比获得时间毫秒更精确和准确,但不如nanoTime那么糟糕。