Статьи

Что стоит за System.nanoTime ()?

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

WINDOWS

Функциональность реализована с использованием API QueryPerformanceCounter , у которого, как известно, есть некоторые проблемы. Существует вероятность того, что он может вырваться вперед , некоторые люди сообщают, что это может быть очень медленным на многопроцессорных машинах и т. Д. Я провел некоторое время в сети, пытаясь выяснить, как именно работает QueryPerformanceCounter и что делает. На эту тему нет четкого заключения, но есть некоторые посты, которые могут дать краткое представление о том, как это работает. Я бы сказал, что наиболее полезными, наверное, являются те и те. Конечно, можно найти больше, если поиск немного, но информация будет более или менее той же.

Итак, похоже, что реализация использует HPET , если она доступна. Если нет, то он использует TSC с некоторой синхронизацией значения между процессорами. Интересно, что QueryPerformanceCounter обещают вернуть значение, которое увеличивается с постоянной частотой. Это означает, что в случае использования TSC и нескольких процессоров могут возникнуть некоторые трудности не только из-за того, что процессоры могут иметь разное значение TSC, но также могут иметь разную частоту. Имея в виду все это, Microsoft рекомендует использовать SetThreadAffinityMask для привязки потока, который вызывает QueryPerformanceCounter к одному процессору, чего, очевидно, в JVM не происходит.

LINUX

Linux очень похож на Windows, за исключением того, что он намного прозрачнее (мне удалось скачать исходники :)). Значение читается из clock_gettime с флагом CLOCK_MONOTONIC (для реального человека источник доступен в vclock_gettime.c из источника Linux). Который использует или TSC или HPET . Единственное отличие от Windows заключается в том, что Linux даже не пытается синхронизировать значения TSC, считанные с разных процессоров, а просто возвращает их как есть. Это означает, что значение может отскочить назад и перейти вперед с зависимостью процессора, где оно читается. Кроме того, в контракте с Windows, Linux не поддерживает постоянную частоту изменений. С другой стороны, это определенно должно улучшить производительность.

SOLARIS

Солярис прост. Я полагаю, что через gethrtime он идет более или менее в той же реализации clock_gettime, что и linux. Разница в том, что Solaris гарантирует, что счетчик не будет перепрыгивать, что возможно в Linux, но возможно, что это же значение будет возвращено обратно. Эта гарантия, как можно видеть из исходного кода, реализуется с использованием CAS, который требует синхронизации с основной памятью и может быть относительно дорогим на многопроцессорных компьютерах. Как и в Linux, скорость изменения может варьироваться.

ВЫВОД

Вывод король облачный. Разработчик должен знать, что функция не идеальна, она может прыгать назад или просто вперед. Он может не изменяться монотонно, а скорость изменения может варьироваться в зависимости от тактовой частоты процессора. Кроме того, это не так быстро, как многие могут подумать. На моей машине с Windows 7 в однопоточном тесте он примерно на 10% быстрее, чем System.currentTimeMillis (), в многопоточном тесте, где количество потоков совпадает с числом процессоров, оно одинаково. Таким образом, в целом все, что он дает, — это увеличение разрешения, что может быть важно для некоторых случаев. И, наконец, даже если частота процессора не меняется, не думайте, что вы можете надежно отобразить это значение на системные часы, подробности см. Здесь .

ПРИЛОЖЕНИЕ

Приложение содержит реализации функции для разных ОС. Исходный код взят из OpenJDK v.7.

Solaris

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// gethrtime can move backwards if read from one cpu and then a different cpu
// getTimeNanos is guaranteed to not move backward on Solaris
inline hrtime_t getTimeNanos() {
  if (VM_Version::supports_cx8()) {
    const hrtime_t now = gethrtime();
    // Use atomic long load since 32-bit x86 uses 2 registers to keep long.
    const hrtime_t prev = Atomic::load((volatile jlong*)&max_hrtime);
    if (now <= prev)  return prev;   // same or retrograde time;
    const hrtime_t obsv = Atomic::cmpxchg(now, (volatile jlong*)&max_hrtime, prev);
    assert(obsv >= prev, "invariant");   // Monotonicity
    // If the CAS succeeded then we're done and return "now".
    // If the CAS failed and the observed value "obs" is >= now then
    // we should return "obs".  If the CAS failed and now > obs > prv then
    // some other thread raced this thread and installed a new value, in which case
    // we could either (a) retry the entire operation, (b) retry trying to install now
    // or (c) just return obs.  We use (c).   No loop is required although in some cases
    // we might discard a higher "now" value in deference to a slightly lower but freshly
    // installed obs value.   That's entirely benign -- it admits no new orderings compared
    // to (a) or (b) -- and greatly reduces coherence traffic.
    // We might also condition (c) on the magnitude of the delta between obs and now.
    // Avoiding excessive CAS operations to hot RW locations is critical.
    // See http://blogs.sun.com/dave/entry/cas_and_cache_trivia_invalidate
    return (prev == obsv) ? now : obsv ;
  } else {
    return oldgetTimeNanos();
  }
}

Linux

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
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;
  }
}

Windows

01
02
03
04
05
06
07
08
09
10
11
12
jlong os::javaTimeNanos() {
  if (!has_performance_count) {
    return javaTimeMillis() * NANOS_PER_MILLISEC; // the best we can do.
  } else {
    LARGE_INTEGER current_count;
    QueryPerformanceCounter(¤t_count);
    double current = as_long(current_count);
    double freq = performance_frequency;
    jlong time = (jlong)((current/freq) * NANOS_PER_SEC);
    return time;
  }
}

Ссылка: