【问题标题】:Is System.nanoTime() completely useless?System.nanoTime() 完全没用吗?
【发布时间】:2010-10-05 08:36:04
【问题描述】:

如博文 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 中得到一个小于 time1 的值。因此我们会在 timeSpent 中得到一个负值。

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

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

【问题讨论】:

  • 我没有答案,但我同意你的看法。也许它应该被认为是 JVM 中的一个错误。
  • 那个帖子不正确,不使用 TSC 很慢,但你必须忍受:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6440250 TSC 也可以通过管理程序变得有用,但它又变慢了。
  • 当然,您可以在虚拟机中运行,其中 CPU 可以在会话中途出现:D

标签: java nanotime


【解决方案1】:

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

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

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

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

我找不到关于其他平台的第二部分(或更多),但该文章确实包含了 Linux 遇到并以相同方式解决相同问题的评论,并带有指向 FAQ for clock_gettime(CLOCK_REALTIME) 的链接,上面写着:

  1. clock_gettime(CLOCK_REALTIME) 在所有处理器/内核中是否一致? (arch 重要吗?例如 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 和摩托罗拉,实际上与英特尔不同,实际上知道如何设计微处理器)。

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

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

【讨论】:

  • 甚至 WinXP SP2 似乎也受到影响。使用void foo() { Thread.sleep(40); } 运行原始代码示例,我使用单个Athlon 64 X2 4200+ 处理器得到了一个负时间(-380 毫秒!)
  • 我不认为有更新,wrt。 Linux、BSD 或其他平台上的行为?
  • 好答案,应该添加一个链接到这个主题的最新探索:shipilev.net/blog/2014/nanotrusting-nanotime
  • @SOFe:哦,真可惜。幸运的是,它在web archive 中。我会看看我是否可以找到当前版本。
  • 注意:OpenJDK 直到 OpenJDK 8u192 才支持规范,请参阅 bugs.openjdk.java.net/browse/JDK-8184271。确保至少使用新版本的 OpenJDK 8 或 OpenJDK 11+。
【解决方案2】:

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

从 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() 不是没用的...它只是不是在每种情况下使用的最谨慎的方法。

【讨论】:

  • > 这个方法足够好,即使返回的结果是负数。我不明白,如果 timespent 的值是负数,那么它在测量 foo() 中花费的时间方面有什么用?
  • 没关系,因为您所担心的只是差异的绝对值。即,如果您的测量是时间 t,其中 t = t2 - t1 那么您想知道 |t|....那么如果值为负数怎么办...即使是多核问题,影响也很少无论如何都是纳秒。
  • 备份@Aaron:t2 和 t1 可能都是负数,但 (t2-t1) 不能是负数。
  • aaron:这正是我的意思。 t2-t1 永远不应该是负数,否则我们有一个错误。
  • @pdeva - 但您误解了文档所说的内容。 您提出的不是问题。 某个时间点被视为“0”。 nanoTime() 返回的值相对于该时间是准确的。它是一个单调递增的时间线。您可能只是从该时间线的负数部分获得一系列数字。 -100、-99、-98(在实践中显然值更大)。他们正朝着正确的方向前进(增加),所以这里没有问题。
【解决方案3】:

从 Java 7 开始,JDK 规范保证 System.nanoTime() 是安全的。 System.nanoTime()'s Javadoc 明确指出,在 JVM 中(即跨所有线程)观察到的所有调用都是单调的:

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

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

这个问题的大多数其他旧答案(写于 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() 来实现定时阻塞、间隔等待、超时等,因为后者是“时间倒退”现象的主题(例如,由于服务器时间校正) , 一世。 e. currentTimeMillis() 根本不适合测量时间间隔。请参阅this answer 了解更多信息。

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

【讨论】:

    【解决方案4】:

    无需争论,使用源代码即可。 在这里,针对 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;
      }
    }
    

    【讨论】:

    • 只有在您知道所用 API 的作用时才有用。使用的API由操作系统实现;这段代码是正确的。所用 API 的规范 (clock_gettime/gettimeofday),但正如其他人指出的那样,一些非最新的操作系统有错误的实现。
    【解决方案5】:

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

    【讨论】:

    • 能否提供参考(Linux 和 Windows 上的行为)?
    • 不幸的是,所提出的方法通常会非常不精确,因为每个落入 +/- 100 毫秒挂钟更新槽的事件通常会在亚秒级操作中返回零。每个持续时间为零的 9 个操作的总和是零,除以九是......零。相反,使用 System.nanoTime() 将提供相对准确(非零)的事件持续时间,然后将其相加并除以事件数将提供高度精确的平均值。
    • @DarrellTeague 总结 1000 个事件并将它们相加与端到端时间相同。
    • @DarrellTeague System.nanoTime() 在大多数系统上精确到 1 微秒或更好(不是 100,000 微秒)。平均许多操作仅在您降至几微秒时才有意义,并且仅在某些系统上。
    • 道歉,因为在“求和”事件中使用的语言有些混乱。是的,如果时间是在 1000 亚秒操作开始时标记的,它们会运行,然后在结束时再次标记时间并划分时间 - 这对于某些正在开发的系统来说是有效的,以获得给定的持续时间的良好近似值事件。
    【解决方案6】:

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

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

    而且我只在多核机器上测试。

    【讨论】:

      【解决方案7】:

      我看到使用 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 64 位。我只见过这个错误发生一次,所以它似乎非常罕见。我不知道原因——它是特定于硬件的问题,还是 JVM 缺陷——我不知道。我也不知道 nanoTime() 一般对准确性的影响。

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

      【讨论】:

      • 唉,似乎是一些操作系统/硬件问题。该文档指出,核心值可能为负值,但(较大的负值减去较小的负值)仍应为正值。事实上,假设在同一个线程中, nanoTime() 调用应该总是返回一个正值或负值。多年来从未在各种 Unix 和 Windows 系统上看到过这种情况,但听起来可能,尤其是当硬件/操作系统将这种看似原子的操作拆分到处理器之间时。
      • @BasilVandegriend 它不是任何地方的错误。根据文档,您示例中的第二个 System.nanoTime() 很少可以在不同的 CPU 上运行,并且在该 CPU 上计算的 nanoTime 值可能恰好低于在第一个 CPU 上计算的值。因此,elapsedNanos 的 -ve 值是可能的
      【解决方案8】:

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

      在三个线程的测试中,我没有看到 System.nanoTime() 倒退。处理器都很忙,线程偶尔会进入睡眠状态以激发移动线程。

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

      【讨论】:

      • 它可能不会一直发生,但由于 nanotime() 的实现方式,这种可能性总是存在的。
      • 我猜它只发生在物理上独立的处理器上,即计数器在同一个芯片上为多个内核同步。
      • 即使这取决于具体的实现,IIRC。但这是操作系统应该处理的事情。
      • 同一 x86 处理器的多个内核上的 RDTSC 计数器不一定同步 - 一些现代系统让不同的内核以不同的速度运行。
      【解决方案9】:

      不,不是...这仅取决于您的 CPU,请查看 High Precision Event Timer 了解如何/为什么根据 CPU 对事物进行不同处理。

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

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

      【讨论】:

      • 所有实现都定义了行为,“买者警告!”
      【解决方案10】:

      我所链接的内容本质上与 Peter Lawrey 提供了一个很好的答案的讨论相同。 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 上,您也可能会得到否定的结果。为了防止这种情况发生,您必须调用一个内存屏障来序列化您的指令。

      如果 System.nanoTime() 在它执行的地方返回 coreID,那就太酷了。

      【讨论】:

      • 除 3. 和 5. 之外的所有点都是错误的。 1. nanoTime() 不是CPU 周期计数器,它是nano time。 2. nanoTime 值的产生方式因平台而异。 4. 不,根据 nanoTime() 规范,差异不能为负数。假设 OpenJDK 在 nanoTime() 中没有错误,并且目前没有已知的未解决错误。 6. nanoTime 调用不能在线程内重新排序,因为它是本机方法并且 JVM 尊重程序顺序。 JVM 从不重新排序本机方法调用,因为它不知道它们内部发生了什么,因此无法证明这种重新排序是安全的。
      • 关于 5. nanoTime() 差异结果确实可能具有误导性,但不是因为本回答中其他点中提出的原因。而是出于此处介绍的原因:shipilev.net/blog/2014/nanotrusting-nanotime
      • 具有讽刺意味的是,关于 6. OpenJDK 中存在一个错误,特别是由于重新排序:bugs.openjdk.java.net/browse/JDK-8184271。在 OpenJDK 中,nanoTime() 是一个内在函数,它被允许重新排序,这是一个错误。
      • @leventov,那么 nanotime() 可以安全使用吗?意思是,它不能返回负值,并且就时间而言是准确的。我看不出公开一个充满问题的 API 函数的意义。这篇文章是一个证明,从 2009 年开始,到 2019 年仍然有评论。对于关键任务的东西,我想人们依赖于像 Symmetricom 这样的计时卡
      • 你的#4 说的是负的差异,而不是值:“有可能得到 nanoTime() 导致负差异。”
      【解决方案11】:

      Java 是跨平台的,而 nanoTime 是平台相关的。如果你使用 Java - 什么时候不要使用 nanoTime。我发现这个函数在不同的 jvm 实现中存在真正的错误。

      【讨论】:

        【解决方案12】:

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

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

        Java 5 API Doc

        【讨论】:

          【解决方案13】:

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

          【讨论】:

            【解决方案14】:

            nanoTime 对计时非常不安全。我在我的基本素数测试算法上进行了尝试,它给出的答案对于相同的输入实际上相差一秒。不要使用那种荒谬的方法。我需要比 get time millis 更准确和精确的东西,但不如nanoTime。

            【讨论】:

            • 没有来源或更好的解释,这条评论是无用的
            猜你喜欢
            • 1970-01-01
            • 2013-02-03
            • 2017-09-19
            • 1970-01-01
            • 2018-02-05
            • 1970-01-01
            • 1970-01-01
            • 2023-03-23
            • 1970-01-01
            相关资源
            最近更新 更多