System.currentTimeMillis против System.nanoTime

379

Точность против точность

Я хотел бы знать, должен ли я использовать System.currentTimeMillis () или System.nanoTime () при обновлении позиций моего объекта в моей игре? Их изменение в движении прямо пропорционально времени, прошедшему с момента последнего звонка, и я хочу быть максимально точным.

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

Есть ли лучшие варианты, чем два, которые я перечислил?

Любые предложения / комментарии?

mmcdole
источник
81
nanoTimeобычно значительно точнее, чем currentTimeMillis, но это и относительно дорогой вызов. currentTimeMillis()работает в нескольких (5-6) процессорных часах, nanoTime зависит от базовой архитектуры и может быть более 100 тактовых процессоров.
bestsss
10
Вы все понимаете, что Windows обычно имеет гранулярность временного интервала 1000 мс / 64, верно? Что составляет 15.625 мс или 15625000 нс!
7
Я не думаю, что сотня дополнительных тактов повлияет на вашу игру, и компромисс, вероятно, того стоит. Вы должны вызывать метод только один раз за обновление игры, а затем сохранять значение в mem, чтобы это не добавляло много накладных расходов. Что касается гранулярности разных платформ, я понятия не имею.
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 () для получения дополнительной информации.

dancavallaro
источник
20
Вы уверены, что знаете разницу между точностью и точностью? Нет никакого способа, которым это точно с точностью до наносекунды.
mmcdole
6
Извините, я имел в виду точный. Я использовал термин свободно, но я согласен, что это сбивало с толку (и неправильное использование слова).
dancavallaro
4
@dancavallaro, спасибо за информацию. Если вы не возражаете, я отредактировал ваш ответ, включив цитату из документов, и исправил ссылки
mmcdole
135
Этот ответ является технически правильным при выборе nanoTime (), но полностью закрывает чрезвычайно важный момент. nanoTime (), как говорит док, является точным таймером. currentTimeMillis () - это НЕ ТАЙМЕР, это «настенные часы». nanoTime () всегда будет давать положительное время, а currentTimeMillis - нет (например, если вы измените дату, нажмете секунду и т. д.) Это чрезвычайно важное различие для некоторых типов систем.
charstar
11
Пользователь меняет время и синхронизацию NTP, но почему бы currentTimeMillisизмениться из-за перехода на летнее время? Переключатель DST не меняет количество секунд после эпохи. Это могут быть «настенные часы», но они основаны на UTC. Вы должны определить, основываясь на вашем часовом поясе и настройках летнего времени, что это означает для вашего местного времени (или использовать другие утилиты Java, чтобы сделать это для вас).
Человек-тень
100

Так как никто другой не упомянул об этом ...

Не безопасно сравнивать результаты System.nanoTime()звонков между разными потоками. Даже если события потоков происходят в предсказуемом порядке, разница в наносекундах может быть положительной или отрицательной.

System.currentTimeMillis() безопасен для использования между потоками.

gubby
источник
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 дляnanoTime() говорит: один и тот же источник используется всеми вызовами этого метода в экземпляре виртуальной машины Java; другие экземпляры виртуальной машины могут использовать другое происхождение. что означает , что он действительно возвращает то же самое через потоки.
Саймон Форсберг
58

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


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

Внутри Hotspot VM: часы, таймеры и планирование событий - Часть I - Windows

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

Майкл Берр
источник
1
@Arkadiy: у вас есть источник для утверждения в обновлении?
Лий
@Lii - К сожалению, нет. Это исходит от меня, запустив код в этом вопросе: stackoverflow.com/questions/34090914/… . Код производит 15 мс точности с Java 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; д-р он стал более точным, так как вся система ввела некоторые более точные методы синхронизации).
11

System.nanoTime()не поддерживается в старых JVM. Если это проблема, придерживайтесьcurrentTimeMillis

Что касается точности, вы почти правы. На некоторых компьютерах с Windows currentTimeMillis()имеет разрешение около 10 мс (не 50 мс). Я не уверен, почему, но некоторые машины Windows так же точны, как машины Linux.

Я использовал GAGETimer в прошлом с умеренным успехом.

Пол Морель
источник
3
"старые JVM" как и в каких? Java 1.2 или что-то?
Саймон Форсберг
1
System.nanoTime () была введена в Java 1.5 в 2004 году. Расширенная поддержка Java 1.4 появилась в 2013 году, поэтому можно с уверенностью сказать, что на System.nanoTime () теперь можно положиться, и этот ответ устарел.
Дэйв Л.
9

Как уже говорили другие, currentTimeMillis - это время на часах, которое изменяется из-за перехода на летнее время, пользователей, меняющих настройки времени, високосных секунд и синхронизации времени в Интернете. Если ваше приложение зависит от монотонно увеличивающихся значений прошедшего времени, вы можете предпочесть nanoTime.

Вы можете подумать, что игроки не будут играть с настройками времени во время игры, и, возможно, вы будете правы. Но не стоит недооценивать сбои из-за синхронизации времени в Интернете или, возможно, пользователей удаленного рабочего стола. API nanoTime невосприимчив к таким нарушениям.

Если вы хотите использовать время на часах, но избегаете разрывов из-за синхронизации времени в Интернете, вы можете рассмотреть NTP-клиента, такого как Meinberg, который «настраивает» тактовую частоту на ноль, а не просто периодически сбрасывает часы.

Я говорю из личного опыта. В приложении для погоды, которое я разработал, я получал случайные всплески скорости ветра. Мне потребовалось некоторое время, чтобы понять, что моя временная база нарушается из-за поведения часов на типичном ПК. Все мои проблемы исчезли, когда я начал использовать nanoTime. Согласованность (монотонность) была важнее для моего приложения, чем грубая точность или абсолютная точность.

KarlU
источник
19
«currentTimeMillis - это время в часах, которое меняется из-за перехода на летнее время» ... почти уверен, что это утверждение неверно. System.currentTimeMillis()сообщает прошедшее время (в миллисекундах) с начала эпохи Unix / Posix, которая наступила в полночь 1 января 1970 года по Гринвичу. Поскольку UTC никогда не корректируется для перехода на летнее время, это значение не будет смещено, когда местные часовые пояса добавляются или смещаются к местному времени для перехода на летнее время. Кроме того, Java Time Scale сглаживает високосные секунды, так что дни продолжают казаться 86 400 секунд.
Скотт
5

Да, если требуется такая точность, используйте System.nanoTime(), но имейте в виду, что вам требуется Java 5+ JVM.

В моих системах 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()можно улучшить, включают в себя:

  • точное позиционирование пикселей ниже разрешения настенных часов
  • возможность сглаживания между пикселями, если хотите
  • Неточность окон настенных часов
  • джиттер часов (несоответствие того, когда настенные часы на самом деле движутся вперед)

Как показывают другие ответы, nanoTime имеет затраты производительности при повторном вызове - лучше всего вызывать его только один раз за кадр и использовать одно и то же значение для вычисления всего кадра.

Томас В.
источник
1

У меня был хороший опыт работы с nanotime . Он обеспечивает время настенных часов в виде двух длинных (секунды с начала эпохи и наносекунды в течение этой секунды) с использованием библиотеки JNI. Он доступен с предварительно скомпилированной частью JNI для Windows и Linux.

Джон Брайт
источник
1

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

О методе nanoTime:

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

Если вы используете System.currentTimeMillis()ваше прошедшее время может быть отрицательным (Назад <- в будущее)

Рикардо Гаска
источник
-3

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

Sarvesh
источник
6
Как отмечалось в других ответах и ​​комментариях, currentTimeMillis подвержен изменениям системных часов и, следовательно, является плохим выбором для расчета истекшего времени после некоторого более раннего события в JVM.
дуэльные маркеры