Невже марний System.nanoTime ()?


153

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

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

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

Враховуючи цей випадок, чи не так це, що System.nanotime наразі є майже марним?

Я знаю, що зміна системного часу не впливає на нано-час. Це не проблема, яку я описав вище. Проблема полягає в тому, що кожен процесор буде зберігати інший лічильник з моменту його ввімкнення. Цей лічильник може бути нижчим для другого процесора порівняно з першим процесором. Оскільки потік може бути призначений ОС для другого процесора після отримання часу1, значення timeSpent може бути неправильним і навіть негативним.


2
У мене немає відповіді, але я з вами згоден. Можливо, це слід вважати помилкою в JVM.
Аарон Дігулла

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

1
І звичайно, ви можете запускатись у віртуальній машині, де процесор може з’являтися посеред сеансу: D
обмежене спокута

Відповіді:


206

Ця відповідь була написана в 2011 році з точки зору того, що насправді робив Sun JDK часу, що працює на операційних системах того часу. Це було давно! Відповідь Левентова пропонує більш сучасну перспективу.

Цей пост невірний і nanoTimeбезпечний. Там є коментар до посту, який посилається на допис у блозі Девіда Холмса , реального часу та одночасного хлопця Sun. Він говорить:

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

Отже, в Windows це було проблемою до WinXP SP2, але це не зараз.

Я не можу знайти частину II (або більше), яка розповідає про інші платформи, але ця стаття містить зауваження про те, що Linux зіткнувся і вирішив ту саму проблему таким же чином, посилаючись на посилаючись поширені запитання для clock_gettime (CLOCK_REALTIME) , який говорить:

  1. Чи погоджено тактовий час (CLOCK_REALTIME) у всіх процесорах / ядрах? (Чи має значення арка? Наприклад, ppc, arm, x86, amd64, sparc).

Це повинен або вважається баггі.

Однак на x86 / x86_64 можна побачити, що несинхронізовані або змінні частотні ТСК викликають невідповідність часу. 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 це правильно.

EDIT: Ця відповідь ґрунтується на джерелах, які вона цитує. Але я все одно переживаю, що це насправді може бути абсолютно неправильним. Дещо більше актуальної інформації було б дійсно цінним. Щойно я натрапив на посилання на чотирирічну нову статтю про годинники 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+.
leventov

35

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

Ознайомтеся з цією цитатою на сайті Java Sun:

Годинники в режимі реального часу та System.nanoTime () базуються на одній і тій же системній виклику і, отже, однакові.

Завдяки Java RTS усі API на основі часу (наприклад, Таймери, Періодичні потоки, Моніторинг терміну та ін.) Базуються на таймері високої роздільної здатності. І разом з пріоритетами в режимі реального часу вони можуть забезпечити виконання відповідного коду в потрібний час для обмежень у режимі реального часу. На відміну від цього, звичайні API Java SE пропонують лише декілька методів, здатних керувати часом високої роздільної здатності, без гарантії виконання в даний момент часу. Використання System.nanoTime () між різними точками коду для проведення вимірювань минулого часу завжди повинно бути точним.

У Java також є застереження для nanoTime () методу :

Цей метод може використовуватися лише для вимірювання минулого часу і не пов'язаний з будь-яким іншим поняттям системи або часу настінного годинника. Повернене значення являє собою наносекунд з деякого фіксованого, але довільного часу (можливо, у майбутньому, тому значення можуть бути негативними). Цей спосіб забезпечує наносекундну точність, але не обов'язково наносекундну точність. Не дається гарантій щодо того, як часто змінюються значення. Відмінності в послідовних викликах, що тривають більше, ніж приблизно 292,3 року (2 63 наносекунди), не будуть точно обчислювати минулий час через чисельне переповнення.

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

Тож, щоб відповісти на ваше запитання ... жоден nanoTime () не є марним .... його просто не самий розумний метод використовувати у будь-якій ситуації.


3
> Цей метод досить хороший, навіть якщо отримане повернене значення негативне. Я не розумію цього, якщо значення в часовому витраті від'ємне, то як він взагалі корисний при вимірюванні часу, зайнятого в foo ()?
pdeva

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

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

2
Аарон: саме в цьому і полягає моя думка. t2-t1 ніколи не повинен бути негативним, інакше у нас є помилка.
pdeva

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 реалізований операційною системою; цей код правильний wrt. специфікацію використовуваного 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 ). У OpenJDK wrt немає відкритих (відомих) помилокnanoTime() , але виявлення нової такої помилки чи регресія в новій версії OpenJDK нікого не повинно шокувати.

Зважаючи на це, код, який використовується nanoTime()для блокування приуроченого часу, інтервального очікування, тайм-аутів тощо, бажано вважати негативними різницями у часі (таймаутами) як нулі, а не викидами. Ця практика також є кращою , оскільки він узгоджується з поведінкою всіх синхронізованих методів очікування у всіх класах в java.util.concurrent.*, наприклад Semaphore.tryAcquire(), Lock.tryLock(),BlockingQueue.poll() і т.д.

Тим не менш, nanoTime()все ж слід віддавати перевагу впровадженню тимчасового блокування, інтервального очікування, тайм-аутів тощо, currentTimeMillis()оскільки останні є предметом явища "час, що йде назад" (наприклад, через корекцію часу на сервері), тобто currentTimeMillis()не підходить для вимірювання часових інтервалів. зовсім. Дивіться цю відповідь для отримання додаткової інформації.

Замість використання nanoTime() для вимірювання часу виконання коду безпосередньо, спеціалізовані бенчмаркінг структури і профайлери переважно повинні бути використані, наприклад , JMH і асинхронної-Profiler в режимі профілювання на стіні годинник .


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 () забезпечить відносно точні (не нульові) тривалості подій, які потім підсумовуються та діляться на кількість подій забезпечать високоточне середнє значення.
Darrell Teague

@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 років, що є точкою переповнення нано, що зберігається довгими :)

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

Щоб відповісти на первісне запитання, я не вважаю, що nanoTime є марним - він забезпечує підмілісекундний час, але існує фактичний (не лише теоретичний) ризик його неточності, який потрібно враховувати.


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

@BasilVandegriend це не помилка ніде. Згідно з документацією, рідко другий System.nanoTime () у вашому прикладі може працювати на іншому процесорі, і значення nanoTime, розраховані на цьому процесорі, можуть бути просто нижчими за значення, обчислені для першого процесора. Отже, можливе значення -ve для минулого Nanos
нескінченне

2

Здається, це не є проблемою для Core 2 Duo з ОС Windows XP та JRE 1.5.0_06.

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

[EDIT] Я здогадуюсь, що це відбувається лише на фізично окремих процесорах, тобто лічильники синхронізуються для декількох ядер на одній матриці.


2
Це, мабуть, не буде постійно, але завдяки способу реалізації nanotime () можливість завжди є.
pdeva

Я б припустив, що це відбувається лише на фізично окремих процесорах, тобто лічильники синхронізуються для декількох ядер на одній матриці.
starblue

Навіть це залежить від конкретної реалізації, IIRC. Але це те, про що належить подбати ОС.
Blaisorblade

1
Лічильники RDTSC на декількох ядрах одного процесора x86 не обов'язково синхронізуються - деякі сучасні системи дозволяють різним ядрам працювати з різною швидкістю.
Жуль

2

Ні, це не так ... Це просто залежить від вашого процесора, перевірте Таймер подій високої точності для того, як / чому все по-різному трактується відповідно до процесора.

В основному, прочитайте джерело вашої 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 не має помилок wrt nanoTime (), і наразі невідомих невирішених помилок. 6. Виклики nanoTime не можуть бути впорядковані в потоці, оскільки це нативний метод, і JVM поважає програмний порядок. JVM ніколи не впорядковує виклики нативного методу, оскільки він не знає, що відбувається всередині них, і тому не може довести, що такі переупорядкування були б безпечними.
leventov

Що стосується 5. різницьких результатів наноТейм () дійсно можуть бути оманливими, але не з причин, представлених в інших пунктах цього відповіді. Але скоріше з причин, представлених тут: 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 також рекомендує використовувати цей метод з тією ж метою.

Цей метод може використовуватися лише для вимірювання минулого часу і не пов'язаний з будь-яким іншим поняттям системи або часу настінного годинника.

Документ API Java 5


0

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


-3

nanoTimeє надзвичайно небезпечним для проведення часу. Я спробував це на своїх основних алгоритмах тестування первинності, і він дав відповіді, які були буквально за одну секунду за один і той же вхід. Не використовуйте цей смішний метод. Мені потрібно щось більш точне і точне, ніж отримувати час на мілі, але не так погано, як nanoTime.


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