Comment écrire (et exécuter) un micro-benchmark correct en Java?
Je cherche des exemples de code et des commentaires illustrant diverses choses à penser.
Exemple: Le benchmark devrait-il mesurer le temps / itération ou les itérations / temps, et pourquoi?
Connexes: l' analyse comparative du chronomètre est-elle acceptable?
java
jvm
benchmarking
jvm-hotspot
microbenchmark
John Nilsson
la source
la source
Réponses:
Conseils sur l'écriture de micro-benchmarks par les créateurs de Java HotSpot :
Règle 0: Lisez un article réputé sur les JVM et le micro-benchmarking. Un bon exemple est Brian Goetz, 2005 . N'attendez pas trop des micro-benchmarks; ils ne mesurent qu'une gamme limitée de caractéristiques de performances JVM.
Règle 1: incluez toujours une phase de préchauffage qui exécute votre noyau de test tout au long, suffisamment pour déclencher toutes les initialisations et compilations avant de chronométrer les phases. (Moins d'itérations est OK sur la phase d'échauffement. La règle de base est de plusieurs dizaines de milliers d'itérations de boucle interne.)
Règle 2: Toujours exécuter avec
-XX:+PrintCompilation
,-verbose:gc
etc., de sorte que vous pouvez vérifier que le compilateur et d' autres parties de la machine virtuelle Java ne font pas le travail inattendu pendant votre phase de synchronisation.Règle 2.1: Imprimez des messages au début et à la fin des phases de chronométrage et d'échauffement, afin de pouvoir vérifier qu'il n'y a pas de sortie de la règle 2 pendant la phase de chronométrage.
Règle 3: Soyez conscient de la différence entre
-client
et-server
, et OSR et des compilations régulières. Le-XX:+PrintCompilation
drapeau des rapports compilations OSR avec un arobase pour indiquer le point d'entrée non initial, par exemple:Trouble$1::run @ 2 (41 bytes)
. Préférez le serveur au client et régulier à l'OSR, si vous recherchez les meilleures performances.Règle 4: Soyez conscient des effets d'initialisation. N'imprimez pas pour la première fois pendant votre phase de synchronisation, car l'impression charge et initialise les classes. Ne chargez pas de nouvelles classes en dehors de la phase de préchauffage (ou de la phase de rapport final), sauf si vous testez le chargement de classe spécifiquement (et dans ce cas, ne chargez que les classes de test). La règle 2 est votre première ligne de défense contre de tels effets.
Règle 5: Soyez conscient des effets de désoptimisation et de recompilation. Ne prenez pas de chemin de code pour la première fois dans la phase de synchronisation, car le compilateur peut ordonner et recompiler le code, sur la base d'une hypothèse optimiste antérieure selon laquelle le chemin n'allait pas du tout être utilisé. La règle 2 est votre première ligne de défense contre de tels effets.
Règle 6: Utilisez les outils appropriés pour lire l'esprit du compilateur et attendez-vous à être surpris par le code qu'il produit. Inspectez le code vous-même avant de former des théories sur ce qui rend quelque chose plus rapide ou plus lent.
Règle 7: Réduisez le bruit dans vos mesures. Exécutez votre référence sur une machine silencieuse et exécutez-la plusieurs fois, en éliminant les valeurs aberrantes. Utilisez
-Xbatch
pour sérialiser le compilateur avec l'application et envisagez de définir-XX:CICompilerCount=1
pour empêcher le compilateur de s'exécuter en parallèle avec lui-même. Faites de votre mieux pour réduire les frais généraux du GC, définissezXmx
(assez grand) égalXms
et utilisez-leUseEpsilonGC
s'il est disponible.Règle 8: Utilisez une bibliothèque pour votre référence car elle est probablement plus efficace et a déjà été déboguée à cette seule fin. Tels que JMH , Caliper ou Bill and Paul's Excellent UCSD Benchmarks for Java .
la source
System.nanoTime()
n'est pas garanti d'être plus précis queSystem.currentTimeMillis()
. Il est seulement garanti d'être au moins aussi précis. Cependant, il est généralement beaucoup plus précis.System.nanoTime()
au lieu de,System.currentTimeMillis()
c'est que le premier est garanti d'augmenter de façon monotone. En soustrayant les valeurs renvoyées, deuxcurrentTimeMillis
invocations peuvent en fait donner des résultats négatifs, probablement parce que l'heure système a été ajustée par un démon NTP.Je sais que cette question a été marquée comme répondue, mais je voulais mentionner deux bibliothèques qui nous aident à écrire des micro-repères
Pied à coulisse de Google
Tutoriels de démarrage
JMH d'OpenJDK
Tutoriels de démarrage
la source
Les choses importantes pour les benchmarks Java sont:
System.gc()
entre les itérations, c'est une bonne idée de l'exécuter entre les tests, afin que chaque test obtienne, espérons-le, un espace mémoire «propre» pour fonctionner. (Oui,gc()
c'est plus un indice qu'une garantie, mais il est très probable que cela va vraiment ramasser les ordures selon mon expérience.)Je suis en train de bloguer sur la conception d'un cadre d'analyse comparative en .NET. J'ai un deux des postes précédents qui peuvent être en mesure de vous donner quelques idées - pas tout sera approprié, bien sûr, mais certaines d' entre elles peut - être.
la source
gc
toujours de la mémoire inutilisée.System.gc()
, comment proposez-vous de minimiser la collecte des ordures dans un test en raison des objets créés dans les tests précédents? Je suis pragmatique, pas dogmatique.jmh est un ajout récent à OpenJDK et a été écrit par certains ingénieurs de performance d'Oracle. Vaut vraiment le coup d'oeil.
Des informations très intéressantes enfouies dans les exemples de commentaires de tests .
Voir également:
la source
Cela dépend de ce vous essayez de tester.
Si vous êtes intéressé par la latence , utilisez le temps / itération et si vous êtes intéressé par le débit , utilisez les itérations / temps.
la source
Si vous essayez de comparer deux algorithmes, faites au moins deux tests de référence pour chacun, en alternant l'ordre. c'est à dire:
J'ai trouvé des différences notables (5-10% parfois) dans l'exécution du même algorithme dans différentes passes ..
Assurez-vous également que n est très grand, de sorte que le temps d'exécution de chaque boucle soit d'au moins 10 secondes environ. Plus il y a d'itérations, plus les chiffres sont significatifs dans votre temps de référence et plus les données sont fiables.
la source
Assurez-vous que vous utilisez en quelque sorte les résultats qui sont calculés dans du code de référence. Sinon, votre code peut être optimisé.
la source
Il existe de nombreux pièges possibles pour l'écriture de micro-benchmarks en Java.
Premièrement: vous devez calculer avec toutes sortes d'événements qui prennent du temps plus ou moins aléatoires: garbage collection, effets de mise en cache (d'OS pour les fichiers et de CPU pour la mémoire), IO etc.
Deuxièmement: vous ne pouvez pas faire confiance à l'exactitude des temps mesurés pour des intervalles très courts.
Troisièmement: la JVM optimise votre code lors de l'exécution. Ainsi, différentes exécutions dans la même instance JVM seront de plus en plus rapides.
Mes recommandations: faites fonctionner votre benchmark quelques secondes, ce qui est plus fiable qu'un runtime sur des millisecondes. Réchauffez la JVM (signifie exécuter le benchmark au moins une fois sans mesurer, afin que la JVM puisse exécuter des optimisations). Et exécutez votre référence plusieurs fois (peut-être 5 fois) et prenez la valeur médiane. Exécutez chaque micro-benchmark dans une nouvelle instance JVM (appelez chaque benchmark nouveau Java) sinon les effets d'optimisation de la JVM peuvent influencer les tests en cours d'exécution. N'exécutez pas des choses qui ne sont pas exécutées dans la phase de préchauffage (car cela pourrait déclencher le chargement de classe et la recompilation).
la source
Il convient également de noter qu'il pourrait également être important d'analyser les résultats du micro-benchmark lors de la comparaison des différentes implémentations. Par conséquent, un test de signification doit être effectué.
Cela est dû au fait que l'implémentation
A
peut être plus rapide pendant la plupart des exécutions du benchmark que l'implémentationB
. MaisA
peut également avoir un écart plus élevé, de sorte que l'avantage de performance mesuré deA
ne sera pas significatif par rapport àB
.Il est donc également important d'écrire et d'exécuter correctement un micro-benchmark, mais aussi de l'analyser correctement.
la source
Pour ajouter aux autres excellents conseils, je tiens également compte des points suivants:
Pour certains processeurs (par exemple la gamme Intel Core i5 avec TurboBoost), la température (et le nombre de cœurs actuellement utilisés, ainsi que leur pourcentage d'utilisation) affectent la vitesse d'horloge. Étant donné que les processeurs sont synchronisés dynamiquement, cela peut affecter vos résultats. Par exemple, si vous avez une application monothread, la vitesse d'horloge maximale (avec TurboBoost) est plus élevée que pour une application utilisant tous les cœurs. Cela peut donc interférer avec les comparaisons de performances mono et multi-thread sur certains systèmes. Gardez à l'esprit que la température et les volatilités affectent également la durée de maintien de la fréquence Turbo.
Peut-être un aspect plus fondamental sur lequel vous avez un contrôle direct: assurez-vous de mesurer la bonne chose! Par exemple, si vous utilisez
System.nanoTime()
pour comparer un morceau de code particulier, placez les appels à l'affectation dans des endroits qui ont du sens pour éviter de mesurer des choses qui ne vous intéressent pas. Par exemple, ne faites pas:Le problème est que vous n'obtenez pas immédiatement l'heure de fin lorsque le code est terminé. Essayez plutôt ce qui suit:
la source
println
, pas une ligne d'en-tête séparée ou quelque chose, etSystem.nanoTime()
doit être évalué comme la première étape dans la construction de l'argument de chaîne pour cet appel. Il n'y a rien qu'un compilateur puisse faire avec le premier qu'il ne puisse pas faire avec le second, et ni l'un ni l'autre ne les encourage même à faire un travail supplémentaire avant d'enregistrer une heure d'arrêt.http://opt.sourceforge.net/ Java Micro Benchmark - tâches de contrôle requises pour déterminer les caractéristiques de performances comparatives du système informatique sur différentes plates-formes. Peut être utilisé pour guider les décisions d'optimisation et comparer différentes implémentations Java.
la source