System.currentTimeMillis vs System.nanoTime

379

Précision contre. Précision

Ce que je voudrais savoir, c'est si je dois utiliser System.currentTimeMillis () ou System.nanoTime () lors de la mise à jour des positions de mon objet dans mon jeu? Leur changement de mouvement est directement proportionnel au temps écoulé depuis le dernier appel et je veux être aussi précis que possible.

J'ai lu qu'il existe de sérieux problèmes de résolution temporelle entre différents systèmes d'exploitation (à savoir que Mac / Linux ont une résolution de près de 1 ms tandis que Windows a une résolution de 50 ms ??). J'exécute principalement mes applications sur Windows et la résolution de 50 ms semble assez inexacte.

Y a-t-il de meilleures options que les deux que j'ai énumérées?

Des suggestions / commentaires?

mmcdole
la source
81
nanoTimeest généralement plus précis que currentTimeMillis mais c'est aussi un appel relativement cher. currentTimeMillis()fonctionne sur quelques (5-6) horloges cpu, nanoTime dépend de l'architecture sous-jacente et peut être plus de 100 horloges cpu.
bestsss
10
Vous vous rendez tous compte que Windows a généralement une granularité de tranche de temps de 1000 ms / 64, non? Soit 15,625 ms, soit 15625000 nanosecondes!
7
Je ne pense pas qu'une centaine de cycles d'horloge supplémentaires auront un impact sur votre jeu, et le compromis en vaudrait probablement la peine. Vous ne devriez appeler la méthode qu'une fois par mise à jour du jeu, puis enregistrer la valeur dans mem, donc cela n'ajoutera pas beaucoup de frais généraux. Quant à la granularité des différentes plateformes, je n'en ai aucune idée.
aglassman
9
Windows a une granularité de tranche de temps par défaut de 1000 ms / 64. Vous pouvez augmenter cela via l'API native timeBeginPeriod. Les PC modernes ont également des minuteries haute résolution en plus du minuteur de base. Les minuteries haute résolution sont accessibles via l'appel QueryPerformanceCounter.
Robin Davies
2
@Gohan - Cet article détaille le fonctionnement interne de System.currentTimeMillis(): pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
Attila Tanyi

Réponses:

320

Si vous recherchez simplement des mesures extrêmement précises du temps écoulé , utilisez System.nanoTime(). System.currentTimeMillis()vous donnera le temps écoulé le plus précis possible en millisecondes depuis l'époque, mais System.nanoTime()vous donne un temps précis en nanosecondes, par rapport à un point arbitraire.

De la documentation Java:

public static long nanoTime()

Renvoie la valeur actuelle du minuteur système le plus précis disponible, en nanosecondes.

Cette méthode ne peut être utilisée que pour mesurer le temps écoulé et n'est liée à aucune autre notion de temps système ou d'horloge murale. La valeur renvoyée représente des nanosecondes depuis un certain temps d' origine fixe mais arbitraire (peut-être à l'avenir, donc les valeurs peuvent être négatives). Cette méthode fournit une précision en nanosecondes, mais pas nécessairement une précision en nanosecondes. Aucune garantie n'est donnée sur la fréquence à laquelle les valeurs changent. Les différences d'appels successifs qui s'étendent sur plus de 292 ans environ (2 63 nanosecondes) ne permettront pas de calculer avec précision le temps écoulé en raison d'un débordement numérique.

Par exemple, pour mesurer la durée d'exécution d'un code:

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

Voir aussi: JavaDoc System.nanoTime () et JavaDoc System.currentTimeMillis () pour plus d'informations.

dancavallaro
la source
20
Êtes-vous sûr de connaître la différence entre exactitude et précision? Il n'y a aucun moyen qu'il soit précis à une précision en nanosecondes.
mmcdole
6
Désolé, je voulais dire précis. J'utilisais le terme de façon lâche, mais je suis d'accord qu'il était déroutant (et une mauvaise utilisation du mot).
dancavallaro
4
@dancavallaro, merci pour l'information. Si cela ne vous dérange pas, j'ai modifié votre réponse pour inclure une citation des documents et corrigé les liens
mmcdole
135
Cette réponse est techniquement correcte dans le choix de nanoTime () mais passe complètement sous silence un point extrêmement important. nanoTime (), comme le dit le doc, est un temporisateur de précision. currentTimeMillis () n'est PAS UNE MINUTERIE, c'est "l'horloge murale". nanoTime () produira toujours un temps écoulé positif, currentTimeMillis ne le fera pas (par exemple, si vous changez la date, frappez une seconde, etc.) C'est une distinction extrêmement importante pour certains types de systèmes.
charstar
11
L'utilisateur change l'heure et la synchronisation NTP bien sûr, mais pourquoi currentTimeMillischangerait-il en raison de l'heure d'été? Le commutateur DST ne change pas le nombre de secondes après l'époque. Il peut s'agir d'une "horloge murale", mais elle est basée sur UTC. Vous devez déterminer en fonction de votre fuseau horaire et des paramètres DST ce que cela signifie pour votre heure locale (ou utiliser d'autres utilitaires Java pour le faire pour vous).
Shadow Man
100

Puisque personne d'autre ne l'a mentionné…

Il n'est pas sûr de comparer les résultats des System.nanoTime()appels entre différents threads. Même si les événements des threads se produisent dans un ordre prévisible, la différence en nanosecondes peut être positive ou négative.

System.currentTimeMillis() est sûr pour une utilisation entre les threads.

joufflu
la source
4
Sur Windows qui ne tient que jusqu'au SP2 selon: stackoverflow.com/questions/510462/…
Peter Schmitz
3
Bien, tu apprends quelque chose de nouveau tous les jours. Je soupçonne, cependant, qu'étant donné que cela n'était pas sûr dans le passé (renvoie définitivement des lectures absurdes sur les threads), une telle utilisation est probablement toujours en dehors des spécifications et devrait donc probablement être évitée.
gubby
4
@jgubby: Très intéressant ... toute référence au support qui n'est pas sûre pour comparer les résultats d'appels System.nanoTime () entre différents Threads ? Les liens suivants valent la peine d'être vus : bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519418 docs.oracle.com/javase/7/docs/api/java/lang/…
user454322
15
En regardant la description mentionnée ici: docs.oracle.com/javase/7/docs/api/java/lang/… , il semble que la différence entre les valeurs renvoyées par nanoTime soit valide pour comparaison tant qu'elles proviennent de la même 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
7
Le JavaDoc pournanoTime() dit: La même origine est utilisée par toutes les invocations de cette méthode dans une instance d'une machine virtuelle Java; d'autres instances de machine virtuelle sont susceptibles d'utiliser une origine différente. ce qui signifie qu'il ne retourner les mêmes dans les discussions.
Simon Forsberg
58

Mise à jour d' Arkadiy : j'ai observé un comportement plus correct deSystem.currentTimeMillis() Windows 7 dans Oracle Java 8. L'heure a été renvoyée avec une précision d'une milliseconde. Le code source dans OpenJDK n'a pas changé, donc je ne sais pas ce qui cause le meilleur comportement.


David Holmes de Sun a publié il y a quelques années un article de blog qui présente de manière très détaillée les API de synchronisation Java (en particulier System.currentTimeMillis()etSystem.nanoTime() ), quand vous souhaitez les utiliser et comment elles fonctionnent en interne.

À l'intérieur de la machine virtuelle Hotspot: horloges, minuteurs et événements de planification - Partie I - Windows

Un aspect très intéressant du minuteur utilisé par Java sur Windows pour les API qui ont un paramètre d'attente temporisée est que la résolution du minuteur peut changer en fonction des autres appels d'API qui ont été effectués - à l'échelle du système (pas seulement dans le processus particulier) . Il montre un exemple où l'utilisation Thread.sleep()entraînera ce changement de résolution.

Michael Burr
la source
1
@Arkadiy: Avez-vous une source pour la déclaration dans la mise à jour?
Lii
@Lii - Malheureusement non. Cela vient de moi qui exécute le code dans cette question: stackoverflow.com/questions/34090914/… . Le code produit une précision de 15 ms avec Java 7 et une précision de 1 ms avec Java 8
2
J'ai tracé le currentTimeMillis à os :: javaTimeMillis dans hotspot / src / os / windows / vm / os_windows.cpp dans OpenJDK ( hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os/… ) . On dirait que c'est toujours GetSystemTimeAsFileTime, donc je ne sais pas d'où vient le changement. Ou si c'est même valable. Testez avant d'utiliser.
le changement de comportement est le résultat du changement de GetSystemTimeAsFileTimefonctionnement dans XP vs 7. Voir ici pour plus de détails (tl; dr il est devenu plus précis depuis que le système entier a introduit des méthodes de chronométrage plus précises).
11

System.nanoTime()n'est pas pris en charge dans les anciennes machines virtuelles Java. Si cela vous préoccupe, restez aveccurrentTimeMillis

Concernant la précision, vous avez presque raison. Sur CERTAINES machines Windows,currentTimeMillis() a une résolution d'environ 10 ms (et non 50 ms). Je ne sais pas pourquoi, mais certaines machines Windows sont tout aussi précises que les machines Linux.

J'ai utilisé GAGETimer dans le passé avec un succès modéré.

Paul Morel
la source
3
"anciennes machines virtuelles Java" comme dans lesquelles? Java 1.2 ou quelque chose?
Simon Forsberg
1
System.nanoTime () a été introduit dans Java 1.5 en 2004. La prise en charge étendue de Java 1.4 en 2013, il est donc assez sûr de dire que System.nanoTime () peut maintenant être invoqué et cette réponse est désormais obsolète.
Dave L.
9

Comme d'autres l'ont dit, currentTimeMillis est l'heure de l'horloge, qui change en raison de l'heure d'été, des utilisateurs changeant les paramètres de l'heure, des secondes intercalaires et de la synchronisation de l'heure Internet. Si votre application dépend de l'augmentation monotone des valeurs de temps écoulé, vous préférerez peut-être nanoTime à la place.

Vous pourriez penser que les joueurs ne joueront pas avec les paramètres de temps pendant le jeu, et peut-être auriez-vous raison. Mais ne sous-estimez pas la perturbation due à la synchronisation de l'heure Internet, ou peut-être aux utilisateurs de postes de travail distants. L'API nanoTime est à l'abri de ce type de perturbation.

Si vous souhaitez utiliser l'heure de l'horloge, mais évitez les discontinuités dues à la synchronisation de l'heure sur Internet, vous pouvez envisager un client NTP tel que Meinberg, qui "ajuste" la fréquence d'horloge pour la mettre à zéro, au lieu de simplement réinitialiser l'horloge périodiquement.

Je parle d'expérience personnelle. Dans une application météo que j'ai développée, j'obtenais des pointes de vitesse du vent aléatoires. Il m'a fallu un certain temps pour réaliser que ma base de temps était perturbée par le comportement de l'horloge sur un PC typique. Tous mes problèmes ont disparu lorsque j'ai commencé à utiliser nanoTime. La cohérence (monotonie) était plus importante pour mon application que la précision brute ou la précision absolue.

KarlU
la source
19
"currentTimeMillis est l'heure de l'horloge, qui change en raison de l'heure d'été" ... à peu près sûr que cette déclaration est fausse. System.currentTimeMillis()rapporte le temps écoulé (en millisecondes) depuis l'époque Unix / Posix qui est minuit, le 1er janvier 1970 UTC. Étant donné que l'UTC n'est jamais ajusté pour l'heure d'été, cette valeur ne sera pas compensée lorsque les fuseaux horaires locaux ajoutent ou soumettent des décalages aux heures locales pour l'heure d'été. De plus, l'échelle de temps Java adoucit les secondes intercalaires de sorte que les jours semblent continuer à compter 86 400 secondes.
scottb
5

Oui, si une telle précision est requise, utilisez System.nanoTime() , sachez que vous avez alors besoin d'une JVM Java 5+.

Sur mes systèmes XP, je vois l'heure système signalée à au moins 100 microsecondes 278 nanosecondes en utilisant le code suivant:

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
la source
4

Pour des graphiques de jeu et des mises à jour de position fluides, utilisez System.nanoTime()plutôt que System.currentTimeMillis(). Je suis passé de currentTimeMillis () à nanoTime () dans un jeu et j'ai obtenu une amélioration visuelle majeure de la fluidité du mouvement.

Alors qu'une milliseconde peut sembler être déjà précise, visuellement ce n'est pas le cas. Les facteurs nanoTime()peuvent s'améliorer, notamment:

  • positionnement précis des pixels en dessous de la résolution de l'horloge murale
  • possibilité d'anti-alias entre pixels, si vous le souhaitez
  • Imprécision de l'horloge murale Windows
  • gigue d'horloge (incohérence du moment où l'horloge murale avance réellement)

Comme d'autres réponses le suggèrent, nanoTime a un coût de performance s'il est appelé à plusieurs reprises - il serait préférable de l'appeler une seule fois par trame et d'utiliser la même valeur pour calculer la trame entière.

Thomas W
la source
1

J'ai une bonne expérience avec le nanotime . Il fournit une horloge murale en deux longs (secondes depuis l'époque et nanosecondes dans cette seconde), en utilisant une bibliothèque JNI. Il est disponible avec la partie JNI précompilée pour Windows et Linux.

Jon Bright
la source
1

System.currentTimeMillis()n'est pas sûr pour le temps écoulé car cette méthode est sensible aux changements d'horloge en temps réel du système. Tu devrais utiliserSystem.nanoTime . Veuillez consulter l'aide de Java System:

À propos de la méthode nanoTime:

.. Cette méthode fournit une précision en nanosecondes, mais pas nécessairement une résolution en nanosecondes (c'est-à-dire la fréquence à laquelle la valeur change) - aucune garantie n'est apportée, sauf que la résolution est au moins aussi bonne que celle de currentTimeMillis () ..

Si vous utilisez System.currentTimeMillis()votre temps écoulé peut être négatif (Retour <- vers le futur)

Ricardo Gasca
la source
-3

une chose ici est l'incohérence de la méthode nanoTime.il ne donne pas de valeurs très cohérentes pour la même entrée.currentTimeMillis fait beaucoup mieux en termes de performances et de cohérence, et aussi, bien que pas aussi précis que nanoTime, a une marge d'erreur inférieure , et donc plus de précision dans sa valeur. je vous suggère donc d'utiliser currentTimeMillis

sarvesh
la source
6
Comme indiqué dans d'autres réponses et commentaires, currentTimeMillis est soumis à des changements d'horloge système et donc un mauvais choix pour le calcul du temps écoulé depuis un événement antérieur dans la JVM.
marqueurs duelin