¿System.nanoTime () es completamente inútil?

153

Como se documenta en la publicación del blog Cuidado con System.nanoTime () en Java , en sistemas x86, Java System.nanoTime () devuelve el valor de tiempo usando un contador específico de la CPU . Ahora considere el siguiente caso que uso para medir el tiempo de una llamada:

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

Ahora, en un sistema multinúcleo, podría ser que después de medir el tiempo1, el subproceso esté programado para un procesador diferente cuyo contador sea menor que el de la CPU anterior. Por lo tanto, podríamos obtener un valor en time2 que sea menor que time1. Por lo tanto, obtendríamos un valor negativo en timeSpent.

Considerando este caso, ¿no es que System.nanotime es bastante inútil por ahora?

Sé que cambiar la hora del sistema no afecta el nanotime. Ese no es el problema que describo anteriormente. El problema es que cada CPU mantendrá un contador diferente desde que se encendió. Este contador puede ser inferior en la segunda CPU en comparación con la primera CPU. Dado que el sistema operativo puede programar el subproceso en la segunda CPU después de obtener time1, el valor de timeSpent puede ser incorrecto e incluso negativo.

Pdeva
fuente
2
No tengo una respuesta pero estoy de acuerdo contigo. Tal vez debería considerarse un error en la JVM.
Aaron Digulla
2
esa publicación es incorrecta y no usar TSC es lento, pero tiene que vivir con: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6440250 También TSC puede ser útil a través del hipervisor, pero luego vuelve a ser lento.
bestsss
1
Y, por supuesto, puede ejecutar en una máquina virtual donde una CPU puede aparecer a mitad de una sesión: D
Expiación limitada

Respuestas:

206

Esta respuesta fue escrita en 2011 desde el punto de vista de lo que realmente hizo el Sun JDK de la época en los sistemas operativos de la época. ¡Eso fue hace mucho tiempo! respuesta de leventov ofrece una perspectiva más actualizada.

Esa publicación está equivocada y nanoTimees segura. Hay un comentario en la publicación que enlaza con una publicación de blog de David Holmes , un tipo en tiempo real y de concurrencia en Sun. Dice:

System.nanoTime () se implementa utilizando la API QueryPerformanceCounter / QueryPerformanceFrequency [...] El mecanismo predeterminado utilizado por QPC está determinado por la capa de abstracción de hardware (HAL) [...] Este valor predeterminado cambia no solo en el hardware sino también en el sistema operativo versiones. Por ejemplo, Windows XP Service Pack 2 cambió las cosas para usar el temporizador de administración de energía (PMTimer) en lugar del contador de marca de tiempo del procesador (TSC) debido a problemas con el TSC que no se sincroniza en diferentes procesadores en sistemas SMP, y debido al hecho de su frecuencia puede variar (y, por lo tanto, su relación con el tiempo transcurrido) según la configuración de administración de energía.

Entonces, en Windows, esto era un problema hasta WinXP SP2, pero no lo es ahora.

No puedo encontrar una parte II (o más) que habla sobre otras plataformas, pero ese artículo incluye una observación de que Linux ha encontrado y resuelto el mismo problema de la misma manera, con un enlace a las Preguntas frecuentes para clock_gettime (CLOCK_REALTIME) , que dice:

  1. ¿Clock_gettime (CLOCK_REALTIME) es coherente en todos los procesadores / núcleos? (¿Importa el arco? Ej. Ppc, arm, x86, amd64, sparc).

se debe o no lo considera buggy.

Sin embargo, en x86 / x86_64, es posible ver que los TSC de frecuencia variable o no sincronizados causan inconsistencias de tiempo. Los núcleos 2.4 realmente no tenían protección contra esto, y los núcleos 2.6 tempranos tampoco funcionaron muy bien aquí. A partir de 2.6.18 en adelante, la lógica para detectar esto es mejor y generalmente recurriremos a una fuente de reloj segura.

ppc siempre tiene una base de tiempo sincronizada, por lo que no debería ser un problema.

Entonces, si se puede leer que el enlace de Holmes implica nanoTimellamadas clock_gettime(CLOCK_REALTIME), entonces es seguro desde el kernel 2.6.18 en x86, y siempre en PowerPC (porque IBM y Motorola, a diferencia de Intel, realmente saben cómo diseñar microprocesadores).

No hay mención de SPARC o Solaris, lamentablemente. Y, por supuesto, no tenemos idea de lo que hacen las JVM de IBM. Pero las JVM de Sun en Windows y Linux modernos hacen esto bien.

EDITAR: esta respuesta se basa en las fuentes que cita. Pero todavía me preocupa que pueda estar completamente equivocado. Una información más actualizada sería realmente valiosa. Acabo de encontrar un enlace a un artículo nuevo de cuatro años sobre los relojes de Linux que podría ser útil.

Tom Anderson
fuente
13
Incluso WinXP SP2 parece sufrir. Ejecutando el ejemplo de código original, void foo() { Thread.sleep(40); }obtuve un tiempo negativo (-380 ms!) Usando un solo Athlon 64 X2 4200+procesador
Luke Usherwood
Supongo que no hay actualizaciones sobre esto, wrt. comportamiento en Linux, BSD u otras plataformas?
Tomer Gabel
66
Buena respuesta, debería agregar un enlace a la exploración más reciente de este tema: shipilev.net/blog/2014/nanotrusting-nanotime
Nitsan Wakart
1
@SOFe: Oh, eso es una pena. Está en el archivo web , afortunadamente. Veré si puedo rastrear una versión actual.
Tom Anderson el
1
Nota: OpenJDK no mantuvo la especificación hasta OpenJDK 8u192, consulte bugs.openjdk.java.net/browse/JDK-8184271 . Asegúrese de utilizar al menos una versión nueva de OpenJDK 8 u OpenJDK 11+.
leventov
35

Hice un poco de búsqueda y descubrí que si uno es pedante, sí, podría considerarse inútil ... en situaciones particulares ... depende de cuán sensibles sean sus requisitos ...

Consulte esta cita del sitio Java Sun:

El reloj en tiempo real y System.nanoTime () se basan en la misma llamada al sistema y, por lo tanto, en el mismo reloj.

Con Java RTS, todas las API basadas en el tiempo (por ejemplo, temporizadores, subprocesos periódicos, monitoreo de fechas límite, etc.) se basan en el temporizador de alta resolución. Y, junto con las prioridades en tiempo real, pueden garantizar que el código apropiado se ejecutará en el momento adecuado para las restricciones en tiempo real. En contraste, las API Java SE comunes ofrecen solo unos pocos métodos capaces de manejar tiempos de alta resolución, sin garantía de ejecución en un momento dado. El uso de System.nanoTime () entre varios puntos en el código para realizar mediciones de tiempo transcurrido siempre debe ser preciso.

Java también tiene una advertencia para el método nanoTime () :

Este método solo se puede usar para medir el tiempo transcurrido y no está relacionado con ninguna otra noción de sistema o tiempo de reloj de pared. El valor devuelto representa nanosegundos desde un tiempo 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.3 años (2 63 nanosegundos) no calcularán con precisión el tiempo transcurrido debido al desbordamiento numérico.

Parece que la única conclusión que se puede extraer es que no se puede confiar en nanoTime () como un valor preciso. Como tal, si no necesita medir tiempos que están separados por nano segundos, entonces este método es lo suficientemente bueno incluso si el valor devuelto resultante es negativo. Sin embargo, si necesita una mayor precisión, parecen recomendarle que use JAVA RTS.

Entonces, para responder a su pregunta ... no nanoTime () no es inútil ... simplemente no es el método más prudente para usar en cada situación.

mezoide
fuente
3
> este método es lo suficientemente bueno incluso si el valor devuelto resultante es negativo. No entiendo esto, si el valor en timespent es negativo, entonces, ¿cómo es útil para medir el tiempo empleado en foo ()?
pdeva
3
está bien porque lo único que le preocupa es el valor absoluto de la diferencia. es decir, si su medición es el tiempo t donde t = t2 - t1, entonces desea saber | t | .... entonces, ¿qué pasa si el valor es negativo ... incluso con el problema de múltiples núcleos, el impacto rara vez será de unos pocos? nanosegundos de todos modos.
mezoid
3
Para hacer una copia de seguridad de @Aaron: tanto t2 como t1 pueden ser negativos, pero (t2-t1) no debe ser negativo.
jfs
2
aaron: eso es exactamente lo que quiero decir. t2-t1 nunca debería ser negativo, de lo contrario, tenemos un error.
pdeva
12
@pdeva, pero no entiendes lo que dice el documento. Estás planteando un problema. Hay algún punto en el tiempo que se considera "0". Los valores devueltos por nanoTime () son precisos en relación con ese tiempo. Es una línea de tiempo monotónicamente creciente. Es posible que obtenga una serie de números de la parte negativa de esa línea de tiempo. -100, -99, -98(Valores, evidentemente, mucho más grandes en la práctica). Van en la dirección correcta (aumentando), por lo que no hay ningún problema aquí.
ToolmakerSteve
18

No es necesario debatir, solo usa la fuente. Aquí, SE 6 para Linux, saque sus propias conclusiones:

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;
  }
}
blais
fuente
77
Eso es útil solo si sabes lo que hace la API utilizada. La API utilizada es implementada por el sistema operativo; este código es correcto wrt. la especificación de la API utilizada (clock_gettime / gettimeofday), pero como otros señalaron, algunos sistemas operativos no actualizados tienen implementaciones con errores.
Blaisorblade
18

Desde Java 7, System.nanoTime()se garantiza la seguridad de la especificación JDK. System.nanoTime()'Javadoc deja en claro que todas las invocaciones observadas dentro de una JVM (es decir, en todos los hilos) son monótonas:

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). Todas las invocaciones de este método utilizan el mismo origen en una instancia de una máquina virtual Java; Es probable que otras instancias de máquinas virtuales utilicen un origen diferente.

La implementación de JVM / JDK es responsable de resolver las inconsistencias que podrían observarse cuando se llaman las utilidades subyacentes del sistema operativo (por ejemplo, las mencionadas en la respuesta de Tom Anderson ).

La mayoría de las otras respuestas antiguas a esta pregunta (escritas en 2009-2012) expresan FUD que probablemente fue relevante para Java 5 o Java 6, pero ya no es relevante para las versiones modernas de Java.

Sin embargo, vale la pena mencionar que, a pesar de nanoTime()la seguridad de las garantías de JDK , ha habido varios errores en OpenJDK que hacen que no se mantenga esta garantía en ciertas plataformas o bajo ciertas circunstancias (por ejemplo, JDK-8040140 , JDK-8184271 ). No hay errores abiertos (conocidos) en OpenJDK wrtnanoTime() en este momento, pero el descubrimiento de un nuevo error o una regresión en una versión más reciente de OpenJDK no debería sorprender a nadie.

Con eso en mente, el código que se usa nanoTime()para el bloqueo temporizado, la espera de intervalos, los tiempos de espera, etc., debe tratar preferiblemente las diferencias de tiempo negativas (tiempos de espera) como ceros en lugar de arrojar excepciones. Esta práctica también es preferible porque es coherente con el comportamiento de todos los métodos de espera cronometrados en todas las clases en java.util.concurrent.*, por ejemplo Semaphore.tryAcquire(), Lock.tryLock(),BlockingQueue.poll() , etc.

No obstante, nanoTime()aún se debe preferir para implementar el bloqueo temporizado, el intervalo de espera, los tiempos de espera, etc., currentTimeMillis()ya que este último está sujeto al fenómeno del "tiempo que retrocede" (por ejemplo, debido a la corrección de la hora del servidor), currentTimeMillis()es decir, no es adecuado para medir intervalos de tiempo en absoluto. Ver esta respuesta para más información.

En lugar de utilizar nanoTime()directamente para mediciones de tiempo de ejecución de código, se deben utilizar preferiblemente marcos de referencia y perfiladores especializados, por ejemplo JMH y async-profiler en modo de perfil de reloj de pared .

leventov
fuente
6

Linux corrige las discrepancias entre las CPU, pero Windows no. Le sugiero que asuma que System.nanoTime () solo tiene una precisión de alrededor de 1 micro segundo. Una manera simple de obtener un tiempo más largo es llamar a foo () 1000 o más veces y dividir el tiempo entre 1000.

Peter Lawrey
fuente
2
¿Podría proporcionar una referencia (comportamiento en Linux y Windows)?
jfs
Lamentablemente, el método propuesto suele ser muy impreciso porque cada evento que cae en el intervalo de actualización del reloj de pared de +/- 100 ms a menudo devolverá cero para operaciones de menos de un segundo. La suma de 9 operaciones cada una con una duración de cero es, bueno, cero, dividido entre nueve es ... cero. Por el contrario, el uso de System.nanoTime () proporcionará duraciones de eventos relativamente precisas (distintas de cero), que luego sumadas y divididas por el número de eventos proporcionarán un promedio muy preciso.
Darrell Teague el
@DarrellTeague sumando 1000 eventos y sumándolos es lo mismo que la hora final.
Peter Lawrey
@DarrellTeague System.nanoTime () tiene una precisión de 1 microsegundo o mejor (no 100,000 microsegundos) en la mayoría de los sistemas. El promedio de muchas operaciones solo es relevante cuando se reduce a unos pocos microsegundos, y solo en ciertos sistemas.
Peter Lawrey
1
Disculpas ya que hubo cierta confusión sobre el lenguaje utilizado en los eventos de "suma". Sí, si el tiempo se marca al comienzo de, digamos, 1000 operaciones de segundo por segundo, se ejecutan y luego el tiempo se marca nuevamente al final y se divide, eso funcionaría para algún sistema en desarrollo para obtener una buena aproximación de la duración de un determinado evento.
Darrell Teague el
5

Absolutamente no inútil. Los aficionados al tiempo señalan correctamente el problema de múltiples núcleos, pero en aplicaciones de palabras reales a menudo es radicalmente mejor que currentTimeMillis ().

Al calcular las posiciones gráficas en las actualizaciones de cuadros, nanoTime () conduce a un movimiento MUCHO más suave en mi programa.

Y solo pruebo en máquinas multinúcleo.

Yuvi Masory
fuente
5

He visto un tiempo transcurrido negativo informado al usar System.nanoTime (). Para ser claros, el código en cuestión es:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

y la variable 'elapsedNanos' tenía un valor negativo. (Estoy seguro de que la llamada intermedia también tardó menos de 293 años, que es el punto de desbordamiento para los nanos almacenados en largos :)

Esto ocurrió utilizando un IBM v1.5 JRE de 64 bits en el hardware IBM P690 (multi-core) que ejecuta AIX. Solo he visto este error ocurrir una vez, por lo que parece extremadamente raro. No sé la causa, es un problema específico de hardware, un defecto de JVM, no lo sé. Tampoco sé las implicaciones para la precisión de nanoTime () en general.

Para responder a la pregunta original, no creo que nanoTime sea inútil: proporciona un tiempo inferior a milisegundos, pero existe un riesgo real (no solo teórico) de que sea inexacto, lo que debe tener en cuenta.

Basil Vandegriend
fuente
Por desgracia, parece ser un problema de sistema operativo / hardware. La documentación indica que los valores centrales pueden ser negativos pero (mayor negativo menos menor negativo) aún debe ser un valor positivo. De hecho, se supone que en el mismo hilo, la llamada nanoTime () siempre debe devolver un valor positivo o negativo. Nunca he visto esto en varios sistemas Unix y Windows durante muchos años, pero parece posible, especialmente si el hardware / sistema operativo está dividiendo esta operación aparentemente atómica entre los procesadores.
Darrell Teague el
@BasilVandegriend no es un error en ningún lado. Según la documentación, rara vez el segundo System.nanoTime () en su ejemplo puede ejecutarse en una CPU diferente y los valores de nanoTime calculados en esa CPU podrían ser inferiores a los valores calculados en la primera CPU. Por lo tanto, es posible un valor -ve para Nanos transcurridos
interminable
2

Esto no parece ser un problema en un Core 2 Duo con Windows XP y JRE 1.5.0_06.

En una prueba con tres hilos no veo que System.nanoTime () vaya hacia atrás. Los procesadores están ocupados y los hilos se van a dormir ocasionalmente para provocar hilos en movimiento.

[EDITAR] Supongo que solo ocurre en procesadores físicamente separados, es decir, que los contadores están sincronizados para múltiples núcleos en el mismo dado.

starblue
fuente
2
Probablemente no sucederá todo el tiempo, pero debido a la forma en que se implementa nanotime (), la posibilidad siempre está ahí.
pdeva
Supongo que solo ocurre en procesadores físicamente separados, es decir, que los contadores están sincronizados para múltiples núcleos en el mismo dado.
starblue
Incluso eso depende de la implementación específica, IIRC. Pero eso es algo de lo que se supone que se ocupa el sistema operativo.
Blaisorblade
1
Los contadores RDTSC en múltiples núcleos del mismo procesador x86 no están necesariamente sincronizados: algunos sistemas modernos permiten que diferentes núcleos funcionen a diferentes velocidades.
Jules
2

No, no lo es ... Solo depende de tu CPU, verifica el temporizador de eventos de alta precisión cómo / por qué las cosas se tratan de manera diferente según la CPU.

Básicamente, lea la fuente de su Java y verifique qué hace su versión con la función, y si funciona contra la CPU, la estará ejecutando.

IBM incluso sugiere que lo use para la evaluación comparativa del rendimiento (una publicación de 2008, pero actualizada).

Ric Tokyo
fuente
Como toda la implementación definió el comportamiento, "advertencia advertencia!"
David Schmitt
2

Me estoy vinculando a lo que esencialmente es la misma discusión donde Peter Lawrey está proporcionando una buena respuesta. ¿Por qué obtengo un tiempo transcurrido negativo usando System.nanoTime ()?

Muchas personas mencionaron que en Java System.nanoTime () podría devolver un tiempo negativo. Pido disculpas por repetir lo que otras personas ya dijeron.

  1. nanoTime () no es un reloj sino un contador de ciclos de CPU.
  2. El valor de retorno se divide por la frecuencia para que parezca el tiempo.
  3. La frecuencia de la CPU puede fluctuar.
  4. Cuando su hilo está programado en otra CPU, existe la posibilidad de obtener nanoTime (), lo que resulta en una diferencia negativa. Eso es logico. Los contadores entre las CPU no están sincronizados.
  5. En muchos casos, podría obtener resultados bastante engañosos, pero no podría saberlo porque delta no es negativo. Piénsalo.
  6. (sin confirmar) Creo que puede obtener un resultado negativo incluso en la misma CPU si se reordenan las instrucciones. Para evitar eso, tendrías que invocar una barrera de memoria que serializa tus instrucciones.

Sería genial si System.nanoTime () devolviera coreID donde se ejecutó.

Vórtice
fuente
1
Todos los puntos excepto 3. y 5. son incorrectos. 1. nanoTime () no es un contador de ciclos de CPU, es nano time . 2. Cómo se produce el valor nanoTime es específico de la plataforma. 4. No, la diferencia no puede ser negativa, de acuerdo con la especificación nanoTime (). Suponiendo que OpenJDK no tiene errores wrt nanoTime (), y no hay errores no resueltos conocidos en este momento. 6. las llamadas de nanoTime no se pueden reordenar dentro de un hilo porque es un método nativo y JVM respeta el orden del programa. JVM nunca reordena las llamadas a métodos nativos porque no sabe lo que sucede dentro de ellas y, por lo tanto, no puede probar que tales reordenamientos sean seguros.
leventov
Con respecto a 5. los resultados de la diferencia de nanoTime () pueden ser engañosos, pero no por las razones presentadas en otros puntos en esta respuesta. Pero más bien por las razones presentadas aquí: shipilev.net/blog/2014/nanotrusting-nanotime
leventov
Irónicamente, con respecto a 6. hubo un error en OpenJDK específicamente debido a reordenamientos: bugs.openjdk.java.net/browse/JDK-8184271 . En OpenJDK, nanoTime () es intrínseco y se permitió reordenar, eso fue un error.
leventov
@leventov, ¿es seguro usar nanotime ()? es decir, no puede devolver valores negativos y es ~ preciso en lo que respecta al tiempo. No veo el punto de exponer una función API que está plagada de problemas. Esta publicación es una prueba, comenzó en 2009 y aún se comentó en 2019. Para cosas de misión crítica, imagino que la gente confía en tarjetas de tiempo como Symmetricom
Vortex
1
Su # 4 dice acerca de la diferencia negativa , no de los valores: "existe la posibilidad de obtener nanoTime (), lo que resulta en una diferencia negativa".
leventov
1

Java es multiplataforma y nanoTime depende de la plataforma. Si usa Java, cuando no use nanoTime. Encontré errores reales en diferentes implementaciones de jvm con esta función.

knave kilork
fuente
0

La documentación de Java 5 también recomienda usar este método para el mismo propósito.

Este método solo puede usarse para medir el tiempo transcurrido y no está relacionado con ninguna otra noción de sistema o tiempo de reloj de pared.

Java 5 API Doc


fuente
0

Además, System.currentTimeMillies()cambia cuando cambia el reloj de su sistema, mientras System.nanoTime()que no lo hace, por lo que este último es más seguro para medir duraciones.

RobAu
fuente
-3

nanoTimeEs extremadamente inseguro para el tiempo. Lo probé en mis algoritmos básicos de prueba de primalidad y dio respuestas que estaban literalmente separadas por un segundo para la misma entrada. No uses ese método ridículo. Necesito algo que sea más preciso y preciso que obtener tiempo millis, pero no tan malo como nanoTime.

sarvesh
fuente
sin una fuente o una mejor explicación este comentario es inútil
ic3