System.currentTimeMillis vs System.nanoTime

379

Precisión vs. Precisión

Lo que me gustaría saber es si debo usar System.currentTimeMillis () o System.nanoTime () al actualizar las posiciones de mi objeto en mi juego. Su cambio de movimiento es directamente proporcional al tiempo transcurrido desde la última llamada y quiero ser lo más preciso posible.

He leído que hay algunos problemas serios de resolución de tiempo entre diferentes sistemas operativos (es decir, que Mac / Linux tienen una resolución de casi 1 ms mientras que Windows tiene una resolución de 50 ms). Principalmente estoy ejecutando mis aplicaciones en Windows y la resolución de 50 ms parece bastante inexacta.

¿Hay mejores opciones que las dos que enumeré?

¿Alguna sugerencia / comentario?

mmcdole
fuente
81
nanoTimees significativamente más preciso que currentTimeMillis, pero también es una llamada relativamente costosa. currentTimeMillis()se ejecuta en unos pocos (5-6) relojes de CPU, nanoTime depende de la arquitectura subyacente y puede tener más de 100 relojes de CPU.
bestsss
10
Todos ustedes se dan cuenta de que Windows generalmente tiene una granularidad de segmento de tiempo de 1000ms / 64, ¿verdad? ¡Qué es 15.625 ms, o 15625000 nanosegundos!
77
No creo que cien ciclos de reloj adicionales vayan a afectar su juego, y la compensación probablemente valdría la pena. Solo debe llamar al método una vez por actualización del juego y luego guardar el valor en mem, por lo que no agregará demasiados gastos generales. En cuanto a la granularidad de las diferentes plataformas, no tengo idea.
aglassman
99
Windows tiene una granularidad de intervalo de tiempo PREDETERMINADA de 1000 ms / 64. Puede aumentar esto a través de la API nativa timeBeginPeriod. Las PC modernas también tienen temporizadores de alta resolución además del temporizador básico. Se puede acceder a los temporizadores de alta resolución a través de la llamada QueryPerformanceCounter.
Robin Davies
2
@Gohan - Este artículo entra en detalles sobre el funcionamiento interno de System.currentTimeMillis(): pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
Attila Tanyi

Respuestas:

320

Si solo está buscando mediciones extremadamente precisas del tiempo transcurrido , úselas System.nanoTime(). System.currentTimeMillis()le dará el tiempo transcurrido más preciso posible en milisegundos desde la época, pero System.nanoTime()le dará un tiempo preciso de nanosegundos, en relación con algún punto arbitrario.

De la documentación de Java:

public static long nanoTime()

Devuelve el valor actual del temporizador del sistema más preciso disponible, en nanosegundos.

Este método solo se puede utilizar para medir el tiempo transcurrido y no está relacionado con ninguna otra noción de tiempo de sistema o reloj de pared. El valor devuelto representa nanosegundos desde un tiempo de origen fijo pero arbitrario (quizás en el futuro, por lo que los valores pueden ser negativos). Este método proporciona precisión en nanosegundos, pero no necesariamente precisión en nanosegundos. No se garantiza con qué frecuencia cambian los valores. Las diferencias en llamadas sucesivas que abarcan más de aproximadamente 292 años (2 63 nanosegundos) no calcularán con precisión el tiempo transcurrido debido al desbordamiento numérico.

Por ejemplo, para medir cuánto tiempo lleva ejecutar un código:

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

Consulte también: JavaDoc System.nanoTime () y JavaDoc System.currentTimeMillis () para obtener más información.

dancavallaro
fuente
20
¿Estás seguro de que sabes la diferencia entre precisión y precisión? No hay forma de que sea precisa con una precisión de nanosegundos.
mmcdole
66
Lo siento, quise decir preciso. Estaba usando el término libremente, pero estoy de acuerdo en que era confuso (y un uso incorrecto de la palabra).
dancavallaro
44
@dancavallaro, gracias por la información. Si no le importa, edité su respuesta para incluir una cita de los documentos y arreglé los enlaces
mmcdole
135
Esta respuesta es técnicamente correcta al elegir nanoTime () pero pasa por alto completamente un punto extremadamente importante. nanoTime (), como dice el documento, es un temporizador de precisión. currentTimeMillis () NO ES UN TEMPORIZADOR, es el "reloj de pared". nanoTime () siempre producirá un tiempo transcurrido positivo, currentTimeMillis no lo hará (por ejemplo, si cambia la fecha, da un salto de segundo, etc.) Esta es una distinción extremadamente importante para algunos tipos de sistemas.
charstar
11
El tiempo de cambio del usuario y la sincronización de NTP son seguros, pero ¿por qué currentTimeMilliscambiaría debido al horario de verano? El interruptor DST no cambia el número de segundos después de la época. Puede ser un "reloj de pared" pero está basado en UTC. Debe determinar en función de su configuración de zona horaria y horario de verano lo que eso se traduce para su hora local (o utilizar otras utilidades de Java para hacerlo por usted).
Shadow Man
100

Como nadie más ha mencionado esto ...

No es seguro comparar los resultados de las System.nanoTime()llamadas entre diferentes hilos. Incluso si los eventos de los hilos ocurren en un orden predecible, la diferencia en nanosegundos puede ser positiva o negativa.

System.currentTimeMillis() es seguro para usar entre hilos.

gordito
fuente
44
En Windows que solo se mantiene hasta SP2 de acuerdo con: stackoverflow.com/questions/510462/…
Peter Schmitz
3
Bueno, aprendes algo nuevo todos los días. Sin embargo, sospecho que dado que no era seguro en el pasado (definitivamente devuelve lecturas sin sentido a través de los hilos), tal uso probablemente todavía esté fuera de las especificaciones y, por lo tanto, probablemente deba evitarse.
gordito
44
@jgubby: Muy interesante ... ¿ alguna referencia al soporte que no sea seguro para comparar los resultados de las llamadas System.nanoTime () entre diferentes subprocesos ? Vale la pena ver los siguientes enlaces: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519418 docs.oracle.com/javase/7/docs/api/java/lang/…
user454322
15
Mirando la descripción mencionada aquí: docs.oracle.com/javase/7/docs/api/java/lang/… , parece que la diferencia entre los valores devueltos por nanoTime son válidos para la comparación siempre que fueran del mismo 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
77
El JavaDoc parananoTime() dice: todas las invocaciones de este método utilizan un mismo origen en una instancia de una máquina virtual Java; Es probable que otras instancias de máquinas virtuales utilicen un origen diferente. lo que significa que no devuelven los mismos a través de las discusiones.
Simon Forsberg
58

Actualización de Arkadiy : He observado un comportamiento más correcto System.currentTimeMillis()en Windows 7 en Oracle Java 8. El tiempo se devolvió con una precisión de 1 milisegundo. El código fuente en OpenJDK no ha cambiado, por lo que no sé qué causa el mejor comportamiento.


David Holmes, de Sun, publicó un artículo en el blog hace un par de años que tiene una visión muy detallada de las API de temporización de Java (en particular System.currentTimeMillis()y System.nanoTime()), cuándo querría usar cuáles y cómo funcionan internamente.

Dentro de la VM Hotspot: relojes, temporizadores y eventos de programación - Parte I - Windows

Un aspecto muy interesante del temporizador utilizado por Java en Windows para las API que tienen un parámetro de espera cronometrado es que la resolución del temporizador puede cambiar dependiendo de qué otras llamadas a la API se hayan realizado: en todo el sistema (no solo en el proceso en particular) . Muestra un ejemplo en el que el uso Thread.sleep()provocará un cambio en esta resolución.

Michael Burr
fuente
1
@Arkadiy: ¿Tiene una fuente para la declaración en la actualización?
Lii
@Lii - Desafortunadamente, no. Viene de mí ejecutando el código en esta pregunta: stackoverflow.com/questions/34090914/… . El código produce una precisión de 15 ms con Java 7 y una precisión de 1 ms con Java 8
2
Rastreé el currentTimeMillis hasta os :: javaTimeMillis en hotspot / src / os / windows / vm / os_windows.cpp en OpenJDK ( hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os/… ) . Parece que todavía es GetSystemTimeAsFileTime, por lo que no sé de dónde viene el cambio. O si es incluso válido. Prueba antes de usar.
el cambio en el comportamiento es el resultado de cambiar la forma GetSystemTimeAsFileTimeen que funcionó en XP vs 7. Vea aquí para más detalles (tl; dr se volvió más preciso ya que todo el sistema introdujo algunos métodos de sincronización más precisos).
11

System.nanoTime()no es compatible con JVM anteriores. Si eso es una preocupación, quédate concurrentTimeMillis

En cuanto a la precisión, estás casi en lo correcto. En ALGUNAS máquinas con Windows, currentTimeMillis()tiene una resolución de aproximadamente 10 ms (no 50 ms). No estoy seguro de por qué, pero algunas máquinas con Windows son tan precisas como las máquinas con Linux.

He usado GAGETimer en el pasado con un éxito moderado.

Paul Morel
fuente
3
¿"JVMs más antiguas" como en cuáles? Java 1.2 o algo así?
Simon Forsberg
1
System.nanoTime () se introdujo en Java 1.5 en 2004. Java 1.4 amplió el soporte en 2013, por lo que es bastante seguro decir que ahora se puede confiar en System.nanoTime () y esta respuesta está desactualizada.
Dave L.
9

Como otros han dicho, currentTimeMillis es la hora del reloj, que cambia debido al horario de verano, los usuarios cambian la configuración de la hora, los segundos intermedios y la sincronización de la hora de Internet. Si su aplicación depende de valores de tiempo transcurrido que aumentan monotónicamente, es posible que prefiera nanoTime.

Podrías pensar que los jugadores no estarán jugando con los ajustes de tiempo durante el juego, y tal vez tengas razón. Pero no subestimes la interrupción debido a la sincronización horaria de Internet, o quizás a los usuarios de escritorio remoto. La API nanoTime es inmune a este tipo de interrupción.

Si desea utilizar la hora del reloj, pero evita las discontinuidades debido a la sincronización horaria de Internet, puede considerar un cliente NTP como Meinberg, que "ajusta" la frecuencia del reloj para ponerlo a cero, en lugar de simplemente reiniciar el reloj periódicamente.

Hablo por experiencia personal. En una aplicación meteorológica que desarrollé, recibía picos de velocidad del viento que ocurrían al azar. Me tomó un tiempo darme cuenta de que mi base de tiempo estaba siendo interrumpida por el comportamiento de la hora del reloj en una PC típica. Todos mis problemas desaparecieron cuando comencé a usar nanoTime. La consistencia (monotonicidad) era más importante para mi aplicación que la precisión bruta o la precisión absoluta.

KarlU
fuente
19
"currentTimeMillis es la hora del reloj, que cambia debido al horario de verano" ... estoy bastante seguro de que esta afirmación es falsa. System.currentTimeMillis()informa el tiempo transcurrido (en milisegundos) desde la época de Unix / Posix, que es la medianoche del 1 de enero de 1970 UTC. Como UTC nunca se ajusta para el horario de verano, este valor no se compensará cuando las zonas horarias locales agreguen o sometan compensaciones a los horarios locales para el horario de verano. Además, la escala de tiempo de Java suaviza los segundos bisiestos para que los días sigan teniendo 86.400 segundos.
scottb
5

Sí, si se requiere tal precisión System.nanoTime(), pero tenga en cuenta que necesita una JVM Java 5+.

En mis sistemas XP, veo el tiempo del sistema informado al menos a 100 microsegundos y 278 nanosegundos usando el siguiente código:

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)); }
    }
Lawrence Dol
fuente
4

Para gráficos de juegos y actualizaciones de posición fluidas, use en System.nanoTime()lugar deSystem.currentTimeMillis() . Cambié de currentTimeMillis () a nanoTime () en un juego y obtuve una mejora visual importante en la suavidad del movimiento.

Si bien un milisegundo puede parecer que ya debería ser preciso, visualmente no lo es. Los factores que nanoTime()pueden mejorar incluyen:

  • posicionamiento preciso de píxeles por debajo de la resolución del reloj de pared
  • capacidad de anti-alias entre píxeles, si lo desea
  • Imprecisión del reloj de pared de Windows
  • fluctuación del reloj (inconsistencia de cuando el reloj de pared realmente avanza)

Como sugieren otras respuestas, nanoTime tiene un costo de rendimiento si se llama repetidamente: sería mejor llamarlo solo una vez por fotograma y usar el mismo valor para calcular el fotograma completo.

Thomas W
fuente
1

He tenido buena experiencia con nanotime . Proporciona tiempo de reloj de pared como dos largos (segundos desde la época y nanosegundos dentro de ese segundo), utilizando una biblioteca JNI. Está disponible con la parte JNI precompilada para Windows y Linux.

Jon Bright
fuente
1

System.currentTimeMillis()no es seguro para el tiempo transcurrido porque este método es sensible a los cambios de reloj en tiempo real del sistema. Deberías usarSystem.nanoTime . Consulte la ayuda del sistema Java:

Sobre el método nanoTime:

.. Este método proporciona precisión de nanosegundos, pero no necesariamente una resolución de nanosegundos (es decir, con qué frecuencia cambia el valor): no se hacen garantías, excepto que la resolución es al menos tan buena como la de currentTimeMillis () ..

Si usa System.currentTimeMillis()su tiempo transcurrido puede ser negativo (Volver <- al futuro)

Ricardo Gasca
fuente
-3

Una cosa aquí es la inconsistencia del método nanoTime. No proporciona valores muy consistentes para la misma entrada. CurrentTimeMillis funciona mucho mejor en términos de rendimiento y consistencia, y además, aunque no es tan preciso como nanoTime, tiene un margen de error más bajo y, por lo tanto, más precisión en su valor. Por lo tanto, sugeriría que use currentTimeMillis

sarvesh
fuente
66
Como se señaló en otras respuestas y comentarios, currentTimeMillis está sujeto a cambios en el reloj del sistema y, por lo tanto, es una mala elección para calcular el tiempo transcurrido desde algún evento anterior en la JVM.
marcadores de duelin el