Дрейф nanoTime против currentTimeMillis

Измерения времени относительно начальной точки, взятой с помощью nanoTime, и currentTimeMillis расходятся максимум через три минуты.

Это касается реализаций JDK11++ для Windows, возможно, более ранних. Linux, похоже, не затронут. Поскольку наиболее вероятным виновником является Windows, и я считаю, что currentTimeMillis является стабильной функцией времени, пока nanoTime выключен, я хотел бы знать, как Java получает эти временные метки.

В Linux мы видим различия, связанные с тем, как мы масштабируем милли до нано. Различия хорошо заметны и находятся в пределах миллисекундной точности.

Поскольку оба значения вычисляются относительно соответствующих им начальных точек, взятых с разницей в несколько микросекунд, можно было бы подумать, что они остаются в пределах миллисекундной точности, как это происходит в Linux. Любые отклонения в такте таймера не должны иметь значения, поскольку таймер служит только для обеспечения медленной, читаемой последовательности измерений, где точное время не имеет значения.

import java.text.*;
import java.util.*;
import java.util.concurrent.*;

public class ClockWatch {

    static final long StartNS = System.nanoTime();
    static final long StartMS = System.currentTimeMillis();

    public static void main(String[] args) {
        NumberFormat nf = NumberFormat.getNumberInstance( Locale.US);
        nf.setMaximumFractionDigits( 3);
        nf.setGroupingUsed( true);

        DateFormat df = new SimpleDateFormat( "HH:mm:ss");
        df.setTimeZone( TimeZone.getTimeZone( "GMT"));
        ScheduledExecutorService ses = new ScheduledThreadPoolExecutor( 1);

        Runnable compareJob = () -> {
            long nowNS = System.nanoTime() - StartNS;
            long nowMS = System.currentTimeMillis() - StartMS;
            long tookNS = ( System.nanoTime() - StartNS) - nowNS;
            long diffNS = 1_000_000 * nowMS - nowNS;
            double driftPercent = 100.0 * diffNS / nowNS;
            System.out.println( df.format( new Date( nowMS))
                + " currentTimeMillis is "
                + ( diffNS < 0 ? "" : " ")
                + nf.format( 1e-6 * diffNS)
                + " ms faster than nanoTime, took "
                + nf.format( tookNS) + " ns, drift "
                + nf.format( driftPercent) + " % since start"
            );
        };
        ses.scheduleAtFixedRate( compareJob, 1, 3, TimeUnit.SECONDS);
    }
}

Причина, по которой меня это беспокоит, заключается в том, что я хочу повторно использовать существующую временную метку nano и преобразовать ее в эквивалент currentTimeMillis, не вызывая последнюю. Я знаю, под Windows на последнем оборудовании это занимает всего 4 нс.


Возможно, мне следует предоставить дополнительную информацию после прочтения комментариев. Я знаю о причудах nanoTime и currentTimeMillis. Возможно, я избавлюсь от обоих в пользу Instant.now, так что спасибо, что напомнили. Моя основная причина заключалась в том, что я хотел регистрировать события, используя log4j2 (настроенный с полной меткой времени). В один и тот же файл асинхронно входит множество потоков. Это одна из самых больших машин. При определенных обстоятельствах потоки журналирования могут засоряться, поскольку log4j не успевает за ними, тем самым замедляя потоки журналирования. Чтобы избежать этой проблемы, я переместил вызовы log4j в очередь, обслуживаемую другим потоком. Поскольку теперь я ожидал, что этот поток будет вызывать задержки в очереди, я хотел добавить к этим сообщениям журнала еще одну временную метку, отражающую время, когда они были добавлены в очередь журнала. Для этой внутренней временной метки у меня есть существующая нано-метка времени, которую я хотел повторно использовать таким образом, чтобы она соответствовала временной метке log4j как части шаблона сообщения журнала.

С тех пор я решил эту проблему, получив еще одну временную метку currentTimeMillis, но это потребовало добавления еще одного длинного поля и еще одного измерения, чего мне хотелось бы избежать. Поскольку мне нужно, чтобы мои измерения были совместимы с тем, что использует log4j, похоже, мне нужно поэкспериментировать с Instant.now прямо сейчас.

В исходном коде System.nanoTime в проекте OpenJDK говорится, что этот метод реализован изначально. Я не могу найти собственную реализацию на GitHub. Эта реализация может решить вашу проблему.

Basil Bourque 18.08.2024 23:41

Насколько я могу судить, nanoTime вызывает os::javaTimeNanos и currentTimeMillis вызывает os::javaTimeMillis (обе ссылки для os_windows.cpp из OpenJDK). А затем они вызывают QueryPerformanceCounter и GetSystemTimeAsFileTime из Win32 соответственно.

Slaw 18.08.2024 23:55

@Slaw Спасибо за ваши усилия. Вы хотите сказать, что различия были разными, в большей или меньшей степени? Так это не накопительный дрейф? (Мне любопытно, хотя этот вопрос на самом деле спорный, поскольку в ответе Вассермана эти два измерения служат совершенно разным целям.)

Basil Bourque 19.08.2024 00:04

@Basil Правильно, я не вижу никаких накопленных отклонений на моем компьютере с Windows. Длительность nanoTime была в среднем примерно на 0,6 мс короче, чем длительность currentTimeMillis (измерялась каждые 3 секунды в течение 15 минут). Но вы правы, это не имеет особого значения; то, что я вижу, не гарантировано, и в любом случае они служат разным целям.

Slaw 19.08.2024 07:09

Привет, H, на самом деле ваш вопрос сложен, потому что невозможно сравнить обе меры, поскольку StartMS влияет на nomNs - StartNS (он содержится), а nowNS влияет на nowMS - startMs (он также содержится), поэтому любая попытка сравнения, бесплодно.

Marce Puente 19.08.2024 12:27

Каков именно результат? Как время расходится? (Я не вижу этого в своей работе). Кроме того, какие версии/сборки JDK и Windows вы используете?

apangin 20.08.2024 01:11

Извините, что вернулся к этому только сейчас. Как я уже сказал, это происходило со мной на любой машине с Windows, которую я пробовал, будь то последняя версия сервера или рабочей станции. Версия JDK (пост 11) также не имела значения. Поскольку изначально я хотел сообщить об ошибке JVM, я также попробовал последнюю сборку JDK-24. Везде одинаково. Написав этот вопрос, я заметил аналогичный вопрос в отношении macOS, который, по-видимому, тоже использует два разных источника времени. Кажется, Linux использует одно и то же для нано и миллис. Вызовы также занимают одинаковое время.

Ralf H 23.08.2024 02:38
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
7
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

https://stackoverflow.com/a/64961281/869736 не совсем ответ на этот вопрос, но включает ключевые важные факты. В частности, аппаратные часы большинства компьютеров настроены на периодическое получение поправок с сервера времени. currentTimeMillis будет наблюдать эти поправки как скачки или паузу; nanoTime вообще не будет их наблюдать. currentTimeMillis получает доступ к времени ОС с помощью любых средств, предоставляемых ОС.

Вы правильно заметили, что cTM и nanoTime будут расходиться со временем, и даже не на огромное количество времени. Это неизбежный факт; Я бы не ожидал, что он будет идеальным бесконечно, независимо от вашей ОС.

В общем, лечение должно заключаться в том, чтобы рассматривать эти измерения как совершенно разные измерения, которые редко совпадают. Всегда используйте:

Метод Статус Цель Разрешение System.nanoTime современный Прошедшее время наносекунды Instant.now современный Текущий момент на часах наносекунды

Текущий момент зафиксирован в микросекундах в Java 9+, в миллисекундах в Java 8. System.currentTimeMillis наследие Текущий момент на часах миллисекунды

Чтобы завершить ответ: @RalfH - вы объясняете основную цель вашего вопроса: «Я хочу повторно использовать результат nanoTime как способ получить временную метку на основе cTM, не платя за это снижение производительности при фактическом вызове cTM». Ну, вы просто не можете этого сделать, и нет способа это исправить. Вы должны заплатить этот штраф или найти способ больше не нуждаться в cTM для того, для чего вам нужен cTM. По причинам, которые Луи объясняет здесь. Невозможно cTMize результат nanoTime ни в одной ОС. Вы заметили, что в Linux точность составляет миллисекунды... это тоже не гарантировано.

rzwitserloot 19.08.2024 00:42

принято к упоминанию Instant.now :)

Ralf H 23.08.2024 03:08

Другие вопросы по теме