System.nanoTime()完全没用吗?

问题

如博客文章Beware of System.nanoTime() in Java所述,在x86系统上,Java的System.nanoTime()使用aCPU特定计数器返回时间值。现在考虑我用来测量呼叫时间的以下情况:

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

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

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

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


#1 热门回答(194 赞)

那个帖子是错的,而且nanoTime是安全的。有一条评论链接到522412618,这是Sun的实时和并发人员。它说:

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

所以,在Windows上,直到WinXP SP2才出现这个问题,但现在还没有。

我找不到关于其他平台的第二部分(或更多部分),但该文章确实包含了Linux遇到的注释,并以相同的方式解决了同样的问题,并提供了到FAQ for 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的链接可以理解为暗示nanoTimecallsclock_gettime(CLOCK_REALTIME),那么它在x86上的内核2.6.18是安全的,并且总是在PowerPC上(因为IBM和摩托罗拉不同于Intel,实际上知道如何设计微处理器)。

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

编辑:这个答案是基于它引用的来源。但我仍然担心它可能实际上是完全错误的。一些更新的信息将非常有价值。我刚刚看到一个链接到afour year newer article about Linux's clocks,这可能很有用。


#2 热门回答(35 赞)

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

从Java Sun站点查看this quote

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

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

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

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

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


#3 热门回答(17 赞)

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