2010-07-18 16 views
10

Moi-même et un autre développeur sur mon temps récemment déplacé d'une machine Core 2 Duo au travail à un nouveau Core 2 Quad 9505; les deux exécutant Windows XP SP3 32 bits avec JDK 1.6.0_18. Ce faisant, un couple de nos tests unitaires automatisés pour un code d'agrégation de synchronisation/statistiques/métriques a rapidement commencé à échouer, en raison de ce qui semble être des valeurs ridicules revenant de System.nanoTime().Pourquoi mon System.nanoTime() est-il cassé?

Code d'essai qui montre ce comportement, de manière fiable, sur ma machine est:

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)); 
    } 

} 

sortie typique:

millisTaken=5001 
nanosTaken=2243785148 

Son exécution donne 100x nano résultats entre 33% et 60% du réel Temps de sommeil; habituellement autour de 40% cependant. Je comprends les faiblesses dans la précision des minuteurs dans Windows, et ai lu des threads apparentés comme Is System.nanoTime() consistent across threads?, cependant je crois comprendre que System.nanoTime() est exactement dans le but pour lequel nous l'utilisons: - mesurer le temps écoulé; plus précisément que currentTimeMillis().

Est-ce que quelqu'un sait pourquoi il rend des résultats aussi fous? Est-ce que cela risque d'être un problème d'architecture matérielle (la seule chose qui a changé est la CPU/carte mère sur cette machine)? Un problème avec Windows HAL avec mon matériel actuel? Un problème JDK? Dois-je abandonner nanoTime()? Dois-je enregistrer un bug quelque part, ou des suggestions sur la façon dont je pourrais enquêter plus loin?

MISE À JOUR 19/07 03:15 UTC: Après avoir essayé le cas de test de finnw ci-dessous je l'ai fait un peu plus googler, à venir à travers des entrées telles que bugid:6440250. Cela m'a aussi rappelé un autre comportement étrange que j'ai remarqué vendredi soir où les pings revenaient négatifs. J'ai donc ajouté /usepmtimer à mon boot.ini et maintenant tous les tests se comportent comme prévu., Et mes pings sont normaux aussi.

Je suis un peu confus au sujet de pourquoi c'était toujours un problème si; D'après mes lectures, je pensais que les problèmes TSC vs PMT étaient largement résolus dans Windows XP SP3. Serait-ce parce que ma machine était à l'origine SP2, et a été corrigé au SP3 plutôt qu'installé à l'origine en tant que SP3? Je me demande maintenant si je devrais installer des correctifs comme celui au MS KB896256. Peut-être que je devrais prendre cela avec l'équipe de création de bureau de l'entreprise?

+0

Avez-vous acheté une machine complètement neuve ou votre machine actuelle a-t-elle été mise à niveau en conservant l'ancienne installation de Windows? –

+0

Entièrement nouvelle machine; reconstruit sur une construction standard d'entreprise. – Chad

+0

fonctionne bien pour moi sous Windows 7 64 bits dernière JDK 6. – TofuBeer

Répondre

5

Le problème a été résolu (avec quelques soupçons ouverts quant à la pertinence de NanoTime() sur les systèmes multi-core!) En ajoutant /usepmtimer à la fin de mon C: \ boot.ini chaîne; forcer Windows à utiliser le minuteur de gestion de l'alimentation plutôt que le TSC. C'est une question ouverte quant à la raison pour laquelle je devais le faire étant donné que je suis sur XP SP3, car j'ai compris que c'était la configuration par défaut, mais peut-être était-ce dû à la façon dont ma machine a été corrigée au SP3.

+0

Wow - je suis content d'avoir trouvé cet article - il y avait un site client où ScheduledExecutorService allait complètement à l'écart (le temps restant jusqu'à la prochaine tâche planifiée irait aléatoirement dans la mauvaise direction). –

+0

Heureux qu'il a aidé quelqu'un! J'ai perdu beaucoup de temps à cela :) J'imagine aussi que XP devient de moins en moins utilisé maintenant qu'il est correctement EOL (en particulier par les développeurs eux-mêmes) la capacité de diagnostiquer rapidement de tels problèmes obscurs sur le vieux kit client réduira progressivement .... – Chad

1

Vous souhaitez probablement lire les réponses à cette autre question de débordement de pile: Is System.nanoTime() completely useless?. En résumé, il semblerait que nanoTime repose sur des temporisateurs de système d'exploitation qui peuvent être affectés par la présence de plusieurs processeurs principaux. En tant que tel, nanoTime peut ne pas être très utile sur certaines combinaisons d'OS et de CPU, et il faut faire attention lors de son utilisation dans le code Java portable que vous avez l'intention d'exécuter sur plusieurs plates-formes cibles. Il semble y avoir beaucoup de plaintes sur le web à ce sujet, mais pas beaucoup de consensus sur une alternative significative.

+2

Ce n'est pas un résumé complètement précis. System.nanoTime dépend des minuteurs spécifiques au système d'exploitation. Il y a eu un ou deux bugs dans le passé, par ex. sur les puces Athlon 64 sous Windows, cependant sur la plupart des systèmes, vous pouvez compter sur nanoTime pour fonctionner plutôt bien. Je l'utilise pour l'animation et le timing dans les jeux multi-core, et je n'ai jamais eu de problèmes. – mikera

+0

Merci pour la clarification mikera. J'ai mis à jour ma réponse pour (espérons-le) améliorer la précision. – Tom

+0

Merci Tom. Comme je l'ai mentionné dans la question mise à jour ci-dessus, j'ai réussi à revenir à un comportement «normal» en forçant l'utilisation du PMT. Je suppose que j'ai toujours des inquiétudes quant à savoir si cela se comportera comme nous l'espérons à travers plusieurs noyaux. Et oui, sans alternative valable (à moins de "revenir à currentTimeMillis") il est difficile de savoir comment procéder! – Chad

2

Sur mon système (Windows 7 64 bits, Core i7 980X):

nanosTaken=4999902563 
millisTaken=5001 

System.nanoTime() utilise des appels spécifiques à l'OS, donc je pense que vous voyez un bogue dans Windows/combinaison de processeurs.

+0

Merci mikera, il semble que Windows utilisait un style de minuterie qui ne se comporte pas correctement sur mon Core 2 Quad. Le forcer à utiliser la minuterie de gestion de l'alimentation l'a fait se comporter normalement à nouveau; mais je ne comprends pas très bien pourquoi j'ai dû faire ça! – Chad

1

Il est difficile de dire s'il s'agit d'un bogue ou d'une variation de minuterie normale entre les cœurs.

Une expérience que vous pouvez essayer consiste à utiliser des appels natifs pour forcer le thread à s'exécuter sur un cœur spécifique.

De plus, pour exclure des effets de gestion de l'alimentation, essayez de tourner dans une boucle comme une alternative à 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 vous obtenez des résultats très différents en fonction de la sélection de base (par exemple 5000ms sur le même noyau, mais 2200ms sur des noyaux différents) qui suggérerait que le problème est juste une variation de minuterie naturelle entre les noyaux.

Si vous obtenez des résultats très différents de sommeil par rapport au filage, il est plus probable que la gestion de l'alimentation ralentisse les horloges.

Si aucun des quatre résultats sont proches de 5000ms, alors il pourrait être un bug.

+0

Merci finnw, c'est intéressant. Mes résultats sont les suivants: Dormir, différents noyaux nanosTaken = 2049217124 millisTaken = 4985 dormir, même noyau nanosTaken = 1808868148 millisTaken = 4985 Spinning, noyaux différents nanosTaken = 5015172794 millisTaken = 5000 Spinning, même noyau nanosTaken = 5015295288 millisTaken = 5000 Pensez-vous que cela implique quelque chose de cassé avec la gestion de l'alimentation sur ma machine? – Chad

+0

Après avoir fait quelques lectures supplémentaires, déclenché par votre test, j'ai essayé de redémarrer ma machine avec/usepmtimer dans boot.ini. Maintenant, votre test (et mes tests d'origine) se comportent normalement. J'ai modifié ma question en conséquence. Dois-je faire cela? – Chad

+0

Ce n'est pas forcément "cassé" mais il est clair que le TSC ne convient pas pour une synchronisation de haute précision sur votre machine et l'utilisation de la minuterie PM donne de meilleurs résultats. Je pensais/usepmtimer était la valeur par défaut sur XP SP3 mais vos résultats suggèrent le contraire. – finnw