当前位置:  开发笔记 > 编程语言 > 正文

System.nanoTime()完全没用吗?

如何解决《System.nanoTime()完全没用吗?》经验,为你挑选了8个好方法。

Java博客文章Beware of 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之后OS可以将线程调度到第二个CPU,因此timeSpent的值可能不正确甚至是负数.



1> Tom Anderson..:

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

那篇文章错了,而且nanoTime很安全.有关此帖子的评论链接到David Holmes的博客文章,他是Sun的实时和并发人员.它说:

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

因此,在Windows上,这一个问题,直到WinXP SP2,但它现在不是.

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

    所有处理器/核心的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不同于英特尔,实际上知道如何设计微处理器).

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

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


即便是WinXP SP2也会受到影响.运行原始代码示例,使用`void foo(){Thread.sleep(40); 我使用一个"Athlon 64 X2 4200 +"处理器得到了一个负时间(-380毫秒!)
很好的答案,应该添加一个链接到这个主题的最新探索:http://shipilev.net/blog/2014/nanotrusting-nanotime/

2> mezoid..:

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

从Java Sun站点查看此引用:

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

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

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

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

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

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


@pdeva - 但你误解了医生说的话.**你提出了一个非问题.**某个时间点被认为是"0".nanoTime()返回的值相对于该时间是准确的.它是一个单调增加的时间表.你可能会从该时间轴的负面部分得到一系列数字.`-100`,` - 99`,`-98`(显然在实践中有更大的价值).他们正朝着正确的方向前进(增加),所以这里没有问题.
>即使结果返回值为负,此方法也足够好.我不明白,如果timepent中的值是负数,那么在测量foo()中所用的时间方面它是如何有用的呢?
没关系,因为你所担心的只是差异的绝对值.即如果你的测量是时间t,其中t = t2 - t1然后你想知道| t | ....那么如果值是负的怎么样...即使有多核问题,影响很少会是一些无论如何,纳秒.
要备份@Aaron:t2和t1都可能为负数,但(t2-t1)不得为负数.
亚伦:这正是我的观点.t2-t1永远不应该是否定的,否则我们就会有一个bug.

3> blais..:

无需辩论,只需使用来源.在这里,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;
  }
}


只有当您知道所使用的API的功能时,这才有用.使用的API由操作系统实现; 这段代码是正确的.所使用的API的规范(clock_gettime/gettimeofday),但正如其他人所指出的,一些非最新的操作系统有错误的实现.

4> leventov..:

从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-8040140,JDK-8184271)不能坚持这种保证。目前,OpenJDK wrt中没有任何公开的(已知的)错误nanoTime(),但是发现新的此类错误或OpenJDK的较新版本中的回归都不会令任何人感到震惊。

考虑到这一点,代码,使用nanoTime()对定时阻挡,间隔等待,超时等应优选治疗负的时间差(超时)为零,而不是抛出异常。这种做法也是可取的,因为它是在所有类中的所有定时的等待方法的行为是一致的java.util.concurrent.*,例如Semaphore.tryAcquire()Lock.tryLock()BlockingQueue.poll()等。

但是,nanoTime()仍应首选实施定时阻塞,间隔等待,超时等,currentTimeMillis()因为后者会受到“时间倒退”现象的影响(例如由于服务器时间校正),即currentTimeMillis()不适合测量时间间隔完全没有 有关更多信息,请参见此答案。

代替nanoTime()直接用于代码执行时间测量,最好应使用专门的基准测试框架和分析器,例如,在壁钟分析模式下的JMH和async-profiler。



5> Julius Musse..:

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

您可能更喜欢这样:

http://juliusdavies.ca/nanotime/

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

一些背景信息在我的网站上:

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



6> Peter Lawrey..:

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


你能提供一个参考(Linux和Windows上的行为)吗?

7> Yuvi Masory..:

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

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

我只测试多核机器.



8> Basil Vandeg..:

我看到使用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是无用的-它提供了亚毫秒级的计时,但是有一个实际的(不仅仅是理论上的)风险,您需要考虑它不准确。

推荐阅读
手机用户2402851155
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有