web-dev-qa-db-fra.com

Temps de mesure des frais généraux en Java

Lors de la mesure du temps écoulé à un faible niveau, j'ai le choix d'utiliser l'un de ces éléments:

System.currentTimeMillis();
System.nanoTime();

Les deux méthodes sont implémentées native. Avant de creuser dans un code C, quelqu'un sait-il s'il y a des frais généraux importants pour appeler l'un ou l'autre? Je veux dire, si je ne me soucie pas vraiment de la précision supplémentaire, laquelle devrait être moins gourmande en temps CPU?

N.B: J'utilise le standard Java 1.6 JDK, mais la question peut être valable pour n'importe quel JRE ...

45
Lukas Eder

La réponse marquée correcte sur cette page n'est en fait pas correcte. Ce n'est pas un moyen valable d'écrire un benchmark en raison de l'élimination du code mort JVM (DCE), du remplacement sur pile (OSR), du déroulement de la boucle, etc. Seul un framework comme le framework de micro-benchmarking JMH d'Oracle peut mesurer correctement quelque chose comme ça. Lisez cet article si vous avez des doutes sur la validité de ces micro-repères.

Voici un benchmark JMH pour System.currentTimeMillis() vs System.nanoTime():

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class NanoBench {
   @Benchmark
   public long currentTimeMillis() {
      return System.currentTimeMillis();
   }

   @Benchmark
   public long nanoTime() {
    return System.nanoTime();
   }
}

Et voici les résultats (sur un Intel Core i5):

Benchmark                            Mode  Samples      Mean   Mean err    Units
c.z.h.b.NanoBench.currentTimeMillis  avgt       16   122.976      1.748    ns/op
c.z.h.b.NanoBench.nanoTime           avgt       16   117.948      3.075    ns/op

Ce qui montre que System.nanoTime() est légèrement plus rapide à ~ 118ns par invocation par rapport à ~ 123ns. Cependant, il est également clair qu'une fois l'erreur moyenne prise en compte, il y a très peu de différence entre les deux. Les résultats sont également susceptibles de varier selon le système d'exploitation. Mais la conclusion générale devrait être qu'ils sont essentiellement équivalents en termes de frais généraux.

MISE À JOUR 2015/08/25: Bien que cette réponse soit plus proche de la correction que la plupart, en utilisant JMH pour mesurer, elle n'est toujours pas correcte. Mesurer quelque chose comme System.nanoTime() lui-même est un type particulier de benchmarking tordu. La réponse et l'article définitif est ici .

43
brettw

Je ne pense pas que vous ayez à vous soucier des frais généraux de l'un ou de l'autre. C'est tellement minime qu'il est à peine mesurable. Voici un micro-benchmark rapide des deux:

for (int j = 0; j < 5; j++) {
    long time = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        long x = System.currentTimeMillis();
    }
    System.out.println((System.nanoTime() - time) + "ns per million");

    time = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        long x = System.nanoTime();
    }
    System.out.println((System.nanoTime() - time) + "ns per million");

    System.out.println();
}

Et le dernier résultat:

14297079ns per million
29206842ns per million

Il semble que System.currentTimeMillis() soit deux fois plus rapide que System.nanoTime(). Cependant, 29ns va être beaucoup plus court que tout ce que vous mesureriez de toute façon. J'irais pour System.nanoTime() pour la précision et l'exactitude car il n'est pas associé aux horloges.

25
WhiteFang34

Vous ne devez utiliser que System.nanoTime() pour mesurer le temps nécessaire à l'exécution. Ce n'est pas seulement une question de précision en nanosecondes, System.currentTimeMillis() est "l'heure de l'horloge murale" tandis que System.nanoTime() est destiné à chronométrer les choses et n'a pas les bizarreries "temps réel" que le d'autres le font. À partir du Javadoc de System.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.

11
ColinD

Si vous avez le temps, regardez cette conférence de Cliff Click , il parle du prix de System.currentTimeMillis ainsi que d'autres choses.

6
mindas

System.currentTimeMillis() est généralement très rapide (afaik 5-6 cycles de processeur mais je ne sais plus où j'ai lu ceci), mais sa résolution varie sur différentes plates-formes.

Donc, si vous avez besoin d'une grande précision, optez pour nanoTime(), si vous êtes préoccupé par les frais généraux, optez pour currentTimeMillis().

6
Nikolaus Gradwohl

La réponse acceptée à cette question est en effet incorrecte. La réponse alternative fournie par @brettw est bonne mais néanmoins légère sur les détails.

Pour un traitement complet de ce sujet et le coût réel de ces appels, veuillez consulter https://shipilev.net/blog/2014/nanotrusting-nanotime/

Pour répondre à la question posée:

quelqu'un sait-il s'il y a des frais généraux importants pour appeler l'un ou l'autre?

  • Les frais généraux d'appeler System#nanoTime est compris entre 15 et 30 nanosecondes par appel.
  • La valeur indiquée par nanoTime, sa résolution, ne change qu'une fois toutes les 30 nanosecondes

Cela signifie que si vous essayez de faire des millions de requêtes par seconde, appeler nanoTime signifie que vous perdez effectivement une énorme partie du deuxième appelant nanoTime. Pour de tels cas d'utilisation, envisagez de mesurer les demandes du côté client, vous assurant ainsi de ne pas tomber dans omission coordonnée , la mesure de la profondeur de la file d'attente est également un bon indicateur.

Si vous n'essayez pas de charger autant de travail que vous le pouvez en une seule seconde, alors nanoTime n'aura pas vraiment d'importance mais l'omission coordonnée est toujours un facteur.

Enfin, pour être complet, currentTimeMillis ne peut pas être utilisé quel que soit son coût. En effet, il n'est pas garanti d'avancer entre deux appels. Surtout sur un serveur avec NTP, currentTimeMillis se déplace constamment. Sans oublier que la plupart des choses mesurées par un ordinateur ne prennent pas une milliseconde entière.

4
pjulien

Au niveau théorique, pour un VM qui utilise des threads natifs et se trouve sur un système d'exploitation préemptif moderne, le currentTimeMillis peut être implémenté pour être lu une seule fois par tranche de temps. Vraisemblablement, les implémentations de nanoTime ne sacrifiez la précision.

2
Dilum Ranatunga