Является ли System.nanoTime () полностью бесполезным? - PullRequest
149 голосов
/ 04 февраля 2009

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

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

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

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

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

Ответы [ 15 ]

202 голосов
/ 04 января 2011

Этот ответ был написан в 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 , которая может быть полезна.

35 голосов
/ 04 февраля 2009

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

Ознакомьтесь с этой цитатой с сайта Java Sun:

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

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

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

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

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

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

18 голосов
/ 01 апреля 2011

Не нужно спорить, просто используйте источник. Вот, 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;
  }
}
10 голосов
/ 10 марта 2010

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

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

http://juliusdavies.ca/nanotime/

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

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

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

8 голосов
/ 07 февраля 2019

Поскольку 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 и асинхронный профилировщик в стене режим профилирования часов .

6 голосов
/ 06 февраля 2009

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

5 голосов
/ 31 марта 2010

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

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

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

4 голосов
/ 03 января 2011

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

2 голосов
/ 20 февраля 2016

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

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

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

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

2 голосов
/ 04 февраля 2009

Похоже, что это не проблема для Core 2 Duo под управлением Windows XP и JRE 1.5.0_06.

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

[РЕДАКТИРОВАТЬ] Я бы предположил, что это происходит только на физически отдельных процессорах, то есть, что счетчики синхронизируются для нескольких ядер на одном кристалле.

...