System.currentTimeMillis проти System.nanoTime


379

Точність Vs. Точність

Що я хотів би знати, чи слід використовувати System.currentTimeMillis () або System.nanoTime () при оновленні позицій об’єкта в моїй грі? Їх зміна в русі прямо пропорційна минулому часу з моменту останнього дзвінка, і я хочу бути максимально точним.

Я читав, що між різними операційними системами є деякі серйозні проблеми з врегулюванням часу (а саме те, що Mac / Linux має майже 1 мс роздільну здатність, а Windows має роздільну здатність 50 мс). Я в основному запускаю свої програми на Windows і роздільна здатність 50 мс здається досить неточною.

Чи є кращі варіанти, ніж два, які я перерахував?

Будь-які пропозиції / коментарі?


81
nanoTimeзазвичай, точніше, ніж currentTimeMillis, але це також досить дорогий дзвінок. currentTimeMillis()працює в декількох (5-6) тактових процесорах, nanoTime залежить від основної архітектури і може становити 100+ тактових процесорів.
bestsss

10
Ви всі розумієте, що Windows, як правило, має зернистість часу 1000 мс / 64, правда? Це 15,625 мс, або 15625000наносекунд!

7
Я не думаю, що сто додаткових тактових циклів впливатимуть на вашу гру, і розпродаж, мабуть, вартує того. Ви повинні викликати метод лише один раз за оновлення гри, а потім зберегти значення в пам’яті, так що це не додасть багато накладних витрат. Що стосується деталізації різних платформ, то я поняття не маю.
aglassman

9
Windows має зернистість частоти DEFAULT 1000мс / 64. Ви можете збільшити це за допомогою нативного API timeBeginPeriod. Сучасні ПК також мають базові таймери на додаток до основного таймера. Таймери високої роздільної здатності доступні через дзвінок QueryPerformanceCounter.
Робін Девіс

2
@Gohan - Ця стаття детально розглядає питання про внутрішню роботу System.currentTimeMillis(): pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
Аттіла Таній

Відповіді:


320

Якщо ви просто шукаєте надзвичайно точні вимірювання минулого часу , використовуйте System.nanoTime(). System.currentTimeMillis()дасть вам найточніший можливий пройдений час у мілісекундах з епохи, алеSystem.nanoTime() надасть точний наносекундний час відносно деякої довільної точки.

З документації Java:

public static long nanoTime()

Повертає поточне значення найбільш точного доступного системного таймера в наносекундах.

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

Наприклад, щоб виміряти, скільки часу потрібно для виконання коду:

long startTime = System.nanoTime();    
// ... the code being measured ...    
long estimatedTime = System.nanoTime() - startTime;

Дивіться також: JavaDoc System.nanoTime () та JavaDoc System.currentTimeMillis () для отримання додаткової інформації.


20
Ви впевнені, що знаєте різницю між точністю та точністю? Ніякого способу це точно до наносекундної точності.
mmcdole

6
Вибачте, я мав на увазі точне. Я використовував цей термін вільно, але я погоджуюся, що це було заплутано (і неправильне використання слова).
dancavallaro

4
@dancavallaro, дякую за інформацію. Якщо ви не заперечуєте, я відредагував вашу відповідь, щоб включити цитату з документів і зафіксував посилання
mmcdole

135
Ця відповідь технічно правильна у виборі nanoTime (), але повністю переливається над надзвичайно важливим моментом. nanoTime (), як каже доктор, є точним таймером. currentTimeMillis () НЕ ТАЙМЕР, це "настінні годинники". nanoTime () завжди створюватиме позитивний минулий час, currentTimeMillis не буде (наприклад, якщо змінити дату, натиснути на стрибок секунди тощо). Це надзвичайно важлива відмінність для деяких типів систем.
charstar

11
Користувач змінює час та синхронізацію NTP, але чому це currentTimeMillisзміниться через DST? Перемикач DST не змінює кількість секунд минулої епохи. Це може бути "настінний годинник", але він заснований на UTC. Ви повинні визначити, виходячи зі свого часового поясу та параметрів літнього часу, до чого позначається місцевий час (або використовувати інші утиліти Java, щоб зробити це для вас).
Shadow Man

100

Оскільки ніхто інший цього не згадував ...

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

System.currentTimeMillis() безпечний для використання між нитками.


4
У Windows, що зберігається лише до SP2, відповідно до: stackoverflow.com/questions/510462/…
Петер Шмітц,

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

4
@jgubby: Дуже цікаво ... будь-яка посилання на підтримку, яка не є безпечною для порівняння результатів викликів System.nanoTime () між різними потоками ? Варто переглянути такі посилання: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519418 docs.oracle.com/javase/7/docs/api/java/lang/…
user454322

15
Дивлячись на згаданий тут опис: docs.oracle.com/javase/7/docs/api/java/lang/… , схоже, різниця між значеннями, поверненими з nanoTime, є дійсними для порівняння, доки вони були з одного і того ж JVM -The values returned by this method become meaningful only when the difference between two such values, obtained within the same instance of a Java virtual machine, is computed.
Tuxdude

7
JavaDoc fornanoTime() каже: те саме походження використовується всіма викликами цього методу в екземплярі віртуальної машини Java; інші екземпляри віртуальної машини, ймовірно, використовують інше походження. що означає , що він дійсно повертає те ж саме через потоки.
Саймон Форсберг

58

Оновлення Аркадія : Я спостерігав більш правильну поведінку System.currentTimeMillis()Windows 7 в Oracle Java 8. Час повернуто з точністю до 1 мілісекунди. Вихідний код у OpenJDK не змінився, тому я не знаю, що викликає кращу поведінку.


Кілька років тому Девід Холмс із Sun опублікував статтю в блозі, де дуже детально ознайомився з тимчасовими API-кодами Java (зокрема System.currentTimeMillis()і System.nanoTime()), коли ви хочете використовувати, які та як вони працюють у внутрішніх умовах.

Всередині VM Hotspot: Годинники, таймери та події планування - Частина I - Windows

Одним з дуже цікавих аспектів таймеру, який використовується Java в Windows для API, що мають параметр приуроченого очікування, є те, що роздільна здатність таймера може змінюватися залежно від того, які інші виклики API могли бути зроблені - загальносистемні (не тільки в конкретному процесі) . Він показує приклад, коли використання Thread.sleep()призведе до зміни цієї роздільної здатності.


1
@Arkadiy: У вас є джерело заяви для оновлення?
Лій

@Lii - На жаль, ні. Він походить від мене, який запускає код у цьому питанні: stackoverflow.com/questions/34090914/… . Код забезпечує точність 15 мс з точністю до Яви 7 та 1 мс з Java 8

2
Я простежив currentTimeMillis до os :: javaTimeMillis в hotspot / src / os / windows / vm / os_windows.cpp у OpenJDK ( hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os/… ) . Схоже, це все ще GetSystemTimeAsFileTime, тому я не знаю, звідки беруться зміни. Або якщо це навіть дійсно. Тестуйте перед використанням.

Зміна поведінки є результатом зміни способу GetSystemTimeAsFileTimeроботи в XP проти 7. Дивіться тут докладніше (tl; dr це стало більш точним, оскільки вся система впровадила деякі більш точні методи часу).

11

System.nanoTime()не підтримується у старих JVM. Якщо це турбота, дотримуйтесьcurrentTimeMillis

Щодо точності, ви майже правильні. На деяких машинах Windows, currentTimeMillis()має роздільну здатність близько 10 мс (не 50 мс). Я не впевнений, чому, але деякі машини Windows настільки ж точні, як і машини Linux.

Раніше я використовував GAGETimer з помірним успіхом.


3
"старіші СП", як у яких? Java 1.2 чи щось таке?
Саймон Форсберг

1
System.nanoTime () був представлений в Java 1.5 в 2004 році. Розширена підтримка Java 1.4 в 2013 році, тому сказати, що System.nanoTime () зараз можна покластись, і ця відповідь застаріла.
Дейв Л.

9

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

Ви можете подумати, що гравці не вподобають налаштувань часу під час гри, і, можливо, ви матимете рацію. Але не варто недооцінювати збої через синхронізацію часу в Інтернеті чи, можливо, користувачів віддалених настільних ПК. API nanoTime не застрахований від подібних порушень.

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

Я кажу з особистого досвіду. У програмі погоди, яку я розробив, я отримував випадкові сплески швидкості вітру. Мені знадобилося певний час, щоб зрозуміти, що мою базу часу порушувала поведінка годинника на типовому ПК. Усі мої проблеми зникли, коли я почав використовувати nanoTime. Консистенція (монотонність) була важливішою для мого застосування, ніж сира точність або абсолютна точність.


19
"currentTimeMillis - це годинний час, який змінюється через літній час" ... майже впевнений, що це твердження помилкове. System.currentTimeMillis()Звіти минули час (у мілісекундах) з часу епохи Unix / Posix, яка є опівночі, 1 січня 1970 року UTC. Оскільки UTC ніколи не коригується для економії літнього часу, це значення не буде компенсовано, коли місцеві часові пояси додають або зміщують зміщення місцевого часу для економії літнього часу. Більше того, масштаб часу Java згладжує високосні секунди, так що дні, як і раніше, становлять 86 400 секунд.
scottb

5

Так, якщо така точність потрібна System.nanoTime() , але пам’ятайте, що тоді вам потрібен JVM Java 5+.

У моїх системах XP я бачу, що системний час повідомляється щонайменше за 100 мікросекунд 278 наносекунд, використовуючи наступний код:

private void test() {
    System.out.println("currentTimeMillis: "+System.currentTimeMillis());
    System.out.println("nanoTime         : "+System.nanoTime());
    System.out.println();

    testNano(false);                                                            // to sync with currentTimeMillis() timer tick
    for(int xa=0; xa<10; xa++) {
        testNano(true);
        }
    }

private void testNano(boolean shw) {
    long strMS=System.currentTimeMillis();
    long strNS=System.nanoTime();
    long curMS;
    while((curMS=System.currentTimeMillis()) == strMS) {
        if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)); }
        }
    if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)+", Milli: "+(curMS-strMS)); }
    }

4

Для ігрової графіки та гладких оновлень позицій System.nanoTime()скоріше використовуйте System.currentTimeMillis(). У грі я перейшов з currentTimeMillis () на nanoTime () і отримав значне покращення зору в плавності руху.

Хоча одна мілісекунда може здатися такою, ніби вона вже повинна бути точною, візуально це не так. До факторів, які nanoTime()можна покращити, належать:

  • точне розміщення пікселів нижче роздільної здатності настінного годинника
  • можливість анти-псевдоніму між пікселями, якщо ви хочете
  • Неточність настінного годинника Windows
  • тремтіння годинника (невідповідність того, коли настінний годинник насправді тикає вперед)

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


1

Я мав хороший досвід роботи з нанотим . Він забезпечує час настінного часу у вигляді двох довгих (секунд після епохи та наносекунд протягом цієї секунди), використовуючи бібліотеку JNI. Він доступний з попередньо складеною частиною JNI і для Windows, і для Linux.


1

System.currentTimeMillis()не є безпечним протягом минулого часу, оскільки цей метод чутливий до зміни годин в системі в режимі реального часу. Ви повинні використовуватиSystem.nanoTime . Будь ласка, зверніться до довідкової системи Java:

Про метод nanoTime:

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

Якщо ви використовуєте System.currentTimeMillis()минулий час, це може бути негативно (Назад <- у майбутнє)


-3

Одне тут - невідповідність методу nanoTime. він не дає дуже послідовних значень для одного і того ж input.currentTimeMillis робить набагато кращі з точки зору продуктивності та послідовності, а також, хоча і не такий точний, як nanoTime, має менший запас похибки , а отже, більше точності в його значенні. Тому я б запропонував вам використовувати currentTimeMillis


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