System.nanoTime() 完全没用吗?

发布于 2024-07-13 07:54:33 字数 790 浏览 9 评论 0 原文

正如博客文章中所述谨防系统Java 中的 .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的值可能不正确,甚至为负值。

As documented in the blog post Beware of System.nanoTime() in Java, on x86 systems, Java's System.nanoTime() returns the time value using a CPU specific counter. Now consider the following case I use to measure time of a call:

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

Now in a multi-core system, it could be that after measuring time1, the thread is scheduled to a different processor whose counter is less than that of the previous CPU. Thus we could get a value in time2 which is less than time1. Thus we would get a negative value in timeSpent.

Considering this case, isn't it that System.nanotime is pretty much useless for now?

I know that changing the system time doesn't affect nanotime. That is not the problem I describe above. The problem is that each CPU will keep a different counter since it was turned on. This counter can be lower on the second CPU compared to the first CPU. Since the thread can be scheduled by the OS to the second CPU after getting time1, the value of timeSpent may be incorrect and even negative.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

债姬 2024-07-20 07:54:33

这个答案是在 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) 的常见问题解答,其中显示:

  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 做到了这一点。

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

This answer was written in 2011 from the point of view of what the Sun JDK of the time running on operating systems of the time actually did. That was a long time ago! leventov's answer offers a more up-to-date perspective.

That post is wrong, and nanoTime is safe. There's a comment on the post which links to a blog post by David Holmes, a realtime and concurrency guy at Sun. It says:

System.nanoTime() is implemented using the QueryPerformanceCounter/QueryPerformanceFrequency API [...] The default mechanism used by QPC is determined by the Hardware Abstraction layer(HAL) [...] This default changes not only across hardware but also across OS versions. For example Windows XP Service Pack 2 changed things to use the power management timer (PMTimer) rather than the processor timestamp-counter (TSC) due to problems with the TSC not being synchronized on different processors in SMP systems, and due the fact its frequency can vary (and hence its relationship to elapsed time) based on power-management settings.

So, on Windows, this was a problem up until WinXP SP2, but it isn't now.

I can't find a part II (or more) that talks about other platforms, but that article does include a remark that Linux has encountered and solved the same problem in the same way, with a link to the FAQ for clock_gettime(CLOCK_REALTIME), which says:

  1. Is clock_gettime(CLOCK_REALTIME) consistent across all processors/cores? (Does arch matter? e.g. ppc, arm, x86, amd64, sparc).

It should or it's considered buggy.

However, on x86/x86_64, it is possible to see unsynced or variable freq TSCs cause time inconsistencies. 2.4 kernels really had no protection against this, and early 2.6 kernels didn't do too well here either. As of 2.6.18 and up the logic for detecting this is better and we'll usually fall back to a safe clocksource.

ppc always has a synced timebase, so that shouldn't be an issue.

So, if Holmes's link can be read as implying that nanoTime calls clock_gettime(CLOCK_REALTIME), then it's safe-ish as of kernel 2.6.18 on x86, and always on PowerPC (because IBM and Motorola, unlike Intel, actually know how to design microprocessors).

There's no mention of SPARC or Solaris, sadly. And of course, we have no idea what IBM JVMs do. But Sun JVMs on modern Windows and Linux get this right.

EDIT: This answer is based on the sources it cites. But i still worry that it might actually be completely wrong. Some more up-to-date information would be really valuable. I just came across to a link to a four year newer article about Linux's clocks which could be useful.

挽心 2024-07-20 07:54:33

自 Java 7 起,JDK 规范保证 System.nanoTime() 的安全。 System.nanoTime() 的 Javadoc 清楚地表明,所有观察到的调用JVM(即跨所有线程)是单调的:

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

JVM/JDK 实现负责消除调用底层操作系统实用程序时可能观察到的不一致问题(例如 Tom Anderson 的回答中提到的那些< /a>)。

这个问题的大多数其他旧答案(写于 2009-2012 年)都表达了 FUD,这可能与 Java 5 或 Java 6 相关,但不再与现代版本的 Java 相关。

然而,值得一提的是,尽管 JDK 保证了 nanoTime() 的安全性,但 OpenJDK 中存在多个错误,使其在某些平台或某些情况下不支持这种保证(例如 JDK-8040140JDK-8184271)。 目前 OpenJDK 中还没有关于 nanoTime() 的公开(已知)错误,但是在 OpenJDK 新版本中发现新的此类错误或回归不应让任何人感到震惊。

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

尽管如此,nanoTime() 仍应优先用于实现定时阻塞、间隔等待、超时等,而不是 currentTimeMillis()因为后者会受到“时间倒退”现象的影响(例如,由于服务器时间校正),即 currentTimeMillis() 根本不适合测量时间间隔。 > 请参阅此答案了解更多信息。

最好使用专门的基准测试框架和分析器,而不是直接使用nanoTime()来测量代码执行时间,例如JMHasync-profiler 挂钟分析模式

Since Java 7, System.nanoTime() is guaranteed to be safe by JDK specification. System.nanoTime()'s Javadoc makes it clear that all observed invocations within a JVM (that is, across all threads) are monotonic:

The value returned represents nanoseconds since some fixed but arbitrary origin time (perhaps in the future, so values may be negative). The same origin is used by all invocations of this method in an instance of a Java virtual machine; other virtual machine instances are likely to use a different origin.

JVM/JDK implementation is responsible for ironing out the inconsistencies that could be observed when underlying OS utilities are called (e. g. those mentioned in Tom Anderson's answer).

The majority of other old answers to this question (written in 2009–2012) express FUD that was probably relevant for Java 5 or Java 6 but is no longer relevant for modern versions of Java.

It's worth mentioning, however, that despite JDK guarantees nanoTime()'s safety, there have been several bugs in OpenJDK making it to not uphold this guarantee on certain platforms or under certain circumstances (e. g. JDK-8040140, JDK-8184271). There are no open (known) bugs in OpenJDK wrt nanoTime() at the moment, but a discovery of a new such bug or a regression in a newer release of OpenJDK shouldn't shock anybody.

With that in mind, code that uses nanoTime() for timed blocking, interval waiting, timeouts, etc. should preferably treat negative time differences (timeouts) as zeros rather than throw exceptions. This practice is also preferable because it is consistent with the behaviour of all timed wait methods in all classes in java.util.concurrent.*, for example Semaphore.tryAcquire(), Lock.tryLock(), BlockingQueue.poll(), etc.

Nonetheless, nanoTime() should still be preferred for implementing timed blocking, interval waiting, timeouts, etc. to currentTimeMillis() because the latter is a subject to the "time going backward" phenomenon (e. g. due to server time correction), i. e. currentTimeMillis() is not suitable for measuring time intervals at all. See this answer for more information.

Instead of using nanoTime() for code execution time measurements directly, specialized benchmarking frameworks and profilers should preferably be used, for example JMH and async-profiler in wall-clock profiling mode.

初见终念 2024-07-20 07:54:33

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

查看 这句话来自 Java Sun 站点:

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

使用 Java RTS,所有基于时间的 API
(例如,定时器、周期
线程、截止日期监控等
第四)基于
高分辨率定时器。 并且,一起
通过实时优先级,他们可以
确保适当的代码将
在正确的时间执行
实时限制。 相比之下,
普通的 Java SE API 只提供几个
能够处理的方法
高分辨率时间,没有
保证在给定的执行
时间。 在之间使用 System.nanoTime()
代码中要执行的各个点
经过时间测量应该
始终准确。

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

该方法只能用于
测量经过的时间而不是
与系统的任何其他概念相关
或挂钟时间。 返回值
代表纳秒,因为一些
固定但任意的时间(也许在
未来,所以价值观可能是
消极的)。 该方法提供了
纳秒精度,但不是
必然是纳秒级的精度。 不
保证如何
价值观经常改变。 差异
在跨度更大的连续调用中
超过约 292.3 年(263
纳秒)将不准确
计算由于数值而经过的时间
溢出。

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

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

I did a bit of searching and found that if one is being pedantic then yes it might be considered useless...in particular situations...it depends on how time sensitive your requirements are...

Check out this quote from the Java Sun site:

The real-time clock and
System.nanoTime() are both based on
the same system call and thus the same
clock.

With Java RTS, all time-based APIs
(for example, Timers, Periodic
Threads, Deadline Monitoring, and so
forth) are based on the
high-resolution timer. And, together
with real-time priorities, they can
ensure that the appropriate code will
be executed at the right time for
real-time constraints. In contrast,
ordinary Java SE APIs offer just a few
methods capable of handling
high-resolution times, with no
guarantee of execution at a given
time. Using System.nanoTime() between
various points in the code to perform
elapsed time measurements should
always be accurate.

Java also has a caveat for the nanoTime() method:

This method can only be used to
measure elapsed time and is not
related to any other notion of system
or wall-clock time. The value returned
represents nanoseconds since some
fixed but arbitrary time (perhaps in
the future, so values may be
negative). This method provides
nanosecond precision, but not
necessarily nanosecond accuracy. No
guarantees are made about how
frequently values change. Differences
in successive calls that span greater
than approximately 292.3 years (263
nanoseconds) will not accurately
compute elapsed time due to numerical
overflow.

It would seem that the only conclusion that can be drawn is that nanoTime() cannot be relied upon as an accurate value. As such, if you do not need to measure times that are mere nano seconds apart then this method is good enough even if the resulting returned value is negative. However, if you're needing higher precision, they appear to recommend that you use JAVA RTS.

So to answer your question...no nanoTime() is not useless....its just not the most prudent method to use in every situation.

囍孤女 2024-07-20 07:54:33

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

No need to debate, just use the source.
Here, SE 6 for Linux, make your own conclusions:

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;
  }
}
攒一口袋星星 2024-07-20 07:54:33

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

Linux corrects for discrepancies between CPUs, but Windows does not. I suggest you assume System.nanoTime() is only accurate to around 1 micro-second. A simple way to get a longer timing is to call foo() 1000 or more times and divide the time by 1000.

绾颜 2024-07-20 07:54:33

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

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

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

Absolutely not useless. Timing aficionados correctly point out the multi-core problem, but in real-word applications it is often radically better than currentTimeMillis().

When calculating graphics positions in frame refreshes nanoTime() leads to MUCH smoother motion in my program.

And I only test on multi-core machines.

久光 2024-07-20 07:54:33

我发现使用 System.nanoTime() 报告的经过时间为负数。 需要明确的是,有问题的代码是:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

变量“elapsedNanos”的值为负值。 (我确信中间调用也花费了不到 293 年的时间,这是存储在 long 中的 nano 的溢出点:)

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

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

I have seen a negative elapsed time reported from using System.nanoTime(). To be clear, the code in question is:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

and variable 'elapsedNanos' had a negative value. (I'm positive that the intermediate call took less than 293 years as well, which is the overflow point for nanos stored in longs :)

This occurred using an IBM v1.5 JRE 64bit on IBM P690 (multi-core) hardware running AIX. I've only seen this error occur once, so it seems extremely rare. I do not know the cause - is it a hardware-specific issue, a JVM defect - I don't know. I also don't know the implications for the accuracy of nanoTime() in general.

To answer the original question, I don't think nanoTime is useless - it provides sub-millisecond timing, but there is an actual (not just theoretical) risk of it being inaccurate which you need to take into account.

不寐倦长更 2024-07-20 07:54:33

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

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

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

This doesn't seem to be a problem on a Core 2 Duo running Windows XP and JRE 1.5.0_06.

In a test with three threads I don't see System.nanoTime() going backwards. The processors are both busy, and threads go to sleep occasionally to provoke moving threads around.

[EDIT] I would guess that it only happens on physically separate processors, i.e. that the counters are synchronized for multiple cores on the same die.

未央 2024-07-20 07:54:33

不,不是...这仅取决于您的 CPU,请检查高精度事件计时器如何/为什么根据 CPU 的不同而对事物进行不同的处理。

基本上,阅读您的 Java 源代码并检查您的版本对该函数的作用,以及它是否适用于您将运行它的 CPU。

IBM 甚至建议您使用它来进行性能基准测试(2008 年帖子,但已更新)。

No, it's not... It just depends on your CPU, check High Precision Event Timer for how/why things are differently treated according to CPU.

Basically, read the source of your Java and check what your version does with the function, and if it works against the CPU you will be running it on.

IBM even suggests you use it for performance benchmarking (a 2008 post, but updated).

我不是你的备胎 2024-07-20 07:54:33

我链接到的内容本质上是相同的讨论,Peter Lawrey 提供了一个很好的答案。
为什么我使用 System.nanoTime( )?

许多人提到在 Java 中 System.nanoTime() 可能会返回负时间。 我为重复其他人已经说过的话表示歉意。

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

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

I am linking to what essentially is the same discussion where Peter Lawrey is providing a good answer.
Why I get a negative elapsed time using System.nanoTime()?

Many people mentioned that in Java System.nanoTime() could return negative time. I for apologize for repeating what other people already said.

  1. nanoTime() is not a clock but CPU cycle counter.
  2. Return value is divided by frequency to look like time.
  3. CPU frequency may fluctuate.
  4. When your thread is scheduled on another CPU, there is a chance of getting nanoTime() which results in a negative difference. That's logical. Counters across CPUs are not synchronized.
  5. In many cases, you could get quite misleading results but you wouldn't be able to tell because delta is not negative. Think about it.
  6. (unconfirmed) I think you may get a negative result even on the same CPU if instructions are reordered. To prevent that, you'd have to invoke a memory barrier serializing your instructions.

It'd be cool if System.nanoTime() returned coreID where it executed.

孤君无依 2024-07-20 07:54:33

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

Java is crossplatform, and nanoTime is platform-dependent. If you use Java - when don't use nanoTime. I found real bugs across different jvm implementations with this function.

萌逼全场 2024-07-20 07:54:33

Java 5 文档还建议使用此方法来达到相同的目的。

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

Java 5 API 文档

The Java 5 documentation also recommends using this method for the same purpose.

This method can only be used to
measure elapsed time and is not
related to any other notion of system
or wall-clock time.

Java 5 API Doc

月棠 2024-07-20 07:54:33

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

Also, System.currentTimeMillies() changes when you change your systems clock, while System.nanoTime() doesn't, so the latter is safer to measure durations.

往日情怀 2024-07-20 07:54:33

nanoTime 对于计时来说极其不安全。 我在基本的素性测试算法上进行了尝试,对于相同的输入,它给出的答案实际上相隔一秒。 别用那种可笑的方法。 我需要比获取时间毫秒更准确和精确的东西,但又不像nanoTime那么糟糕。

nanoTime is extremely insecure for timing. I tried it out on my basic primality testing algorithms and it gave answers which were literally one second apart for the same input. Don't use that ridiculous method. I need something that is more accurate and precise than get time millis, but not as bad as nanoTime.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文