Home >Java >Why does the elapsed time calculated over a large time window differ by up to 100+ milliseconds between System.currentTimeMillis and System.nanoTime

Why does the elapsed time calculated over a large time window differ by up to 100+ milliseconds between System.currentTimeMillis and System.nanoTime

王林
王林forward
2024-02-09 10:24:08935browse

php editor Apple will explain why the elapsed time calculated on a large time window is up to 100 milliseconds different between System.currentTimeMillis and System.nanoTime. This issue involves the underlying mechanism of the computer system and the scheduling method of the operating system. When calculating elapsed time, System.currentTimeMillis uses the wall clock time provided by the operating system, while System.nanoTime uses CPU clock cycles. Due to the operating system's scheduling mechanism and the asynchronous nature of the hardware clock, these two timing methods will cause time differences. The specific reasons and solutions will be introduced in detail in the following articles.

Question content

I am trying to create a ksuid that can do microsecond precession sorting in the same jvm, since currenttimemillis only gives millisecond precision, consider using currenttimemillis() and nanotime() Used together, the nanotime difference in the number of microseconds elapsed since the first time instant is calculated by collecting the values ​​starting at the first instant and the second instant. Use the elapsed time calculated from nanotime and add it to the initial instant milliseconds so that we can calculate the epoch with microsecond precision at any instant after the jvm starts (all within the same jvm).

//Static collection of initial nanotime and currentmillis Final long inittimenanos = system.nanotime(); Final long inittimemillis = system.currenttimemillis();

//From here on, when the current time with micro/nanosecond precision is required, calculate the elapsed time from nanotime and add the elapsed milliseconds to initialmillis, and the remaining elapsed time gives the time with microsecond precision

final long elapsednanotime = system.nanotime() - inittimenanos;final double elapsedmillisfromnanotime = elapsednanos / (1000000.0);

//Monotonically increasing timestamp milliseconds since jvm started (epoch, we may not call it epoch) final long calculation current time millis = inittimemillis elapsedmillisfromnanotime; final long nanosprecision = elapsednanos % 1000000; //Additional time precision from nanotime

Consider using these values ​​to generate a monotonically increasing ksuid, which can approximate the current time but is guaranteed to be sorted according to the creation time within the same jvm. Since currenttimemillis does not provide a monotonically increasing timestamp guarantee, consider using this method to generate A monotonically increasing timestamp that is approximately close to the real timestamp (maybe a few milliseconds different, unless leap second adjustments are made to the epoch time). The elapsed time calculated using epoch and nanotime was expected to differ by a few milliseconds, but the actual difference varied very frequently, and when I ran the test below for 48 hours, I observed a difference of up to 150 milliseconds between the two. In most cases, the elapsed time calculated using nanotime is higher than the elapsed time calculated using currenttimemillis, and the observed times range from -2 milliseconds to 150 milliseconds.

final long elapsedmillis = system.currenttimemillis() - inittimemillis; //Elapsed time milliseconds from system.currenttimemillis() The final double difference millis = elapsedmillisfromnanotime - elapsedmillis; //Elapsed time variance

Am I missing anything about jvm timing guarantees, is the way of calculating monotonically increasing approximate timestamps wrong? (This is not used as the real epoch in the system, only to generate the uuid which also represents the approximate timestamp instant within the same jvm).

//Test class

package org.example;

import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws Exception {
        final long initTimeNanos = System.nanoTime();
        final long initTimeMillis = System.currentTimeMillis();
        System.out.println("Nanos: " + initTimeNanos);
        System.out.println("Millis: " + initTimeMillis);

        while (true) {
            final long currentNanos = System.nanoTime();
            final long elapsedNanos = currentNanos - initTimeNanos;
            final double elapsedMillisFromNanos = elapsedNanos / 1000000.0;
            final long elapsedMillis = System.currentTimeMillis() - initTimeMillis;
            final double varianceMillis = elapsedMillisFromNanos - elapsedMillis;
            if (Math.abs(varianceMillis) > 1) {
                System.out.printf("\nVariance Observed: %.6f\n", varianceMillis);
                System.out.printf("Elapsed Time: %.6fms (from System.nanoTime)\n", elapsedMillisFromNanos);
                System.out.printf("Elapsed Time: %dms (from System.currentTimeMillis)\n", elapsedMillis);
            }
            if (elapsedMillis > TimeUnit.HOURS.toMillis(48)) {
                break;
            }
            Thread.sleep(5000);
            System.out.print(".");
        }
    }
}

Why does the elapsed time variance keep changing? What is the maximum variance we can expect if the jvm runs continuously for a year (any jvm guarantees an upper or lower bound on this, tested with mac and windows, mac gave a slow increase in variance, windows was much faster) ?

I would expect the elapsed time to vary by less than 10 milliseconds and the changes to occur less frequently. But the actual observation is that the variance is constantly changing, fluctuating up and down, with up to 150 milliseconds observed within 48 hours

Solution

One explanation is time smearing based on ntp. More generally, nanotime and ctm measure completely different things and you can't mix them.

nanotime has an arbitrary 0 point (getting a nanotime of 0 has no special meaning), so there is no need to call it at all other than to compare what it returns with the results of different nanotime calls. nanotime tracks elapsed time, that's all.

system.ctm Gets the system clock. If you use the posix date command or edit the system's time settings in System Settings, this will not affect nanotime, but will change what system.ctm returns . ctm is also generally much slower than nanotime. ctm also has a well-defined 0 - representing midnight UTC January 1, 1970.

Problem: "I want the current time to match the system clock with nanosecond precision" is not possible on the jvm.

时间涂抹是指某些网络时间守护程序注意到您的系统时钟略有偏差,并且不只是将系统时钟编辑到正确的时间,而是引入了涂抹:假设您比实时时间“提前”了 400 毫秒。 ntp 可以将您的时钟向后设置 400 毫秒,但许多日志记录系统假设 system.ctm 不会向后移动(这是一个不正确但广泛应用的假设)。

这可以通过让时间放慢一段时间来“修复”:每经过 100 毫秒,ntp 守护进程就会将时钟“保持”在同一毫秒上一毫秒。每 100 毫秒就赶上 1 毫秒,因此在 400,000 毫秒(仅 400 秒)内,时钟恢复与网络同步,并且日志记录根本不受影响。

但是,这显然会完全破坏 system.ctm 和 nanotime 之间的任何关系!

大多数 ntp 都是这样涂抹的(它们也会向前涂抹 - 如果您的系统时钟落后,它不仅仅会向前跳跃:这会使日志撒谎(使其看起来就像两个事件之间存在一些间隙)比实际大得多),因此每 100 毫秒,ntp 就会使时钟跳过一毫秒,类似这样的事情,以赶上。

...但我需要单调递增的时间戳!

那么 nanotime 就无关紧要了。不要使用它。

拥有一些提供 id 的集中“商店”。一种实现:

class TimeStore {
  long lastVal = 0L;

  public synchronized long get() {
    long mark = System.currentTimeMillis() << 4;
    return lastVal = Math.max(mark, lastVal + 1);
  }
}

这将返回当前时间,左移 4 位,并将填充此移位“释放”的 16 个值,以便能够在同一时间生成单调递增值,最多 16 次;同一毫秒内的任何进一步请求都会潜入下一毫秒。

尽管如此,这可能并不比nanotime慢。

rzwitserloot的回答是正确的。我将向您提供我对各种问题的看法。

currenttimemillisnanotime 无关

system.currenttimemillissystem.nanotime 彼此无关。

  • currenttimemillis 从主机的硬件时钟单元检索当前日期和时间,由主机操作系统管理。
  • nanotime 来自主机 cpu 保存的计数。

currenttimemillis

所以首先要了解人类的年月日和时分秒的概念。传统计算机中使用的时钟硬件的分辨率有限,有些是毫秒,有些是微秒,但没有纳秒。

由于各种原因,本次通话报告的日期和时间可能会有所不同。其中一个原因是,在电池电量耗尽的情况下启动的计算机会将其时钟重置为默认时刻,直到通过调用时间服务器进行纠正。另一个原因是系统管理员或用户可能会更改日期时间。还有一个原因是硬件时钟可能无法很好地保持时间,并且会通过调用时间服务器在更新之间运行得快或慢。

nanotime

nanotime 调用以纳秒计数形式告知经过的时间。但这个流逝的时间与日历和墙上的时钟无关。此调用仅返回经过的纳秒的单调计数。

这个计数非常准确,因为它来自计算机的“跳动的心脏”。返回的数量始终在增加,直到达到 long.max_value,然后环绕到 long.min_value。这给出了大约 292 年的范围,但这并不意味着从现在起 292 年。计数的起点未指定。在某些 java 实现中,您可能会看到计数在计算机启动时开始。但并不能保证这一点。

问题

您可能会考虑寻找 新 6、7、8 版本 的实现a href="https://en.wikipedia.org/wiki/universally_unique_identifier" rel="nofollow noreferrer">uuid 被提议给 ietf 进行标准化。

currenttimemillis 在现代 java 中已被 java.time.instant 类取代。调用 instant.now 捕获 utc 中的当前时刻。

java 9+ 中的常见实现以微秒为单位报告,但在 java 8 中以毫秒为单位。 instant 类能够以纳秒为单位。

Smart idea. But it's not realistic. As mentioned above, currenttimemillis and nanotime are not related. The bottom line is, currenttimemillis results may vary for a variety of reasons. Also, the nanotime count may change with each boot of the host machine, and certainly will not correspond to any nanotime calls made on other machines.

Yes, you are ignoring the fact that the java specification does not make any of the guarantees you seem to assume.

The only guarantee in the

javadoc is that nanotime is "high resolution" and at least as good as currenttimemillis. And there's no guarantee when you can expect the end of 292.

At the same time, currenttimemillis is a moving target that can change at any time, moving forward or backward.

This is unpredictable.

The java specification makes no such guarantee.

The above is the detailed content of Why does the elapsed time calculated over a large time window differ by up to 100+ milliseconds between System.currentTimeMillis and System.nanoTime. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete