System.nanoTime () est-il complètement inutile?


153

Comme documenté dans le billet de blog Attention à System.nanoTime () en Java , sur les systèmes x86, System.nanoTime () de Java renvoie la valeur de temps en utilisant un compteur spécifique au processeur . Considérons maintenant le cas suivant que j'utilise pour mesurer le temps d'un appel:

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

Or, dans un système multicœur, il se pourrait qu'après la mesure du temps1, le thread soit planifié sur un processeur différent dont le compteur est inférieur à celui de la CPU précédente. Ainsi, nous pourrions obtenir une valeur dans time2 qui est inférieure à time1. Ainsi, nous aurions une valeur négative en timeSpent.

Compte tenu de ce cas, n'est-ce pas que System.nanotime est pratiquement inutile pour le moment?

Je sais que la modification de l'heure du système n'affecte pas le nanotime. Ce n'est pas le problème que je décris ci-dessus. Le problème est que chaque CPU conservera un compteur différent depuis sa mise sous tension. Ce compteur peut être inférieur sur la deuxième CPU par rapport à la première CPU. Étant donné que le thread peut être planifié par le système d'exploitation sur le deuxième processeur après avoir obtenu time1, la valeur de timeSpent peut être incorrecte et même négative.


2
Je n'ai pas de réponse mais je suis d'accord avec vous. Peut-être que cela devrait être considéré comme un bogue dans la JVM.
Aaron Digulla le

2
ce message est incorrect et ne pas utiliser TSC est lent mais vous devez vivre avec: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6440250 TSC peut également être rendu utile via l'hyperviseur, mais il est à nouveau lent.
bestsss

1
Et bien sûr, vous pouvez exécuter dans une machine virtuelle où un processeur peut apparaître à mi-chemin d'une session: D
Expiation limitée

Réponses:


206

Cette réponse a été écrite en 2011 du point de vue de ce que faisait réellement le Sun JDK de l'époque fonctionnant sur les systèmes d'exploitation de l'époque. C'était il y a très longtemps! La réponse de leventov offre une perspective plus actuelle.

Ce message est faux et nanoTimeest sûr. Il y a un commentaire sur l'article qui renvoie à un article de blog de David Holmes , un gars du temps réel et de la concurrence chez Sun. Ça dit:

System.nanoTime () est implémenté à l'aide de l'API QueryPerformanceCounter / QueryPerformanceFrequency [...] Le mécanisme par défaut utilisé par QPC est déterminé par la couche d'abstraction matérielle (HAL) [...] Cette valeur par défaut change non seulement à travers le matériel, mais aussi à travers le système d'exploitation versions. Par exemple, Windows XP Service Pack 2 a changé les choses pour utiliser la minuterie de gestion de l'alimentation (PMTimer) plutôt que le compteur d'horodatage du processeur (TSC) en raison de problèmes avec le TSC non synchronisé sur différents processeurs dans les systèmes SMP, et en raison de sa fréquence peut varier (et donc sa relation avec le temps écoulé) en fonction des paramètres de gestion de l'alimentation.

Donc, sous Windows, c'était un problème jusqu'à WinXP SP2, mais ce n'est pas le cas maintenant.

Je ne trouve pas de partie II (ou plus) qui parle d'autres plates-formes, mais cet article comprend une remarque que Linux a rencontré et résolu le même problème de la même manière, avec un lien vers la FAQ pour clock_gettime (CLOCK_REALTIME) , qui dit:

  1. Clock_gettime (CLOCK_REALTIME) est-il cohérent sur tous les processeurs / cœurs? (L'arc est-il important? Par exemple, ppc, arm, x86, amd64, sparc)

Il devrait ou c'est considéré comme un buggy.

Cependant, sur x86 / x86_64, il est possible de voir des TSC à fréquence variable ou non synchronisés provoquer des incohérences temporelles. Les noyaux 2.4 n'avaient vraiment aucune protection contre cela, et les premiers noyaux 2.6 ne fonctionnaient pas très bien ici non plus. À partir de 2.6.18 et plus, la logique de détection est meilleure et nous reviendrons généralement à une source d'horloge sûre.

ppc a toujours une base de temps synchronisée, donc cela ne devrait pas être un problème.

Donc, si le lien de Holmes peut être lu comme impliquant des nanoTimeappels clock_gettime(CLOCK_REALTIME), alors c'est sûr à partir du noyau 2.6.18 sur x86, et toujours sur PowerPC (car IBM et Motorola, contrairement à Intel, savent en fait comment concevoir des microprocesseurs).

Il n'y a malheureusement aucune mention de SPARC ou de Solaris. Et bien sûr, nous n'avons aucune idée de ce que font les JVM IBM. Mais les JVM Sun sur Windows et Linux modernes réussissent parfaitement.

EDIT: Cette réponse est basée sur les sources qu'elle cite. Mais je crains toujours que cela soit complètement faux. Des informations plus récentes seraient vraiment précieuses. Je viens de tomber sur un lien vers un article plus récent de quatre ans sur les horloges de Linux qui pourrait être utile.


13
Même WinXP SP2 semble en souffrir. Exécution de l'exemple de code original, avec void foo() { Thread.sleep(40); }un temps négatif (-380 ms!) En utilisant un seul Athlon 64 X2 4200+processeur
Luke Usherwood

Je suppose qu'il n'y a pas de mise à jour à ce sujet, wrt. comportement sur Linux, BSD ou autres plates-formes?
Tomer Gabel

6
Bonne réponse, devrait ajouter un lien vers l'exploration plus récente de ce sujet: shipilev.net/blog/2014/nanotrusting-nanotime
Nitsan Wakart

1
@SOFe: Oh, c'est dommage. C'est dans l' archive Web , heureusement. Je vais voir si je peux retrouver une version actuelle.
Tom Anderson

1
Remarque: OpenJDK n'a pas respecté les spécifications jusqu'à OpenJDK 8u192, voir bugs.openjdk.java.net/browse/JDK-8184271 . Assurez-vous d'utiliser au moins une nouvelle version d'OpenJDK 8 ou d'OpenJDK 11+.
leventov

35

J'ai fait un peu de recherche et j'ai trouvé que si quelqu'un est pédant, alors oui, cela pourrait être considéré comme inutile ... dans des situations particulières ... cela dépend de la sensibilité temporelle de vos exigences ...

Consultez cette citation du site Java Sun:

L'horloge temps réel et System.nanoTime () sont tous deux basés sur le même appel système et donc sur la même horloge.

Avec Java RTS, toutes les API basées sur le temps (par exemple, les minuteurs, les threads périodiques, la surveillance des délais, etc.) sont basées sur le minuteur haute résolution. Et, avec les priorités en temps réel, ils peuvent garantir que le code approprié sera exécuté au bon moment pour les contraintes en temps réel. En revanche, les API Java SE ordinaires ne proposent que quelques méthodes capables de gérer des temps de haute résolution, sans aucune garantie d'exécution à un moment donné. L'utilisation de System.nanoTime () entre différents points du code pour effectuer des mesures de temps écoulé doit toujours être précise.

Java a également une mise en garde pour la méthode nanoTime () :

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 les nanosecondes depuis un certain temps fixe mais arbitraire (peut-être dans le futur, les valeurs peuvent donc être négatives). Cette méthode fournit une précision nanoseconde, mais pas nécessairement une précision nanoseconde. Aucune garantie n'est donnée sur la fréquence à laquelle les valeurs changent. Les différences dans les appels successifs qui s'étalent sur plus d'environ 292,3 ans (2 63 nanosecondes) ne permettront pas de calculer avec précision le temps écoulé en raison d'un dépassement numérique.

Il semblerait que la seule conclusion qui puisse être tirée est que nanoTime () ne peut pas être considéré comme une valeur précise. En tant que telle, si vous n'avez pas besoin de mesurer des temps séparés par de simples nanosecondes, cette méthode est suffisante même si la valeur renvoyée résultante est négative. Cependant, si vous avez besoin d'une précision plus élevée, ils semblent vous recommander d'utiliser JAVA RTS.

Donc, pour répondre à votre question ... aucun nanoTime () n'est pas inutile ... ce n'est tout simplement pas la méthode la plus prudente à utiliser dans toutes les situations.


3
> cette méthode est assez bonne même si la valeur renvoyée résultante est négative. Je ne comprends pas cela, si la valeur en timespent est négative, alors en quoi est-ce utile pour mesurer le temps pris dans foo ()?
pdeva le

3
c'est ok parce que tout ce qui vous préoccupe, c'est la valeur absolue de la différence. c'est-à-dire si votre mesure est le temps t où t = t2 - t1 alors vous voulez savoir | t | .... alors que faire si la valeur est négative ... même avec le problème multicœur, l'impact sera rarement de quelques nanosecondes de toute façon.
mezoid le

3
Pour sauvegarder @Aaron: t2 et t1 peuvent être négatifs mais (t2-t1) ne doit pas être négatif.
jfs

2
aaron: c'est exactement ce que je veux dire. t2-t1 ne devrait jamais être négatif sinon nous avons un bug.
pdeva

12
@pdeva - mais vous ne comprenez pas ce que dit le doc. Vous soulevez un non-problème. Il y a un moment qui est considéré comme "0". Les valeurs renvoyées par nanoTime () sont précises par rapport à cette heure. C'est une chronologie croissante monotone. Vous pourriez juste obtenir une série de chiffres de la partie négative de cette chronologie. -100, -99, -98(Valeurs évidemment beaucoup plus importantes dans la pratique). Ils vont dans la bonne direction (en augmentant), il n'y a donc pas de problème ici.
ToolmakerSteve

18

Pas besoin de débattre, utilisez simplement la source. Ici, SE 6 pour Linux, faites vos propres conclusions:

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

7
Cela n'est utile que si vous savez ce que fait l'API utilisée. L'API utilisée est implémentée par le système d'exploitation; ce code est correct. la spécification de l'API utilisée (clock_gettime / gettimeofday), mais comme d'autres l'ont souligné, certains systèmes d'exploitation non à jour ont des implémentations boguées.
Blaisorblade

18

Depuis Java 7, la System.nanoTime()sécurité est garantie par la spécification JDK. System.nanoTime()Javadoc indique clairement que toutes les invocations observées dans une JVM (c'est-à-dire sur tous les threads) sont monotones:

La valeur renvoyée représente des nanosecondes depuis une heure d'origine fixe mais arbitraire (peut-être dans le futur, les valeurs peuvent donc être négatives). 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 utiliseront probablement une origine différente.

L'implémentation JVM / JDK est responsable de l'aplanissement des incohérences qui pourraient être observées lorsque les utilitaires du système d'exploitation sous-jacents sont appelés (par exemple ceux mentionnés dans la réponse de Tom Anderson ).

La majorité des autres anciennes réponses à cette question (écrites en 2009-2012) expriment un FUD qui était probablement pertinent pour Java 5 ou Java 6 mais qui n'est plus pertinent pour les versions modernes de Java.

Il convient cependant de mentionner que malgré nanoTime()la sécurité des garanties JDK , il y a eu plusieurs bogues dans OpenJDK qui l'empêchent de respecter cette garantie sur certaines plates-formes ou dans certaines circonstances (par exemple JDK-8040140 , JDK-8184271 ). Il n'y a pas de bogue ouvert (connu) dans OpenJDK nanoTime()pour le moment, mais la découverte d'un nouveau bogue ou une régression dans une nouvelle version d'OpenJDK ne devrait choquer personne.

Dans cet esprit, le code qui utilise nanoTime()pour le blocage temporisé, l'attente d'intervalle, les délais d'attente, etc. devrait de préférence traiter les différences de temps négatives (délais d'attente) comme des zéros plutôt que de lever des exceptions. Cette pratique est également préférable , car il est compatible avec le comportement de toutes les méthodes chronométrées d'attente dans toutes les classes java.util.concurrent.*, par exemple Semaphore.tryAcquire(), Lock.tryLock(), BlockingQueue.poll(), etc.

Néanmoins, il nanoTime()faut toujours préférer pour mettre en œuvre le blocage temporisé, l'attente d'intervalle, les délais d'attente, etc. à currentTimeMillis()parce que ce dernier est sujet au phénomène de «retour en arrière» (par exemple en raison de la correction de l'heure du serveur), c'est currentTimeMillis()-à- dire qu'il n'est pas adapté à la mesure des intervalles de temps du tout. Voir cette réponse pour plus d'informations.

Au lieu d'utiliser nanoTime()directement pour les mesures de temps d'exécution du code, des cadres d'analyse comparative et des profileurs spécialisés devraient de préférence être utilisés, par exemple JMH et async-profiler en mode de profilage d'horloge murale .


10

Avertissement: je suis le développeur de cette bibliothèque

Vous aimerez peut-être mieux ceci:

http://juliusdavies.ca/nanotime/

Mais il copie un fichier DLL ou Unix .so (objet partagé) dans le répertoire personnel de l'utilisateur actuel afin qu'il puisse appeler JNI.

Certaines informations générales sont sur mon site à:

http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html


@Julius Avez-vous supprimé la bibliothèque de votre site?
Nitin Dandriyal

6

Linux corrige les écarts entre les processeurs, mais pas Windows. Je vous suggère de supposer que System.nanoTime () n'est précis qu'à environ 1 micro-seconde. Un moyen simple d'obtenir une durée plus longue est d'appeler foo () 1000 fois ou plus et de diviser le temps par 1000.


2
Pourriez-vous fournir une référence (comportement sous Linux et Windows)?
jfs

Malheureusement, la méthode proposée sera généralement très imprécise car chaque événement tombant dans la fente de mise à jour de l'horloge murale de +/- 100 ms renverra souvent zéro pour les opérations inférieures à la seconde. La somme de 9 opérations chacune avec une durée de zéro est, eh bien, zéro, divisé par neuf est ... zéro. Inversement, l'utilisation de System.nanoTime () fournira des durées d'événements relativement précises (non nulles), qui ensuite additionnées et divisées par le nombre d'événements fourniront une moyenne très précise.
Darrell Teague

@DarrellTeague additionnant 1000 événements et les additionnant équivaut à l'heure de bout en bout.
Peter Lawrey

@DarrellTeague System.nanoTime () est précis à 1 micro-seconde ou mieux (pas 100 000 microsecondes) sur la plupart des systèmes. Le calcul de la moyenne de nombreuses opérations n'est pertinent que lorsque vous passez à quelques micro-secondes, et uniquement sur certains systèmes.
Peter Lawrey

1
Toutes mes excuses car il y avait une certaine confusion au sujet du langage utilisé pour «résumer» les événements. Oui, si le temps est marqué au début, disons, de 1000 opérations en sous-secondes, ils s'exécutent, puis le temps est à nouveau marqué à la fin et divisé - cela fonctionnerait pour certains systèmes en cours de développement pour obtenir une bonne approximation de la durée pour une durée donnée un événement.
Darrell Teague

5

Absolument pas inutile. Les amateurs de chronométrage soulignent correctement le problème multicœur, mais dans les applications de mots réels, il est souvent radicalement meilleur que currentTimeMillis ().

Lors du calcul des positions graphiques dans le cadre, les actualisations nanoTime () permettent un mouvement BEAUCOUP plus fluide dans mon programme.

Et je ne teste que sur des machines multicœurs.


5

J'ai vu un temps écoulé négatif rapporté à l'aide de System.nanoTime (). Pour être clair, le code en question est:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

et la variable «elapsedNanos» avait une valeur négative. (Je suis certain que l'appel intermédiaire a également pris moins de 293 ans, ce qui est le point de débordement pour les nanos stockés dans les longs :)

Cela s'est produit avec un matériel IBM v1.5 JRE 64 bits sur IBM P690 (multicœur) exécutant AIX. Je n'ai vu cette erreur se produire qu'une seule fois, elle semble donc extrêmement rare. Je ne connais pas la cause - est-ce un problème spécifique au matériel, un défaut JVM - je ne sais pas. Je ne connais pas non plus les implications pour la précision de nanoTime () en général.

Pour répondre à la question initiale, je ne pense pas que nanoTime soit inutile - il fournit un timing inférieur à la milliseconde, mais il existe un risque réel (pas seulement théorique) qu'il soit inexact que vous devez prendre en compte.


Hélas, semble être un problème de système d'exploitation / matériel. La documentation indique que les valeurs de base peuvent être négatives mais (un négatif plus grand moins un négatif plus petit) doit toujours être une valeur positive. En effet, l'hypothèse étant que dans le même thread, l'appel nanoTime () doit toujours renvoyer une valeur positive ou négative. Je n'ai jamais vu cela sur divers systèmes Unix et Windows depuis de nombreuses années, mais cela semble possible, surtout si le matériel / le système d'exploitation divise cette opération apparemment atomique entre les processeurs.
Darrell Teague

@BasilVandegriend ce n'est un bug nulle part. Selon la documentation, rarement le deuxième System.nanoTime () de votre exemple peut fonctionner sur un processeur différent et les valeurs nanoTime calculées sur ce processeur pourraient simplement être inférieures aux valeurs calculées sur le premier processeur. Ainsi, une valeur -ve pour elapsedNanos est possible
sans fin le

2

Cela ne semble pas être un problème sur un Core 2 Duo exécutant Windows XP et JRE 1.5.0_06.

Dans un test avec trois threads, je ne vois pas System.nanoTime () revenir en arrière. Les processeurs sont tous les deux occupés et les threads s'endorment parfois pour provoquer des mouvements de threads.

[EDIT] Je suppose que cela n'arrive que sur des processeurs physiquement séparés, c'est-à-dire que les compteurs sont synchronisés pour plusieurs cœurs sur le même die.


2
Cela n'arrivera probablement pas tout le temps, mais en raison de la façon dont nanotime () est implémenté, la possibilité est toujours là.
pdeva le

Je suppose que cela ne se produit que sur des processeurs physiquement séparés, c'est-à-dire que les compteurs sont synchronisés pour plusieurs cœurs sur le même die.
starblue

Même cela dépend de la mise en œuvre spécifique, IIRC. Mais c'est quelque chose dont le système d'exploitation est censé s'occuper.
Blaisorblade

1
Les compteurs RDTSC sur plusieurs cœurs du même processeur x86 ne sont pas nécessairement synchronisés - certains systèmes modernes permettent à différents cœurs de fonctionner à des vitesses différentes.
Jules

2

Non, ce n'est pas ... Cela dépend juste de votre CPU, vérifiez le Minuteur d'événements de haute précision pour savoir comment / pourquoi les choses sont traitées différemment en fonction du CPU.

En gros, lisez la source de votre Java et vérifiez ce que votre version fait avec la fonction, et si cela fonctionne avec le processeur sur lequel vous l'exécuterez.

IBM vous suggère même de l' utiliser pour l'analyse comparative des performances (un article de 2008, mais mis à jour).


Comme toutes les implémentations ont défini un comportement, "caveat emptor!"
David Schmitt le

2

Je fais un lien avec ce qui est essentiellement la même discussion où Peter Lawrey fournit une bonne réponse. Pourquoi j'obtiens un temps écoulé négatif en utilisant System.nanoTime ()?

Beaucoup de gens ont mentionné que dans Java System.nanoTime () pouvait renvoyer un temps négatif. Je m'excuse de répéter ce que d'autres personnes ont déjà dit.

  1. nanoTime () n'est pas une horloge mais un compteur de cycles CPU.
  2. La valeur de retour est divisée par la fréquence pour ressembler au temps.
  3. La fréquence du processeur peut fluctuer.
  4. Lorsque votre thread est planifié sur un autre processeur, il y a une chance d'obtenir nanoTime (), ce qui entraîne une différence négative. C'est logique. Les compteurs des CPU ne sont pas synchronisés.
  5. Dans de nombreux cas, vous pourriez obtenir des résultats assez trompeurs, mais vous ne pourrez pas le dire car le delta n'est pas négatif. Pensez-y.
  6. (non confirmé) Je pense que vous pouvez obtenir un résultat négatif même sur le même processeur si les instructions sont réorganisées. Pour éviter cela, vous devez invoquer une barrière de mémoire sérialisant vos instructions.

Ce serait cool si System.nanoTime () retournait coreID là où il s'exécutait.


1
Tous les points sauf 3. et 5. sont faux. 1. nanoTime () n'est pas un compteur de cycles CPU, c'est du temps nano . 2. La manière dont la valeur nanoTime est produite dépend de la plate-forme. 4. Non, la différence ne peut pas être négative, selon les spécifications de nanoTime (). En supposant qu'OpenJDK n'a pas de bogues avec nanoTime (), et qu'il n'y a pas de bogues non résolus connus pour le moment. 6. Les appels nanoTime ne peuvent pas être réorganisés dans un thread car il s'agit d'une méthode native et la JVM respecte l'ordre du programme. La JVM ne réorganise jamais les appels de méthode native car elle ne sait pas ce qui se passe à l'intérieur et ne peut donc pas prouver que de telles réorganisations seraient sûres.
leventov

En ce qui concerne 5. les résultats de différence nanoTime () peuvent en effet être trompeurs, mais pas pour les raisons présentées dans d'autres points de cette réponse. Mais plutôt pour les raisons présentées ici: shipilev.net/blog/2014/nanotrusting-nanotime
leventov

Ironiquement, concernant 6. il y avait eu un bogue dans OpenJDK spécifiquement en raison de réorganisations: bugs.openjdk.java.net/browse/JDK-8184271 . Dans OpenJDK, nanoTime () est intrinsèque et il était permis d'être réorganisé, c'était un bogue.
leventov

@leventov, est-ce que nanotime () est sûr à utiliser? Cela signifie qu'il ne peut pas renvoyer de valeurs négatives et est ~ précis dans la mesure du temps. Je ne vois pas l'intérêt d'exposer une fonction API qui est criblée de problèmes. Cet article est une preuve, commencé en 2009 et encore commenté en 2019. Pour les choses critiques pour la mission, j'imagine que les gens comptent sur des cartes de chronométrage comme Symmetricom
Vortex

1
Votre n ° 4 parle de différence négative , pas de valeurs: "il y a une chance d'obtenir nanoTime () qui entraîne une différence négative."
leventov

1

Java est multiplateforme et nanoTime dépend de la plate-forme. Si vous utilisez Java - quand n'utilisez pas nanoTime. J'ai trouvé de vrais bugs dans différentes implémentations jvm avec cette fonction.


0

La documentation Java 5 recommande également d'utiliser cette méthode dans le même but.

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.

Doc API Java 5


0

En outre, System.currentTimeMillies()change lorsque vous modifiez l'horloge de votre système, alors que ce System.nanoTime()n'est pas le cas, ce dernier est donc plus sûr pour mesurer les durées.


-3

nanoTimeest extrêmement incertain pour le timing. Je l'ai essayé sur mes algorithmes de test de primalité de base et il a donné des réponses qui étaient littéralement à une seconde d'intervalle pour la même entrée. N'utilisez pas cette méthode ridicule. J'ai besoin de quelque chose qui soit plus précis et précis que le temps millisime, mais pas aussi mauvais que nanoTime.


sans une source ou une meilleure explication ce commentaire est inutile
ic3
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.