Подтвердить что ты не робот

Почему System.nanoTime() и System.currentTimeMillis() дрейфуют так быстро?

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

Я написал приложение для быстрого тестирования, чтобы увидеть, насколько стабильна разница между этими значениями, и, к моему удивлению, значения сразу расходятся для меня на уровне нескольких миллисекунд в секунду. Несколько раз я видел гораздо более быстрые расхождения. Это на 64-разрядном рабочем столе Win7 с Java 6. Я не пробовал эту тестовую программу под Linux (или Solaris или MacOS), чтобы увидеть, как она работает. Для некоторых запусков этого приложения расхождение положительное, для некоторых работает отрицательно. Похоже, что это зависит от того, что делает рабочий стол, но это трудно сказать.

public class TimeTest {
  private static final int ONE_MILLION  = 1000000;
  private static final int HALF_MILLION =  499999;

  public static void main(String[] args) {
    long start = System.nanoTime();
    long base = System.currentTimeMillis() - (start / ONE_MILLION);

    while (true) {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // Don't care if we're interrupted
      }
      long now = System.nanoTime();
      long drift = System.currentTimeMillis() - (now / ONE_MILLION) - base;
      long interval = (now - start + HALF_MILLION) / ONE_MILLION;
      System.out.println("Clock drift " + drift + " ms after " + interval
                         + " ms = " + (drift * 1000 / interval) + " ms/s");
    }
  }
}

Неточности с временем Thread.sleep(), а также прерывания, должны быть совершенно неактуальны для дрейфа таймера.

Оба этих вызова Java "System" предназначены для использования в качестве измерения: один для измерения различий в настенных часах, а другой для измерения абсолютных интервалов, поэтому, когда часы реального времени не изменяются, эти значения должны меняться очень близко к одной и той же скорости, верно? Является ли это ошибкой или слабостью или неудачей в Java? Есть ли что-то в ОС или аппаратных средствах, которые мешают Java быть более точными?

Я полностью ожидаю некоторого дрейфа и джиттера (**) между этими независимыми измерениями, но я ожидал гораздо меньше минуты в день дрейфа. 1 мсек в секунду дрейфа, если монотонно, составляет почти 90 секунд! Мой худший случай наблюдал дрейф, возможно, в десять раз. Каждый раз, когда я запускаю эту программу, я вижу дрейф при первом измерении. До сих пор я не запускал программу более 30 минут.

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

Есть ли в какой-либо версии Windows механизм, подобный Linux, который настраивает тактовую частоту системы, чтобы медленно синхронизировать синхронизацию времени с внешним источником синхронизации? Будет ли такая вещь влиять как на таймеры, так и на таймер настенных часов?

(*) Я понимаю, что на некоторых архитектурах System.nanoTime() по необходимости будет использовать тот же механизм, что и System.currentTimeMillis(). Я также считаю справедливым предположить, что любой современный сервер Windows не является такой аппаратной архитектурой. Это плохое предположение?

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

4b9b3361

Ответ 1

Вы можете найти эту статью в блоге Sun/Oracle о таймерах JVM.

Вот пара абзацев этой статьи о таймерах JVM под Windows:

System.currentTimeMillis() реализуется с использованием метода GetSystemTimeAsFileTime, который по существу просто считывает значение времени с низким разрешением, которое поддерживает Windows. Чтение этой глобальной переменной, естественно, очень быстро - около 6 циклов в соответствии с сообщенной информацией. Это значение времени суток обновляется с постоянной скоростью независимо от того, как запрограммировано прерывание таймера - в зависимости от платформы это будет либо 10 мс, либо 15 мс (это значение похоже на период прерывания по умолчанию).

System.nanoTime() реализуется с использованием API QueryPerformanceCounter/QueryPerformanceFrequency (если доступно, иначе он возвращает currentTimeMillis*10^6). QueryPerformanceCounter (QPC) реализуется по-разному в зависимости от используемого оборудования. Обычно он будет использовать либо программируемый интервал-таймер (PIT), либо таймер управления питанием ACPI (PMT), либо счетчик времени метки CPU (TSC). Доступ к PIT/PMT требует выполнения инструкций порта медленного ввода-вывода, и в результате время выполнения для QPC составляет порядка микросекунд. Напротив, чтение TSC составляет порядка 100 тактов (чтобы прочитать TSC от микросхемы и преобразовать его в значение времени, основанное на рабочей частоте). Вы можете узнать, использует ли ваша система ACPI PMT, проверяя, возвращает ли QueryPerformanceFrequency значение подписи 3,579,545 (т.е. 3,57 МГц). Если вы видите значение около 1,19 МГц, то ваша система использует старую чипсет 8245 PIT. В противном случае вы должны увидеть значение, приблизительно соответствующее вашей частоте процессора (по модулю любого регулирования скорости или управления питанием, которое может быть в силе.)

Ответ 2

Я не уверен, насколько это действительно поможет. Но это область активных изменений в мире Windows/Intel/AMD/Java. Необходимость точного и точного измерения времени была очевидна для нескольких (не менее 10) лет. Как Intel, так и AMD ответили, изменив работу TSC. У обеих компаний теперь есть что-то, называемое Invariant-TSC и/или Constant-TSC.

Проверьте точность rdtsc в ядрах процессора. Цитата из osgx (кто ссылается на руководство Intel).

"16.11.1 Инвариантный TSC

Счетчик метки времени в новых процессорах может поддерживать расширение, называемое инвариантным TSC. Поддержка процессора для инвариантного TSC указывается PUID.80000007H: EDX [8].

Инвариантный TSC будет работать с постоянной скоростью во всех ACPI P-, C-. и T-состояния. Это архитектурное поведение продвигается вперед. На процессорах с инвариантной поддержкой TSC ОС может использовать TSC для служб таймера настенных часов (вместо таймеров ACPI или HPET). Чтения TSC намного эффективнее и не несут накладные расходы, связанные с циклическим переходом или доступом к ресурсу платформы.

См. также http://www.citihub.com/requesting-timestamp-in-applications/. Цитата из автора

Для AMD:

Если CPUID 8000_0007.edx [8] = 1, то скорость TSC гарантируется как инвариантная для всех состояний P-состояний, C-состояний и переходов стоп-грантов (таких как STPCLK Throttling); поэтому TSC подходит для использования в качестве источника времени.

Для Intel:

Поддержка процессоров для инвариантных TSC указывается CPUID.80000007H: EDX [8]. Инвариантный TSC будет работать с постоянной скоростью во всех ACPI P-, C-. и T-состояния. Это архитектурное поведение продвигается вперед. На процессорах с инвариантной поддержкой TSC ОС может использовать TSC для служб таймера настенных часов (вместо таймеров ACPI или HPET). Чтения TSC намного эффективнее и не несут накладные расходы, связанные с циклическим переходом или доступом к ресурсу платформы.

Теперь действительно важно то, что последние JVM, похоже, используют новые надежные механизмы TSC. Этого не так много, чтобы показать это. Однако посмотрите http://code.google.com/p/disruptor/wiki/PerformanceResults.

"Чтобы измерить задержку, мы берем трехступенчатый конвейер и генерируем события с меньшей насыщенностью. Это достигается путем ожидания 1 микросекунды после инъекции события перед инъекцией следующего и повторения 50 миллионов раз. К тому времени на этом уровне точности он необходимо использовать счетчики штампов времени из ЦП. Мы выбираем процессоры с инвариантным TSC, потому что более старые процессоры страдают от изменения частоты из-за энергосбережения и состояний ожидания. Intel Nehalem и более поздние процессоры используют инвариантный TSC, к которому можно получить доступ с помощью новейшего Oracle JVM, запущенные на Ubuntu 11.04. Для этого теста не было привязки к ЦП"

Обратите внимание, что авторы "Disruptor" тесно связаны с людьми, работающими над Azul и другими JVM.

См. также "Java Flight Records за кулисами". В этой презентации упоминаются новые инвариантные инструкции TSC.

Ответ 3

"Возвращает текущее значение наиболее точного доступного системного таймера в наносекундах.

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

Обратите внимание, что в нем указано "точное", а не "точное".

Это не "ошибка в Java" или "ошибка" во всем. Это определение. Разработчики JVM ищут самый быстрый часы/таймер в системе и используют его. Если это в замке с системными часами, тогда это хорошо, но если это не так, то так же, как печенье рушится. Это вполне правдоподобно, скажем, что компьютерная система будет иметь точные системные часы, но затем имеет встроенный таймер с высокой скоростью, привязанный к тактовой частоте процессора или к некоторым таким. Поскольку частотная частота часто изменяется для минимизации потребления энергии, скорость прироста этого внутреннего таймера будет меняться.

Ответ 4

System.currentTimeMillis() и System.nanoTime() не обязательно предоставляются такое же оборудование. System.currentTimeMillis(), при поддержке GetSystemTimeAsFileTime() имеет элементы разрешения 100ns. Его источником является системный таймер. System.nanoTime() поддерживается высокопроизводительным счетчиком системы. Существует целый ряд различных аппаратных средств обеспечивая этот счетчик. Поэтому его разрешение варьируется в зависимости от используемого оборудования.

Ни в коем случае нельзя предполагать, что эти два источника находятся в фазе. Измерение двух значений друг против друга будут раскрывать другую скорость движения. Если обновление System.currentTimeMillis() принимается за реальный прогресс во времени, вывод System.nanoTime() может быть иногда медленнее, а иногда и быстрее, а также варьироваться.

Необходимо тщательно провести калибровку для блокировки этих двух источников времени.

Более подробное описание связи между этими двумя источниками времени можно найти в Проект временной отметки Windows.

Ответ 5

Есть ли в какой-либо версии Windows механизм, подобный Linux, который настраивает тактовую частоту системы, чтобы медленно синхронизировать синхронизацию времени с внешним источником синхронизации? Будет ли такая вещь влиять как на таймеры, так и на таймер настенных часов?

Проект Timestamp Windows делает то, о чем вы просите. Насколько я знаю, это влияет только на таймер настенных часов.