Является ли System.nanoTime () полностью бесполезным?


153

Как задокументировано в блоге Beware для System.nanoTime () в Java , в системах x86 Java System.nanoTime () возвращает значение времени с помощью счетчика, специфичного для процессора . Теперь рассмотрим следующий случай, который я использую для измерения времени звонка:

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

Теперь в многоядерной системе может быть так, что после измерения времени1 поток назначается другому процессору, чей счетчик меньше, чем у предыдущего процессора. Таким образом, мы можем получить значение в time2, которое меньше, чем time1. Таким образом, мы получили бы отрицательное значение в timeSpent.

Рассматривая этот случай, не правда ли, что System.nanotime пока бесполезен?

Я знаю, что изменение системного времени не влияет на nanotime. Это не проблема, которую я описал выше. Проблема в том, что каждый ЦП будет держать свой счетчик, так как он был включен. Этот счетчик может быть ниже на втором процессоре по сравнению с первым процессором. Поскольку поток может быть запланирован ОС на второй ЦП после получения time1, значение timeSpent может быть неправильным и даже отрицательным.


2
У меня нет ответа, но я согласен с вами. Может быть, это следует считать ошибкой в ​​JVM.
Аарон Дигулла

2
эта запись неверна и не использует TSC медленно, но вы должны жить с: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6440250 Также TSC может быть полезен через гипервизор, но затем он снова медленный.
bestsss

1
И, конечно, вы можете работать на виртуальной машине, где центральный процессор может отображаться в середине сеанса: D
Limited Atonement

Ответы:


206

Этот ответ был написан в 2011 году с точки зрения того, что на самом деле делал Sun JDK того времени, работающего на операционных системах того времени. Это было давным-давно! Ответ Левентова предлагает более современную перспективу.

Это сообщение неверно и nanoTimeбезопасно. Есть комментарий к сообщению, которое ссылается на сообщение в блоге Дэвида Холмса , парня из Sun, работающего в режиме реального времени. Это говорит:

System.nanoTime () реализован с использованием API QueryPerformanceCounter / QueryPerformanceFrequency [...] Механизм по умолчанию, используемый QPC, определяется уровнем аппаратной абстракции (HAL) [...]. Это значение по умолчанию изменяется не только на оборудовании, но и на всей ОС версии. Например, пакет обновления 2 (SP2) для Windows XP изменил использование таймера управления питанием (PMTimer), а не счетчика меток времени процессора (TSC) из-за проблем с синхронизацией TSC на разных процессорах в системах SMP и из-за его частоты может варьироваться (и, следовательно, его отношение к прошедшему времени) в зависимости от настроек управления питанием.

Итак, в Windows это было проблемой до WinXP SP2, но не сейчас.

Я не могу найти часть II (или более), в которой говорится о других платформах, но в этой статье есть замечание, что Linux столкнулся и решил ту же проблему аналогичным образом, со ссылкой на FAQ для 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 всегда имеет синхронизированную временную базу, так что это не должно быть проблемой.

Итак, если ссылка Холмса может быть прочитана как подразумевающая эти nanoTimeвызовы clock_gettime(CLOCK_REALTIME), то она безопасна с ядра 2.6.18 на x86 и всегда на PowerPC (потому что IBM и Motorola, в отличие от Intel, фактически знают, как проектировать микропроцессоры).

К сожалению, нет никаких упоминаний о SPARC или Solaris. И, конечно, мы понятия не имеем, что делают JVM IBM. Но Sun JVM на современных Windows и Linux понимают это правильно.

РЕДАКТИРОВАТЬ: Этот ответ основан на источниках, которые он цитирует. Но я все еще волнуюсь, что это может быть совершенно неправильно. Еще одна актуальная информация была бы действительно ценной. Я только что натолкнулся на ссылку на четырехлетнюю новую статью о часах Linux, которая может быть полезна.


13
Даже WinXP SP2, похоже, страдает. Запустив исходный пример кода, void foo() { Thread.sleep(40); }я получил отрицательное время (-380 мс!) С использованием одного Athlon 64 X2 4200+процессора
Люк Ушервуд,

Я не думаю, что есть что-то новое об этом, wrt. поведение на Linux, BSD или других платформах?
Томер Габель

6
Хороший ответ, следует добавить ссылку на более свежее исследование этой темы: shipilev.net/blog/2014/nanotrusting-nanotime
Nitsan Wakart

1
@ SOFe: О, это позор. К счастью, в веб-архиве . Я посмотрю, смогу ли я отследить текущую версию.
Том Андерсон

1
Примечание: OpenJDK не придерживался спецификации до OpenJDK 8u192, см. Bugs.openjdk.java.net/browse/JDK-8184271 . Убедитесь, что вы используете хотя бы свежую версию OpenJDK 8 или OpenJDK 11+.
Левентов

35

Я немного искал и обнаружил, что если человек педантичен, то да, это может считаться бесполезным ... в определенных ситуациях ... это зависит от того, насколько чувствительны ваши требования к времени ...

Проверьте эту цитату с сайта Java Sun:

Часы реального времени и System.nanoTime () основаны на одном и том же системном вызове и, следовательно, на одних и тех же часах.

В Java RTS все основанные на времени API-интерфейсы (например, Таймеры, Периодические потоки, Контроль сроков и т. Д.) Основаны на таймере высокого разрешения. И вместе с приоритетами в реальном времени они могут гарантировать, что соответствующий код будет выполнен в нужное время для ограничений в реальном времени. Напротив, обычные API Java SE предлагают всего несколько методов, способных обрабатывать время с высоким разрешением, без гарантии выполнения в данный момент времени. Использование System.nanoTime () между различными точками в коде для выполнения измерений прошедшего времени всегда должно быть точным.

У Java также есть предостережение для метода nanoTime () :

Этот метод может использоваться только для измерения прошедшего времени и не связан с каким-либо другим понятием системного или настенного времени. Возвращаемое значение представляет наносекунды с некоторого фиксированного, но произвольного времени (возможно, в будущем, поэтому значения могут быть отрицательными). Этот метод обеспечивает наносекундную точность, но не обязательно наносекундную точность. Нет никаких гарантий относительно того, как часто меняются значения. Различия в последовательных вызовах, которые охватывают более приблизительно 292,3 года (2 63 наносекунды), не будут точно вычислять истекшее время из-за числового переполнения.

Казалось бы, единственный вывод, который можно сделать, состоит в том, что на nanoTime () нельзя полагаться как на точное значение. Таким образом, если вам не нужно измерять время, которое составляет всего лишь наносекунды, этот метод достаточно хорош, даже если полученное возвращаемое значение отрицательное. Однако, если вам нужна более высокая точность, они рекомендуют вам использовать JAVA RTS.

Таким образом, чтобы ответить на ваш вопрос ... никакой nanoTime () не бесполезен .... это просто не самый разумный метод для использования в любой ситуации.


3
> этот метод достаточно хорош, даже если полученное возвращаемое значение отрицательное. Я не понимаю, если значение в timepent является отрицательным, то как это вообще полезно при измерении времени, используемого в foo ()?
Пдева

3
Это нормально, потому что все, что вас беспокоит, это абсолютная величина разницы. то есть, если ваше измерение - это время t, где t = t2 - t1, то вы хотите знать | t | .... так что, если значение будет отрицательным ... даже при многоядерной проблеме влияние редко будет незначительным наносекунды в любом случае.
мезоид

3
Для резервного копирования @Aaron: и t2, и t1 могут быть отрицательными, но (t2-t1) не должно быть отрицательными.
JFS

2
Аарон: это именно то, что моя точка зрения. t2-t1 никогда не должно быть отрицательным, иначе у нас будет ошибка.
Пдева

12
@pdeva - но вы не понимаете, что говорит доктор. Вы поднимаете не проблему. Есть некоторый момент времени, который считается "0". Значения, возвращаемые nanoTime (), являются точными относительно этого времени. Это монотонно увеличивающийся график времени. Вы можете просто получить серию чисел из отрицательной части этой временной шкалы. -100, -99, -98(Очевидно , гораздо большие значения на практике). Они движутся в правильном направлении (увеличиваются), поэтому здесь нет проблем.
ToolmakerSteve

18

Не нужно спорить, просто используйте источник. Вот, SE 6 для 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;
  }
}

7
Это полезно, только если вы знаете, что делает используемый API. Используемый API реализован операционной системой; этот код является правильным по отношению к спецификация используемого API (clock_gettime / gettimeofday), но, как уже указывали другие, некоторые устаревшие операционные системы имеют ошибочные реализации.
Blaisorblade

18

Начиная с Java 7 System.nanoTime()гарантируется безопасность по спецификации JDK. System.nanoTime()Javadoc дает понять, что все наблюдаемые вызовы в JVM (то есть во всех потоках) являются монотонными:

Возвращаемое значение представляет наносекунды с некоторого фиксированного, но произвольного времени начала (возможно, в будущем, поэтому значения могут быть отрицательными). Один и тот же источник используется всеми вызовами этого метода в экземпляре виртуальной машины Java; другие экземпляры виртуальной машины могут использовать другое происхождение.

Реализация JVM / JDK отвечает за устранение несоответствий, которые могут наблюдаться при вызове базовых утилит ОС (например, упомянутых в ответе Тома Андерсона ).

Большинство других старых ответов на этот вопрос (написано в 2009–2012 гг.) Выражают FUD, который, вероятно, имел отношение к Java 5 или Java 6, но больше не относится к современным версиям Java.

Однако следует отметить, что, несмотря на гарантии nanoTime()безопасности JDK, в OpenJDK было несколько ошибок, из-за которых он не поддерживал эту гарантию на определенных платформах или при определенных обстоятельствах (например, JDK-8040140 , JDK-8184271 ). На nanoTime()данный момент в OpenJDK нет открытых (известных) ошибок , но обнаружение новой такой ошибки или регрессия в новой версии OpenJDK никого не должно шокировать.

Имея это в виду, код, который использует nanoTime()временную блокировку, интервальное ожидание, тайм-ауты и т. Д., Предпочтительно должен рассматривать отрицательные временные разницы (тайм-ауты) как нули, а не как исключения. Эта практика также является предпочтительной , поскольку он согласуется с поведением всех синхронизированных методов ожидания во всех классах в java.util.concurrent.*, например Semaphore.tryAcquire(), Lock.tryLock(), BlockingQueue.poll()и т.д.

Тем не менее, nanoTime()все же следует отдать предпочтение реализации временных блокировок, интервалов ожидания, тайм-аутов и т. Д., currentTimeMillis()Поскольку последний подвержен явлению «время идет назад» (например, из-за коррекции времени сервера), то currentTimeMillis()есть не подходит для измерения временных интервалов. вообще. Смотрите этот ответ для получения дополнительной информации.

Вместо nanoTime()непосредственного использования для измерения времени выполнения кода, предпочтительно использовать специализированные эталонные тестовые среды и профилировщики, например, JMH и асинхронный профилировщик в режиме профилирования настенных часов .


10

Отказ от ответственности: я разработчик этой библиотеки

Вам может понравиться это лучше:

http://juliusdavies.ca/nanotime/

Но он копирует файл DLL или Unix .so (общий объект) в домашний каталог текущего пользователя, чтобы он мог вызывать JNI.

Некоторая справочная информация находится на моем сайте по адресу:

http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html


@Julius Вы удалили библиотеку со своего сайта?
Нитин Дандриял

6

Linux исправляет расхождения между процессорами, а Windows - нет. Я полагаю, вы предполагаете, что System.nanoTime () является точным только с точностью до 1 микросекунды. Простой способ увеличить время - это вызвать foo () 1000 или более раз и разделить время на 1000.


2
Не могли бы вы предоставить ссылку (поведение в Linux и Windows)?
JFS

К сожалению, предлагаемый метод обычно очень неточен, потому что каждое событие, попадающее в слот обновления настенных часов +/- 100 мс, часто возвращает ноль для операций с точностью до секунды. Сумма из 9 операций, каждая из которых имеет длительность ноль, ну, в общем, ноль, деленная на девять, это ... ноль. И наоборот, использование System.nanoTime () обеспечит относительно точные (ненулевые) длительности событий, которые затем суммируются и делятся на количество событий и обеспечивают очень точное среднее значение.
Даррелл Тиг,

@DarrellTeague, суммирующий 1000 событий и суммирующий их, соответствует времени окончания.
Питер Лори

@DarrellTeague System.nanoTime () с точностью до 1 микросекунды или лучше (не 100 000 микросекунд) в большинстве систем. Усреднение многих операций уместно только тогда, когда у вас есть несколько микросекунд, и только в определенных системах.
Питер Лори

1
Извинения за то, что возникла путаница в отношении языка, используемого при «суммировании» событий. Да, если время отмечено в начале, скажем, 1000 секундных операций, они запускаются, а затем время снова отмечается в конце и делится - это будет работать для некоторой разрабатываемой системы, чтобы получить хорошее приближение продолжительности для данного мероприятие.
Даррелл Тиг

5

Абсолютно не бесполезно. Поклонники синхронизации правильно указывают на проблему с многоядерностью, но в приложениях с реальными словами это часто радикально лучше, чем currentTimeMillis ().

При вычислении графических позиций в обновлениях кадров nanoTime () приводит к НАМНОГО более плавному движению в моей программе.

И я тестирую только на многоядерных машинах.


5

Я видел отрицательное прошедшее время, сообщенное при использовании System.nanoTime (). Чтобы быть ясным, рассматриваемый код:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

и переменная 'elapsedNanos' имела отрицательное значение. (Я уверен, что промежуточный вызов также занял менее 293 лет, что является точкой переполнения для наноса, хранящегося в longs :)

Это произошло при использовании IBM v1.5 JRE 64bit на оборудовании IBM P690 (многоядерный), работающем под управлением AIX. Я видел эту ошибку только один раз, поэтому она встречается крайне редко. Я не знаю причину - это аппаратная проблема, дефект JVM - я не знаю. Я также не знаю последствия для точности nanoTime () в целом.

Чтобы ответить на первоначальный вопрос, я не думаю, что nanoTime бесполезен - он обеспечивает синхронизацию менее миллисекунды, но существует реальный (не только теоретический) риск того, что он будет неточным, что вам необходимо учитывать.


Увы, похоже, проблема с ОС / оборудованием. В документации говорится, что основные значения могут быть отрицательными, но (больший отрицательный минус меньший отрицательный) все равно должен быть положительным значением. В действительности предполагается, что в том же потоке вызов nanoTime () всегда должен возвращать положительное или отрицательное значение. За многие годы никогда не видел этого в различных системах Unix и Windows, но это кажется возможным, особенно если аппаратное обеспечение / ОС разделяют эту, казалось бы, атомарную работу между процессорами.
Даррелл Тиг

@BasilVandegriend это не ошибка нигде. Согласно документации, редко второй System.nanoTime () в вашем примере может работать на другом процессоре, и значения nanoTime, рассчитанные на этом процессоре, могут оказаться ниже значений, рассчитанных на первом процессоре. Таким образом, -ve значение для elapsedNanos возможно
бесконечное

2

Это не проблема для Core 2 Duo под управлением Windows XP и JRE 1.5.0_06.

В тесте с тремя потоками я не вижу System.nanoTime (), идущий в обратном направлении. Процессоры оба заняты, и потоки иногда засыпают, чтобы провоцировать перемещение потоков.

[EDIT] Я бы предположил, что это происходит только на физически отдельных процессорах, то есть что счетчики синхронизируются для нескольких ядер на одном кристалле.


2
Это, вероятно, не будет происходить постоянно, но благодаря способу реализации nanotime () возможность всегда есть.
Пдева

Я предполагаю, что это происходит только на физически отдельных процессорах, то есть счетчики синхронизируются для нескольких ядер на одном кристалле.
звездный синий

Даже это зависит от конкретной реализации, IIRC. Но об этом должна заботиться ОС.
Blaisorblade

1
Счетчики RDTSC на нескольких ядрах одного и того же процессора x86 не обязательно синхронизированы - некоторые современные системы позволяют различным ядрам работать на разных скоростях.
Жюль

2

Нет, это не так ... Это зависит только от вашего процессора, проверьте High Precision Event Timer, чтобы узнать, как / почему вещи обрабатываются по-разному в зависимости от процессора.

По сути, прочитайте исходный код Java и проверьте, что ваша версия делает с функцией, и если она работает с процессором, вы будете на ней работать.

IBM даже предлагает использовать его для сравнительного анализа производительности (публикация 2008 г., но обновленная).


Как и вся реализация, определено поведение, "будьте бдительны!"
Дэвид Шмитт

2

Я ссылаюсь на то, что по сути является тем же обсуждением, где Питер Лори дает хороший ответ. Почему я получаю отрицательное прошедшее время, используя System.nanoTime ()?

Многие люди упоминали, что в Java System.nanoTime () может возвращать отрицательное время. Я прошу прощения за повторение того, что уже сказали другие люди.

  1. nanoTime () - это не часы, а счетчик тактов процессора.
  2. Возвращаемое значение делится на частоту, чтобы выглядеть как время.
  3. Частота процессора может колебаться.
  4. Когда ваш поток запланирован на другом процессоре, есть шанс получить nanoTime (), что приведет к отрицательной разнице. Это логично. Счетчики между процессорами не синхронизированы.
  5. Во многих случаях вы можете получить весьма вводящие в заблуждение результаты, но вы не сможете сказать, потому что дельта не отрицательна. Подумай об этом.
  6. (не подтверждено) Я думаю, что вы можете получить отрицательный результат даже на том же процессоре, если инструкции будут переупорядочены. Чтобы предотвратить это, вы должны вызвать барьер памяти, сериализующий ваши инструкции.

Было бы здорово, если бы System.nanoTime () возвращал coreID там, где он выполнялся.


1
Все пункты кроме 3. и 5. неверны. 1. nanoTime () - это не счетчик циклов ЦП, это нано время . 2. Как создается значение nanoTime, зависит от платформы. 4. Нет, разница не может быть отрицательной, согласно спецификации nanoTime (). Предполагая, что в OpenJDK нет ошибок по сравнению с nanoTime (), и на данный момент нет известных неразрешенных ошибок. 6. Вызовы nanoTime нельзя переупорядочить в потоке, потому что это собственный метод, а JVM соблюдает порядок программы. JVM никогда не переупорядочивает вызовы собственных методов, потому что не знает, что происходит внутри них, и поэтому не может доказать, что такие перестановки будут безопасными.
Левентов

Относительно 5. Результаты различий в nanoTime () могут действительно вводить в заблуждение, но не по причинам, представленным в других пунктах этого ответа. Но скорее по причинам, представленным здесь: shipilev.net/blog/2014/nanotrusting-nanotime
leventov

По иронии судьбы, в отношении 6. в OpenJDK была ошибка, в частности, из-за переупорядочения: bugs.openjdk.java.net/browse/JDK-8184271 . В OpenJDK nanoTime () является встроенным, и было разрешено переупорядочить, что было ошибкой.
leventov

@leventov, так безопасно ли использовать nanotime ()? это означает, что он не может возвращать отрицательные значения и является точным с точки зрения времени. Я не вижу смысла разоблачать функцию API, которая полна проблем. Этот пост является доказательством, начатым в 2009 году и до сих пор прокомментированным в 2019 году. Что касается критически важных вещей, я полагаю, что люди полагаются на такие временные карты, как Symmetricom
Vortex

1
Ваш # 4 говорит о отрицательной разнице , а не о значениях: «есть шанс получить nanoTime (), что приведет к отрицательной разнице».
Левентов

1

Java является кроссплатформенной, а nanoTime зависит от платформы. Если вы используете Java - когда не используйте nanoTime. Я нашел реальные ошибки в различных реализациях jvm с этой функцией.


0

Документация Java 5 также рекомендует использовать этот метод для той же цели.

Этот метод может использоваться только для измерения прошедшего времени и не связан с каким-либо другим понятием системного или настенного времени.

Java 5 API Doc


0

Кроме того, System.currentTimeMillies()меняется, когда вы меняете системные часы, а System.nanoTime()не изменяет , поэтому последний безопаснее измерять длительности.


-3

nanoTimeкрайне небезопасно для времени. Я опробовал его на моих основных алгоритмах тестирования простоты, и он дал ответы, которые были буквально на расстоянии одной секунды для одного и того же ввода. Не используйте этот нелепый метод. Мне нужно что-то более точное и точное, чем получить время Миллис, но не так плохо, как nanoTime.


без источника или лучшего объяснения этот комментарий бесполезен
ic3
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.