System.nanoTime()完全没用吗?


153

如博客文章《当心 Java中的System.nanoTime()》所述,在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的值可能不正确,甚至为负数。


2
我没有答案,但我同意你的看法。也许应该将其视为JVM中的错误。
亚伦·迪古拉

2
该帖子不正确,没有使用TSC的速度很慢,但是您必须忍受:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6440250 也可以通过虚拟机管理程序使TSC变得有用,但是又变慢了。
bestsss 2012年

1
当然,您也可以在虚拟机中运行,在该虚拟机中,CPU可以在会话中途显示:D
赎罪

Answers:


206

从当时的Sun JDK在当时的操作系统上运行的观点来看,该答案写于2011年。那是很久以前的事!列文托夫的答案提供了最新的观点。

该帖子是错误的,并且nanoTime很安全。Sun的实时与并发专家David Holmes对该博客发表了评论。它说:

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

因此,在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和Motorola与Intel不同,实际上知道如何设计微处理器)。

遗憾的是,没有提到SPARC或Solaris。当然,我们不知道IBM JVM的作用。但是现代Windows和Linux上的Sun JVM可以实现这一目标。

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


13
甚至WinXP SP2似乎也受苦。运行原始代码示例,void foo() { Thread.sleep(40); }使用单个Athlon 64 X2 4200+处理器的时间为负(-380毫秒!)
Luke Usherwood'Mar

我不认为对此有更新。在Linux,BSD或其他平台上的行为?
Tomer Gabel 2013年

6
好的答案,应该添加一个链接到该主题的最新探索:shipilev.net/blog/2014/nanotrusting-nanotime
Nitsan Wakart 2015年

1
@SOFe:太可惜了。幸运的是,它在网络档案中。我将查看是否可以跟踪当前版本。
汤姆·安德森

1
注意:在OpenJDK 8u192之前,OpenJDK一直不遵守该规范,请参阅bugs.openjdk.java.net/browse/JDK-8184271。确保至少使用新版本的OpenJDK 8或OpenJDK 11+。
leventov '19

35

我进行了一些搜索,发现如果正在修脚,是的,在某些情况下,它可能被认为是无用的……这取决于您对时间的敏感程度……

在Java Sun网站上查看以下报价

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

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

Java还对nanoTime()方法提出了警告

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

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

因此,要回答您的问题……没有nanoTime()并不是没有用的……它不是在每种情况下都应使用的最谨慎的方法。


3
>即使返回的结果为负,此方法也足够好。我不明白这一点,如果timespent中的值是负数,那么它在测量foo()中花费的时间上有什么用?
pdeva

3
可以,因为您担心的只是差异的绝对值。即,如果您的测量是时间t,其中t = t2-t1,那么您想知道| t | ....那么,如果值是负数...即使存在多核问题,影响也很少会是几分毫微秒。
mezoid

3
要备份@Aaron:t2和t1都可能为负,但(t2-t1)不能为负。
jfs

2
亚伦:这正是我的意思。t2-t1永远不应为负,否则我们将出现错误。
pdeva

12
@pdeva-但是您误会了文档的内容。您正在提出非问题。在某个时间点被视为“ 0”。nanoTime()返回的值相对于该时间是准确的。它是单调递增的时间表。您可能只是从该时间轴的负数部分得到了一系列数字。-100-99-98(在实践中明显更大值)。他们正朝着正确的方向发展(不断增加),因此这里没有问题。
ToolmakerSteve

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

7
仅当您知道所用API的功能时,这才有用。使用的API是由操作系统实现的;此代码正确无误。使用的API的规范(clock_gettime / gettimeofday),但正如其他人指出的那样,某些非最新的操作系统具有错误的实现。
Blaisorblade 2012年

18

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

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

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

该问题的其他大多数旧答案(写于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



6

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


2
您能否提供参考(在Linux和Windows上的行为)?
jfs

不幸的是,由于每个事件落入+/- 100ms壁钟更新时隙中,对于次秒操作通常会返回零,因此,所提出的方法通常是高度不精确的。持续时间为零的9个操作的总和为零,除以九为...零。相反,使用System.nanoTime()将提供相对准确的(非零)事件持续时间,然后将其相加并除以事件数量将提供高度精确的平均值。
Darrell Teague

@DarrellTeague汇总1000个事件并将其相加与结束时间相同。
彼得·劳瑞

在大多数系统上,@ DarrellTeague System.nanoTime()的精度为1微秒或更佳(不是100,000微秒)。平均许多操作仅在您降低到几微秒时才有意义,并且仅在某些系统上才有意义。
彼得·劳瑞

1
抱歉,“汇总”事件中使用的语言有些混乱。是的,如果在例如1000个亚秒级操作的开始处标记了时间,那么它们将运行,然后在结束时再次标记时间并进行划分-这将对正在开发的某些系统起作用,从而获得给定时间的良好近似值事件。
Darrell Teague

5

绝对没有用。定时爱好者正确地指出了多核问题,但是在实词应用中,它通常比currentTimeMillis()更好。

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

而且我仅在多核计算机上进行测试。


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 64bit发生的。我只看到此错误发生过一次,因此似乎极为罕见。我不知道原因-这是硬件特定的问题,还是JVM缺陷-我不知道。我也不知道通常对nanoTime()的准确性有何影响。

要回答最初的问题,我并不认为nanoTime是无用的-它提供了亚毫秒级的计时,但是有一个实际的(不仅仅是理论上的)风险,您需要考虑它不准确。


las,似乎是某些操作系统/硬件问题。文档指出核心值可能是负值,但(较大的负值减去较小的负值)仍应为正值。实际上,假设是在同一线程中,nanoTime()调用应始终返回正值或负值。多年以来,从未在各种Unix和Windows系统上看到过这种情况,但是听起来似乎是有可能的,尤其是如果硬件/操作系统将这种看似原子的操作分配给处理器。
Darrell Teague

@BasilVandegriend在任何地方都不是错误。根据文档,示例中的第二个System.nanoTime()很少可以在其他CPU上运行,并且在该CPU上计算的nanoTime值可能恰好低于在第一个CPU上计算的值。因此,经过的Nanos的-ve值是可能的
无休止的

2

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

在具有三个线程的测试中,我看不到System.nanoTime()向后移动。处理器都很忙,线程偶尔会进入睡眠状态,以引起线程移动。

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


2
它可能不会一直发生,但是由于nanotime()的实现方式,这种可能性始终存在。
pdeva

我猜想它只会在物理上分开的处理器上发生,即计数器是针对同一芯片上的多个内核同步的。
starblue

即使如此,也要取决于IIRC的具体实现。但这是操作系统应该解决的问题。
Blaisorblade 2012年

1
同一x86处理器的多个内核上的RDTSC计数器不一定是同步的-一些现代系统使不同的内核以不同的速度运行。
2013年

2

不,不是...这仅取决于您的CPU,请检查高精度事件计时器以了解根据CPU对事物的处理方式/原因。

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

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


当所有实现都定义了行为时,“ caveat emptor!”
David Schmitt,2009年

2

我链接到彼得·劳瑞(Peter Lawrey)提供一个好的答案的本质上是相同的讨论。 为什么使用System.nanoTime()得到的消逝时间是负数?

许多人提到Java System.nanoTime()可能返回负时间。我为重复别人已经说过的道歉。

  1. nanoTime()不是时钟,而是CPU周期计数器。
  2. 返回值除以频率看起来像时间。
  3. CPU频率可能会波动。
  4. 当您的线程安排在另一个CPU上时,就有机会获得nanoTime(),这将导致负差。这是合乎逻辑的。跨CPU的计数器不同步。
  5. 在许多情况下,您可能会得到令人误解的结果,但由于增量不是负数,因此您无法分辨。想一想。
  6. (未经确认)我认为,即使重新分配指令,即使在同一CPU上,您也可能会得到负面结果。为了避免这种情况,您必须调用内存屏障来序列化您的指令。

如果System.nanoTime()在执行位置返回coreID,那将很酷。


1
除3.和5.以外的所有点都是错误的。1. nanoTime()不是CPU周期计数器,而是nano time。2.如何产生nanoTime值是特定于平台的。4.不,根据nanoTime()规范,差异不能为负。假设OpenJDK在nanoTime()上没有错误,并且目前没有已知的未解决的错误。6. nanoTime调用不能在线程内重新排序,因为它是本机方法,并且JVM遵守程序顺序。JVM永远不会对本机方法调用进行重新排序,因为它不知道它们内部发生了什么,因此无法证明这种重新排序是安全的。
leventov '19

关于5. nanoTime()的差异结果确实可能会产生误导,但不是出于此答案中其他要点提出的原因。而是出于此处提出的原因:shipilev.net/blog/2014/nanotrusting-nanotime
leventov '19

具有讽刺意味的是,关于6.,由于重新排序,OpenJDK中存在一个错误:bugs.openjdk.java.net/browse/JDK-8184271。在OpenJDK中,nanoTime()是一个内在函数,它被允许重新排序,这是一个错误。
leventov '19

@leventov,那么nanotime()使用安全吗?意思是说,它不能返回负值,并且就时间而言是准确的。我看不到暴露出充满问题的API函数的意义。这篇文章是从2009年开始的证明,并在2019年仍然进行了评论。对于关键任务的东西,我想人们会依赖诸如Symmetricom
Vortex的

1
您的#4说的是负,而不是值:“有机会获得nanoTime()导致负差。”
leventov '19

1

Java是跨平台的,而nanoTime是依赖于平台的。如果您使用Java-何时不使用nanoTime。我发现此功能在不同的jvm实现中存在真正的错误。


0

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

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

Java 5 API文档


0

同样,System.currentTimeMillies()更改系统时钟时会更改,而System.nanoTime()不会更改,因此后者更安全。


-3

nanoTime计时非常不安全。我在基本素数测试算法上进行了测试,得出的答案对于相同的输入实际上相差一秒钟。不要使用这种可笑的方法。我需要比获得时间毫秒更准确,更精确的东西,但还不如nanoTime


没有消息来源或没有更好的解释,此评论无用
ic3
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.