Является ли System.nanoTime() полностью бесполезным?
Как задокументировано в блоге 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 может быть неправильным и даже отрицательным.
15 ответов
Этот ответ был написан в 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), который говорит:
- Является ли 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, которая может быть полезна.
Начиная с Java 7, System.nanoTime()
гарантируется безопасность по спецификации JDK.System.nanoTime()
Javadoc дает понять, что все наблюдаемые вызовы в JVM (то есть во всех потоках) являются монотонными:
Возвращаемое значение представляет наносекунды с некоторого фиксированного, но произвольного времени начала (возможно, в будущем, поэтому значения могут быть отрицательными). Один и тот же источник используется всеми вызовами этого метода в экземпляре виртуальной машины Java; другие экземпляры виртуальной машины могут использовать другое происхождение.
Реализация JVM/JDK отвечает за устранение несоответствий, которые могут наблюдаться при вызове базовых утилит ОС (например, упомянутых в ответе Тома Андерсона).
Большинство других старых ответов на этот вопрос (написано в 2009–2012 гг.) Выражают FUD, который, вероятно, имел отношение к Java 5 или Java 6, но больше не относится к современным версиям Java.
Однако стоит отметить, что, несмотря на гарантии JDK nanoTime()
В целях безопасности в OpenJDK было несколько ошибок, из-за которых он не поддерживал эту гарантию на определенных платформах или при определенных обстоятельствах (например, JDK-8040140, JDK-8184271). В OpenJDK нет открытых (известных) ошибок nanoTime()
на данный момент, но обнаружение новой такой ошибки или регресс в новой версии OpenJDK не должно никого шокировать.
Имея это в виду, код, который использует nanoTime()
для временной блокировки, интервального ожидания, тайм-аутов и т. д. предпочтительно следует рассматривать отрицательные различия во времени (тайм-ауты) как нули, а не как исключения. Эта практика также предпочтительнее, потому что она согласуется с поведением всех временных методов ожидания во всех классах вjava.util.concurrent.*
, напримерSemaphore.tryAcquire()
, Lock.tryLock()
, BlockingQueue.poll()
, так далее.
Тем не менее,nanoTime()
все еще следует отдавать предпочтение реализации временных блокировок, интервалов ожидания, тайм-аутов и т. д.currentTimeMillis()
потому что последний подвержен явлению "время идет назад" (например, из-за коррекции времени сервера), т.е. currentTimeMillis()
не подходит для измерения временных интервалов вообще. Смотрите этот ответ для получения дополнительной информации.
Вместо того, чтобы использовать nanoTime()
для непосредственного измерения времени выполнения кода предпочтительно использовать специализированные платформы и профилировщики для бенчмаркинга, например, JMH и асинхронный профилировщик в режиме профилирования настенных часов.
Я немного поискал и обнаружил, что если кто-то педантичен, то да, это может считаться бесполезным... в определенных ситуациях... это зависит от того, насколько чувствительны ваши требования ко времени...
Проверьте эту цитату с сайта Java Sun:
Часы реального времени и System.nanoTime() основаны на одном и том же системном вызове и, следовательно, на одних и тех же часах.
В Java RTS все основанные на времени API-интерфейсы (например, таймеры, периодические потоки, мониторинг сроков и т. Д.) Основаны на таймере высокого разрешения. И вместе с приоритетами в реальном времени они могут гарантировать, что соответствующий код будет выполнен в нужное время для ограничений в реальном времени. Напротив, обычные API Java SE предлагают всего несколько методов, способных обрабатывать время с высоким разрешением, без гарантии выполнения в данный момент времени. Использование System.nanoTime() между различными точками в коде для выполнения измерений прошедшего времени всегда должно быть точным.
В Java также есть предостережение для метода nanoTime ():
Этот метод может использоваться только для измерения прошедшего времени и не связан с каким-либо другим понятием системного или настенного времени. Возвращаемое значение представляет наносекунды с некоторого фиксированного, но произвольного времени (возможно, в будущем, поэтому значения могут быть отрицательными). Этот метод обеспечивает точность наносекунды, но не обязательно точность наносекунды. Не делается никаких гарантий относительно того, как часто меняются значения. Различия в последовательных вызовах, которые охватывают более чем приблизительно 292,3 года (263 наносекунды), не будут точно рассчитывать истекшее время из-за числового переполнения.
Казалось бы, единственный вывод, который можно сделать, состоит в том, что на nanoTime () нельзя полагаться как на точное значение. Таким образом, если вам не нужно измерять время, которое составляет всего лишь наносекунды, тогда этот метод достаточно хорош, даже если полученное возвращаемое значение является отрицательным. Однако, если вам нужна более высокая точность, они рекомендуют вам использовать JAVA RTS.
Так что, чтобы ответить на ваш вопрос... никакой nanoTime () не бесполезен.... это просто не самый разумный метод для использования в любой ситуации.
Не нужно спорить, просто используйте источник. Вот, 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;
}
}
Отказ от ответственности: я разработчик этой библиотеки
Вам может понравиться это лучше:
http://juliusdavies.ca/nanotime/
Но он копирует файл DLL или Unix .so (общий объект) в домашний каталог текущего пользователя, чтобы он мог вызывать JNI.
Некоторая справочная информация находится на моем сайте по адресу:
http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html
Linux исправляет расхождения между процессорами, а Windows - нет. Я полагаю, вы предполагаете, что System.nanoTime() является точной только до 1 микросекунды. Простой способ увеличить время - это вызвать foo() 1000 или более раз и разделить время на 1000.
Абсолютно не бесполезно. Поклонники синхронизации правильно указывают на проблему с многоядерностью, но в приложениях с реальными словами это часто радикально лучше, чем currentTimeMillis().
При вычислении графических позиций в обновлениях кадров nanoTime() приводит к НАМНОГО более плавному движению в моей программе.
И я тестирую только на многоядерных машинах.
Я видел отрицательное прошедшее время, сообщенное при использовании 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 бесполезен - он обеспечивает синхронизацию менее миллисекунды, но существует реальный (не только теоретический) риск того, что он будет неточным, и его необходимо учитывать.
Я ссылаюсь на то, что по сути является той же дискуссией, где Питер Лори дает хороший ответ. Почему я получаю отрицательное прошедшее время с помощью System.nanoTime()?
Многие люди упоминали, что в Java System.nanoTime() может возвращать отрицательное время. Я прошу прощения за повторение того, что уже сказали другие люди.
- nanoTime () - это не часы, а счетчик тактов процессора.
- Возвращаемое значение делится на частоту, чтобы выглядеть как время.
- Частота процессора может колебаться.
- Когда ваш поток запланирован на другом процессоре, есть шанс получить nanoTime (), что приведет к отрицательной разнице. Это логично. Счетчики между процессорами не синхронизированы.
- Во многих случаях вы можете получить весьма вводящие в заблуждение результаты, но вы не сможете сказать, потому что дельта не отрицательна. Думаю об этом.
- (неподтвержденный) Я думаю, что вы можете получить отрицательный результат даже на том же процессоре, если инструкции будут переупорядочены. Чтобы предотвратить это, вы должны вызвать барьер памяти, сериализующий ваши инструкции.
Было бы здорово, если бы System.nanoTime () возвращал coreID там, где он выполнялся.
Java является кроссплатформенной, а nanoTime зависит от платформы. Если вы используете Java - когда не используйте nanoTime. Я нашел реальные ошибки в разных реализациях jvm с этой функцией.
Нет, это не так... Это зависит только от вашего процессора, проверьте High Precision Event Timer, чтобы узнать, как / почему вещи обрабатываются по-разному в зависимости от процессора.
По сути, прочитайте исходный код Java и проверьте, что ваша версия делает с функцией, и если она работает с процессором, вы будете на ней работать.
IBM даже предлагает использовать его для сравнительного анализа производительности (публикация 2008 г., но обновленная).
Похоже, что это не проблема для Core 2 Duo под управлением Windows XP и JRE 1.5.0_06.
В тесте с тремя потоками я не вижу, чтобы System.nanoTime() возвращался назад. Процессоры оба заняты, и потоки иногда засыпают, чтобы спровоцировать перемещение потоков.
[EDIT] Я бы предположил, что это происходит только на физически отдельных процессорах, то есть, что счетчики синхронизируются для нескольких ядер на одном кристалле.
Также, System.currentTimeMillies()
меняется, когда вы меняете системные часы, в то время как System.nanoTime()
нет, поэтому последний безопаснее измерять длительности.
Документация Java 5 также рекомендует использовать этот метод для той же цели.
Этот метод может использоваться только для измерения прошедшего времени и не связан с каким-либо другим понятием системного или настенного времени.
nanoTime
крайне небезопасно для времени. Я опробовал его на моих основных алгоритмах тестирования простоты, и он дал ответы, которые были буквально на расстоянии одной секунды для одного и того же ввода. Не используйте этот нелепый метод. Мне нужно что-то более точное и точное, чем получить время Миллис, но не так плохо, как nanoTime
,