Je veux faire des tests de timing sur une application Java. C'est ce que je fais actuellement:
long startTime = System.currentTimeMillis();
doSomething();
long finishTime = System.currentTimeMillis();
System.out.println("That took: " + (finishTime - startTime) + " ms");
Y a-t-il quelque chose de "mal" avec des tests de performances comme celui-ci? Quelle est la meilleure façon?
Dupliquer : L'analyse comparative du chronomètre est-elle acceptable?
Le seul défaut de cette approche est que le temps "réel" que prend doSomething()
pour s'exécuter peut varier énormément en fonction des autres programmes exécutés sur le système et de sa charge. Cela rend la mesure des performances quelque peu imprécise.
Un moyen plus précis de suivre le temps qu'il faut pour exécuter le code, en supposant que le code est monothread, est de regarder le temps CPU consommé par le thread pendant l'appel. Vous pouvez le faire avec les classes JMX; en particulier, avec ThreadMXBean
. Vous pouvez récupérer une instance de ThreadMXBean
dans Java.lang.management.ManagementFactory
, et, si votre plate-forme le prend en charge (la plupart le font), utilisez la méthode getCurrentThreadCpuTime
à la place de System.currentTimeMillis
pour effectuer un test similaire. Gardez à l'esprit que getCurrentThreadCpuTime
rapporte le temps en nanosecondes, pas en millisecondes.
Voici un exemple de méthode (Scala) qui pourrait être utilisé pour effectuer une mesure:
def measureCpuTime(f: => Unit): Java.time.Duration = {
import Java.lang.management.ManagementFactory.getThreadMXBean
if (!getThreadMXBean.isThreadCpuTimeSupported)
throw new UnsupportedOperationException(
"JVM does not support measuring thread CPU-time")
var finalCpuTime: Option[Long] = None
val thread = new Thread {
override def run(): Unit = {
f
finalCpuTime = Some(getThreadMXBean.getThreadCpuTime(
Thread.currentThread.getId))
}
}
thread.start()
while (finalCpuTime.isEmpty && thread.isAlive) {
Thread.sleep(100)
}
Java.time.Duration.ofNanos(finalCpuTime.getOrElse {
throw new Exception("Operation never returned, and the thread is dead " +
"(perhaps an unhandled exception occurred)")
})
}
(N'hésitez pas à traduire ce qui précède en Java!)
Cette stratégie n'est pas parfaite, mais elle est moins sujette aux variations de charge du système.
Le code affiché dans la question n'est pas un bon code de mesure des performances:
Le compilateur peut choisir d'optimiser votre code en réorganisant les instructions. Oui, il peut le faire. Cela signifie que votre test peut échouer. Il peut même choisir d'intégrer la méthode testée et de réorganiser les instructions de mesure dans le code désormais intégré.
Le hotspot peut choisir de réorganiser vos instructions, le code en ligne, les résultats du cache, retarder l'exécution ...
Même en supposant que le compilateur/point d'accès ne vous a pas trompé, ce que vous mesurez est le "temps du mur". Ce que vous devez mesurer est le temps processeur (sauf si vous utilisez des ressources du système d'exploitation et souhaitez les inclure également ou si vous mesurez la contestation des verrous dans un environnement multithread).
La solution? Utilisez un vrai profileur. Il y en a beaucoup, à la fois des profileurs gratuits et des démos/essais temporisés de ceux qui font la force des publicités.
Utiliser un Java Profiler est la meilleure option et il vous donnera toutes les informations dont vous avez besoin dans le code. À savoir Temps de réponse, Thread CallTraces, Utilisations de la mémoire, etc.
Je vais vous suggérer [~ # ~] jensor [~ # ~] , une source ouverte Java Profiler, pour sa facilité d'utilisation et sans frais généraux sur le CPU Vous pouvez le télécharger, instrumenter le code et obtenir toutes les informations dont vous avez besoin sur votre code.
Vous pouvez le télécharger à partir de: http://jensor.sourceforge.net /
N'oubliez pas que la résolution de System.currentTimeMillis()
varie selon les différents systèmes d'exploitation. Je crois que Windows est d'environ 15 ms. Donc, si votre doSomething()
s'exécute plus rapidement que la résolution temporelle, vous obtiendrez un delta de 0. Vous pouvez exécuter doSomething()
dans une boucle plusieurs fois, mais la JVM peut l'optimiser.
J'imagine que vous voudriez faire quelque chose () avant de commencer à chronométrer aussi, pour que le code soit JITter et "réchauffé".
Eh bien, ce n'est qu'une partie des tests de performances. Selon la chose que vous testez, vous devrez peut-être examiner la taille du segment de mémoire, le nombre de threads, le trafic réseau ou toute une foule d'autres choses. Sinon, j'utilise cette technique pour des choses simples que je veux juste voir combien de temps cela prend pour fonctionner.
C'est bien lorsque vous comparez une implémentation à une autre ou essayez de trouver une partie lente dans votre code (bien que cela puisse être fastidieux). C'est une très bonne technique à connaître et vous l'utiliserez probablement plus que toute autre, mais familiarisez-vous également avec un outil de profilage.
Japex peut vous être utile, soit comme moyen de créer rapidement des benchmarks, soit comme un moyen d'étudier les problèmes de benchmarking dans Java via le code source).