System.currentTimeMillis против System.nanoTime
Точность против точность
Я хотел бы знать, должен ли я использовать System.currentTimeMillis() или System.nanoTime() при обновлении позиций моего объекта в моей игре? Их изменение в движении прямо пропорционально времени, прошедшему с момента последнего звонка, и я хочу быть максимально точным.
Я читал, что существуют некоторые серьезные проблемы с разрешением времени между различными операционными системами (а именно, что Mac / Linux имеют разрешение почти 1 мс, в то время как Windows имеет разрешение 50 мс??). Я в основном запускаю свои приложения на Windows, и разрешение 50 мс кажется довольно неточным.
Есть ли лучшие варианты, чем два, которые я перечислил?
Любые предложения / комментарии?
10 ответов
Если вам нужны исключительно точные измерения прошедшего времени, используйте System.nanoTime()
, System.currentTimeMillis()
даст вам наиболее точное возможное время, прошедшее с начала эпохи в миллисекундах, но System.nanoTime()
дает вам время с точностью до наносекунды относительно некоторой произвольной точки.
Из документации Java:
public static long nanoTime()
Возвращает текущее значение наиболее точного доступного системного таймера в наносекундах.
Этот метод может использоваться только для измерения прошедшего времени и не связан с каким-либо другим понятием системного или настенного времени. Возвращаемое значение представляет наносекунды с некоторого фиксированного, но произвольного времени (возможно, в будущем, поэтому значения могут быть отрицательными). Этот метод обеспечивает точность наносекунды, но не обязательно точность наносекунды. Не делается никаких гарантий относительно того, как часто меняются значения. Различия в последовательных вызовах, которые охватывают более приблизительно 292 лет (263 наносекунды), не будут точно вычислять истекшее время из-за числового переполнения.
Например, чтобы измерить, сколько времени занимает выполнение кода:
long startTime = System.nanoTime();
// ... the code being measured ...
long estimatedTime = System.nanoTime() - startTime;
См. Также: JavaDoc System.nanoTime () и JavaDoc System.currentTimeMillis() для получения дополнительной информации.
Потокобезопасность
Так как никто другой не упомянул об этом...
Не безопасно
Не безопасно сравнивать результаты System.nanoTime()
звонки между разными потоками. Даже если события потоков происходят в предсказуемом порядке, разница в наносекундах может быть положительной или отрицательной.
Безопасный
System.currentTimeMillis()
безопасен для использования между потоками.
Обновление от Arkadiy: я заметил более правильное поведение 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()
приведет к изменению этого разрешения.
Как уже говорили другие, currentTimeMillis - это время на часах, которое изменяется из-за перехода на летнее время, пользователей, меняющих настройки времени, високосных секунд и синхронизации времени в Интернете. Если ваше приложение зависит от монотонно увеличивающихся значений прошедшего времени, вы можете предпочесть nanoTime.
Вы можете подумать, что игроки не будут играть с настройками времени во время игры, и, возможно, вы будете правы. Но не стоит недооценивать сбои из-за синхронизации времени в Интернете или, возможно, пользователей удаленного рабочего стола. API nanoTime невосприимчив к таким нарушениям.
Если вы хотите использовать время на часах, но избегаете разрывов из-за синхронизации времени в Интернете, вы можете рассмотреть NTP-клиента, такого как Meinberg, который "настраивает" тактовую частоту на ноль, а не просто периодически сбрасывает часы.
Я говорю из личного опыта. В приложении для погоды, которое я разработал, я получал случайные всплески скорости ветра. Мне потребовалось некоторое время, чтобы понять, что моя временная база нарушается из-за поведения часов на типичном ПК. Все мои проблемы исчезли, когда я начал использовать nanoTime. Согласованность (монотонность) была важнее для моего приложения, чем грубая точность или абсолютная точность.
System.nanoTime()
не поддерживается в старых JVM. Если это проблема, придерживайтесь currentTimeMillis
Что касается точности, вы почти правы. На некоторых компьютерах с Windows, currentTimeMillis()
имеет разрешение около 10 мс (не 50 мс). Я не уверен почему, но некоторые машины с Windows так же точны, как машины с Linux.
Я использовал GAGETimer в прошлом с умеренным успехом.
Для игровой графики и плавного обновления позиций используйте System.nanoTime()
скорее, чем System.currentTimeMillis()
, Я переключился с currentTimeMillis() на nanoTime() в игре и получил значительное визуальное улучшение плавности движений.
Хотя одна миллисекунда может показаться, что она уже должна быть точной, визуально это не так. Факторы nanoTime()
можно улучшить включают в себя:
- точное позиционирование пикселей ниже разрешения настенных часов
- возможность сглаживания между пикселями, если хотите
- Неточность окон настенных часов
- джиттер часов (несоответствие того, когда настенные часы на самом деле движутся вперед)
Как показывают другие ответы, nanoTime имеет затраты на производительность при повторном вызове - лучше всего вызывать его только один раз за кадр и использовать одно и то же значение для вычисления всего кадра.
Да, если требуется такая точность, используйте 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)); }
}
System.currentTimeMillis()
не является безопасным для истекшего времени, потому что этот метод чувствителен к системным изменениям часов реального времени системы. Вы должны использовать System.nanoTime
, Пожалуйста, обратитесь к справке Java System:
О методе nanoTime:
.. Этот метод обеспечивает точность наносекунды, но не обязательно наносекундное разрешение (то есть как часто изменяется значение) - никаких гарантий не дается, за исключением того, что разрешение, по крайней мере, так же хорошо, как у currentTimeMillis()..
Если вы используете System.currentTimeMillis()
Ваше прошедшее время может быть отрицательным (Назад <- в будущее)
У меня был хороший опыт работы с nanotime. Он обеспечивает время настенных часов в виде двух длинных (секунды с начала эпохи и наносекунды в течение этой секунды) с использованием библиотеки JNI. Он доступен с предварительно скомпилированной частью JNI для Windows и Linux.
Одна вещь здесь - несогласованность метода nanoTime. Это не дает очень последовательные значения для того же самого input.currentTimeMillis делает намного лучше с точки зрения производительности и согласованности, и также, хотя и не так точно, как nanoTime, имеет более низкий предел погрешности и, следовательно, больше точности в его значении. поэтому я бы предложил вам использовать currentTimeMillis