2010-07-18 25 views
10

Yo y otro desarrollador recientemente pasé de una máquina Core 2 Duo en el trabajo a una nueva Core 2 Quad 9505; ambos ejecutando Windows XP SP3 de 32 bits con JDK 1.6.0_18.¿Por qué se rompe mi System.nanoTime()?

Al hacerlo, un par de nuestras pruebas unitarias automatizadas para algún código de agregación de temporización/estadísticas/métricas comenzaron a fallar rápidamente, debido a lo que parecen ser valores ridículos que vuelven de System.nanoTime().

Código de ensayo que muestra este comportamiento, de forma fiable, en mi máquina es:

import static org.junit.Assert.assertThat; 

import org.hamcrest.Matchers; 
import org.junit.Test; 

public class NanoTest { 

    @Test 
    public void testNanoTime() throws InterruptedException { 
    final long sleepMillis = 5000; 

    long nanosBefore = System.nanoTime(); 
    long millisBefore = System.currentTimeMillis(); 

    Thread.sleep(sleepMillis); 

    long nanosTaken = System.nanoTime() - nanosBefore; 
    long millisTaken = System.currentTimeMillis() - millisBefore; 

    System.out.println("nanosTaken="+nanosTaken); 
    System.out.println("millisTaken="+millisTaken); 

    // Check it slept within 10% of requested time 
    assertThat((double)millisTaken, Matchers.closeTo(sleepMillis, sleepMillis * 0.1)); 
    assertThat((double)nanosTaken, Matchers.closeTo(sleepMillis * 1000000, sleepMillis * 1000000 * 0.1)); 
    } 

} 

salida típica:

millisTaken=5001 
nanosTaken=2243785148 

Ejecutarlo rendimientos 100x resultados nano entre 33% y 60% de la actual hora de dormir; generalmente alrededor del 40% sin embargo.

Entiendo las debilidades en la precisión de los temporizadores en Windows, y he leído hilos relacionados como Is System.nanoTime() consistent across threads?, sin embargo entiendo que System.nanoTime() está destinado exactamente para el propósito que lo estamos usando: - medir el tiempo transcurrido; más exactamente que currentTimeMillis().

¿Alguien sabe por qué está devolviendo resultados tan locos? ¿Es probable que esto sea un problema de arquitectura de hardware (la única cosa importante que ha cambiado es la CPU/placa base en esta máquina)? ¿Un problema con Windows HAL con mi hardware actual? Un problema JDK? ¿Debo abandonar nanoTime()? ¿Debo registrar un error en algún lugar, o alguna sugerencia sobre cómo puedo investigar más?

ACTUALIZACIÓN 19/07 03:15 UTC: Después de intentar caso de prueba de finnw continuación he hecho un poco más buscando en Google, al encontrarse con entradas como bugid:6440250. También me recordó a algún otro comportamiento extraño que noté la noche del viernes, donde los pings regresaban negativos. Así que agregué /usepmtimer a mi boot.ini y ahora todas las pruebas se comportan como se esperaba. Y mis pings también son normales.

Estoy un poco confundido acerca de por qué esto todavía era un problema; de mi lectura pensé que los problemas de TSC vs PMT se resolvieron en gran medida en Windows XP SP3. ¿Podría ser porque mi máquina era originalmente SP2 y se parchó en SP3 en lugar de instalarse originalmente como SP3? Ahora también me pregunto si debería instalar parches como el del MS KB896256. ¿Quizás debería abordar esto con el equipo corporativo de construcción de escritorios?

+0

¿Obtuvo una máquina completamente nueva o su máquina actual se actualizó manteniendo la antigua instalación de Windows? –

+0

Máquina completamente nueva; reconstruido en una construcción estándar corporativa. – Chad

+0

funciona bien para mí en Windows 7 64 bit último JDK 6. – TofuBeer

Respuesta

5

se resolvió el problema (con algunas sospechas abiertas acerca de la idoneidad de nanoTime() en sistemas multi-núcleo!) Mediante la adición de /usepmtimer hasta el final de mi C: \ boot.ini cadena; obligando a Windows a usar el temporizador de administración de energía en lugar del TSC. Es una pregunta abierta sobre por qué tenía que hacer esto dado que estoy en el SP3 de XP, ya que entendí que este era el valor predeterminado, sin embargo, tal vez se debió a la forma en que mi máquina fue parchada al SP3.

+0

Wow, estoy contento de haber encontrado esta publicación. Tuve un sitio de cliente en el que ScheduledExecutorService se salía por completo (el tiempo restante hasta que la próxima tarea programada vaya en la dirección equivocada al azar). –

+0

¡Me alegro de que haya ayudado a alguien! Perdí mucho tiempo con esto :) También me imagino que a medida que el XP se usa cada vez menos ahora que está correctamente agotado (especialmente por los propios desarrolladores), la capacidad de diagnosticar rápidamente problemas tan oscuros en el viejo kit de cliente reducirá gradualmente ... – Chad

1

Probablemente quiera leer las respuestas a esta otra pregunta de desbordamiento de pila: Is System.nanoTime() completely useless?.

En resumen, parece que nanoTime depende de los temporizadores del sistema operativo que pueden verse afectados por la presencia de múltiples CPU centrales. Como tal, nanoTime puede no ser tan útil en ciertas combinaciones de sistema operativo y CPU, y se debe tener cuidado al usarlo en el código portátil de Java que tiene la intención de ejecutar en múltiples plataformas de destino. Parece que hay muchas quejas en la web sobre este tema, pero no hay mucho consenso sobre una alternativa significativa.

+2

Este no es un resumen completamente exacto. System.nanoTime depende de los temporizadores específicos del sistema operativo. Ha habido uno o dos errores en el pasado, p. en los chips Athlon 64 en Windows, sin embargo, en la mayoría de los sistemas puede confiar en que nanoTime funcionará bastante bien. Lo uso para la animación y el tiempo en juegos multi-core, y nunca he tenido ningún problema. – mikera

+0

Gracias por la aclaración mikera. He actualizado mi respuesta para (con suerte) mejorar la precisión. – Tom

+0

Gracias Tom. Como mencioné en la pregunta actualizada anterior, logré volver al comportamiento "normal" forzando el uso del PMT. Supongo que todavía tengo inquietudes acerca de si esto se comportará de la manera que esperamos en varios núcleos. Y sí, sin una alternativa significativa (excepto "regrese a currentTimeMillis") es difícil saber cómo proceder. – Chad

2

En mi sistema (Windows 7 64-Bit, Core i7 980X):

nanosTaken=4999902563 
millisTaken=5001 

System.nanoTime() utiliza las llamadas OS-específicas, así que espero que usted está viendo un error en su Windows/combinación de procesador

+0

Gracias mikera, parece que Windows estaba usando un estilo de temporizador que no se comporta correctamente en mi Core 2 Quad. Obligarlo a usar el temporizador de administración de energía lo ha hecho comportarse normalmente de nuevo; ¡pero no entiendo por qué tengo que hacer esto! – Chad

1

Es difícil determinar si esto es un error o simplemente la variación normal del temporizador entre los núcleos.

Un experimento que podría intentar es usar llamadas nativas para forzar la ejecución del subproceso en un núcleo específico.

Además, para descartar los efectos de administración de energía, trate de girar en un bucle como una alternativa a sleep():

import com.sun.jna.Native; 
import com.sun.jna.NativeLong; 
import com.sun.jna.platform.win32.Kernel32; 
import com.sun.jna.platform.win32.W32API; 

public class AffinityTest { 

    private static void testNanoTime(boolean sameCore, boolean spin) 
    throws InterruptedException { 
     W32API.HANDLE hThread = kernel.GetCurrentThread(); 
     final long sleepMillis = 5000; 

     kernel.SetThreadAffinityMask(hThread, new NativeLong(1L)); 
     Thread.yield(); 
     long nanosBefore = System.nanoTime(); 
     long millisBefore = System.currentTimeMillis(); 

     kernel.SetThreadAffinityMask(hThread, new NativeLong(sameCore? 1L: 2L)); 
     if (spin) { 
      Thread.yield(); 
      while (System.currentTimeMillis() - millisBefore < sleepMillis) 
       ; 
     } else { 
      Thread.sleep(sleepMillis); 
     } 

     long nanosTaken = System.nanoTime() - nanosBefore; 
     long millisTaken = System.currentTimeMillis() - millisBefore; 

     System.out.println("nanosTaken="+nanosTaken); 
     System.out.println("millisTaken="+millisTaken); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     System.out.println("Sleeping, different cores"); 
     testNanoTime(false, false); 
     System.out.println("\nSleeping, same core"); 
     testNanoTime(true, false); 
     System.out.println("\nSpinning, different cores"); 
     testNanoTime(false, true); 
     System.out.println("\nSpinning, same core"); 
     testNanoTime(true, true); 
    } 

    private static final Kernel32Ex kernel = 
     (Kernel32Ex) Native.loadLibrary(Kernel32Ex.class); 

} 

interface Kernel32Ex extends Kernel32 { 
    NativeLong SetThreadAffinityMask(HANDLE hThread, NativeLong dwAffinityMask); 
} 

Si obtiene resultados muy diferentes dependiendo de la selección del núcleo (por ejemplo, 5000 ms en el mismo núcleo, pero 2200ms en diferentes núcleos) que sugeriría que el problema es simplemente la variación natural del temporizador entre los núcleos.

Si obtiene resultados muy diferentes de dormir frente a la rotación, es más probable debido a la administración de energía que ralentiza los relojes.

Si ninguno de los cuatro resultados son cerca de 5000ms, entonces podría ser un error.

+0

Gracias finnw, esto es interesante. Mis resultados son: Dormir, diferentes núcleos nanosTaken = 2049217124 millisTaken = 4,985 Dormir, mismo núcleo nanosTaken = 1808868148 millisTaken = 4,985 Spinning, diferentes núcleos nanosTaken = 5015172794 millisTaken = 5,000 Spinning, mismo núcleo nanosTaken = 5015295288 millisTaken = 5000 ¿Cree que esto implica algo roto con la administración de energía en mi máquina? – Chad

+0

Después de leer un poco más, desencadenado por la prueba, intenté reiniciar mi máquina con/usepmtimer en boot.ini. Ahora su prueba (y mis pruebas originales) se comportan normalmente. He editado mi pregunta en consecuencia. ¿Debería hacer esto? – Chad

+0

No está necesariamente "roto", pero está claro que el TSC no es adecuado para la sincronización de alta precisión en su máquina y el uso del temporizador PM da mejores resultados. Pensé que/usepmtimer era el predeterminado en XP SP3 pero tus resultados sugieren lo contrario. – finnw

Cuestiones relacionadas