System.nanTime()完全无用吗?

如博客文章 小心 Java 中的 System.nanTime ()所述,在 x86系统上,Java 的 System.nanTime ()使用特定于 中央处理器的计数器返回时间值。现在考虑下面的例子,我用它来度量一个呼叫的时间:

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

现在在一个多核系统中,可能是在测量时间1之后,线程被调度到一个计数器小于前一个 CPU 计数器的不同处理器。因此我们可以在 time2中得到一个值,它是 更少而不是 time1。因此,我们将得到一个负值的时间花费。

考虑到这种情况,System.nantime 现在是不是已经没什么用了?

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

120925 次浏览

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

基本上,阅读 Java 的源代码,检查你的版本是如何处理这个函数的,如果它是在 CPU 上运行的,那么你就要在它上面运行。

IBM 甚至建议使用 进行性能基准测试(2008年的一篇文章,但是更新了)。

Java5文档还建议为同样的目的使用此方法。

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

Java5API 文件

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

在一个有三个线程的测试中,我没有看到 System.nanTime ()向后退。处理器都很忙,线程偶尔进入休眠状态,以引起线程的移动。

[编辑]我猜它只发生在物理上独立的处理器上,也就是说,计数器在同一个骰子上对多个核进行同步。

我做了一点搜索,发现如果一个人是迂腐的,那么是的,它可能被认为是无用的... 在特定的情况下... 这取决于你的需求时间敏感性有多高..。

从 JavaSun 站点查看 这句话:

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

使用 JavaRTS,所有基于时间的 API (例如,计时器,定期 线程、截止日期监视等等 基于 高分辨率计时器,一起 有了实时优先权,他们就可以 确保适当的代码将 在适当的时候为... ... 执行 实时约束。相比之下, 普通的 JavaSEAPI 只提供一些 能够处理的方法 高分辨率时间,没有 在给定时间执行的保证 之间使用 System.nanTime () 执行代码中的各个点 经过的时间测量应该 一定要准确。

Java 还有一个 关于纳米时间的警告()方法:

此方法只能用于 度量经过的时间,而不是 与任何其他系统概念有关 或壁钟时间。返回的值 代表纳秒 固定但任意的时间(也许在 未来,所以价值可能是 否)。此方法提供 精确到纳秒,但不是 必然是纳秒级的精确度,不 保证如何 价值观经常改变。差异 在连续的叫声中,范围更大 大约292.3年(263 纳秒)不会准确 计算由于数值而消耗的时间 溢出。

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

所以,为了回答你的问题... ... no nanTime ()并非毫无用处... ... 它只是不是在任何情况下使用的最谨慎的方法。

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

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

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

我只在多核机器上测试。

我看到过使用 System.nanTime ()报告的 已经过去了时间为负值:

    long startNanos = System.nanoTime();


Object returnValue = joinPoint.proceed();


long elapsedNanos = System.nanoTime() - startNanos;

变量‘ elapsed Nanos’有一个负值。(我敢肯定,中间调用也花了不到293年,这是长期存储的纳米的溢出点:)

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

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

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

那个帖子是错的,nanoTime是安全的。有一个评论的帖子,链接到 大卫 · 霍姆斯的一篇博客文章,在 Sun 的实时和并发的家伙。上面写着:

采用 QueryPerformanceCounter/QueryPerformanceChannel API 实现 System.nanTime ()[ ... ] QPC 使用的默认机制由硬件抽象层层(HAL)决定[ ... ]这个默认机制不仅在硬件上有变化,而且在操作系统版本上也有变化。例如,Windows XP Service Pack 2改为使用电源管理计时器(PMTimer)而不是处理器时间戳计数器(TSC) ,因为在 SMP 系统的不同处理器上 TSC 不能同步,并且由于它的频率可能会变化(因此它与运行时间的关系)基于电源管理设置。

所以,在 Windows 上,这个 曾经是在 WinXP SP2之前是个问题,但现在不是了。

我找不到关于其他平台的第二部分(或者更多) ,但是那篇文章确实包含了一句话,说 Linux 遇到了同样的问题,并且用同样的方法解决了同样的问题,还有一个到 时钟 _ gettime (CLOCK _ REALTIME)的常见问题解答的链接,说:

  1. 时钟 _ gettime (CLOCK _ REALTIME)在所有处理器/核之间是否一致? (arch 重要吗? 例如: ppc,arm,x86,amd64,Sparc)。

应该或它被认为是错误的。

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

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

因此,如果霍姆斯的链接可以被解读为意味着 nanoTime调用 clock_gettime(CLOCK_REALTIME),那么它是安全的,因为内核2.6.18在 x86上,而且总是在 PowerPC 上(因为 IBM 和摩托罗拉,不像英特尔,实际上知道如何设计微处理器)。

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

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

不需要辩论,只要利用消息来源。 在这里,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;
}
}

Java 是跨平台的,而 nanTime 是依赖于平台的。如果你使用 Java-when 不要使用 nanTime。我在使用这个函数的不同 jvm 实现中发现了真正的 bug。

nanoTime在计时方面极不安全。我在我的基本质性测试算法上试了试,它给出的答案差不多相同的输入只有一秒钟。别用那种荒唐的方法。我需要的东西是更准确和精确的比得到时间毫米,但不像坏的 nanoTime

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

我链接到本质上是相同的讨论,彼得劳里提供了一个很好的答案。 为什么我使用 System.nanTime ()得到一个负的运行时间?

许多人提到,在 JavaSystem.nanTime ()中可能返回负时间。我为重复别人说过的话而道歉。

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

如果 System.nanTime ()在执行时返回 coreID,那将非常酷。

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

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

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

对这个问题的大多数其他老的回答(写于2009 & nash; 2012)表示 FUD 可能与 Java5或 Java6有关,但不再与 Java 的现代版本有关。

然而,值得一提的是,尽管 JDK 保证了 nanoTime()的安全性,但 OpenJDK 中存在一些 bug,使得它在某些平台或某些情况下(如 JDK-8040140JDK-8184271)无法维护这种保证。目前 OpenJDK wrt nanoTime()中没有公开的(已知的) bug,但是在 OpenJDK 的新版本中发现一个新的这样的 bug 或者一个回归应该不会让任何人感到震惊。

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

尽管如此,对于 currentTimeMillis()来说,nanoTime()仍然是实现定时阻塞、间隔等待、超时等的首选,因为后者受到“时间倒退”现象(例如由于服务器时间校正)的影响,即 currentTimeMillis()根本不适合测量时间间隔。参见 这个答案以获得更多信息。

与直接使用 nanoTime()进行代码执行时间度量不同,最好使用专门的基准测试框架和分析器,例如 墙钟分析模式墙钟分析模式中的 JMH异步剖析器