{"id":1041,"date":"2020-04-29T10:35:00","date_gmt":"2020-04-29T08:35:00","guid":{"rendered":"https:\/\/www.loicmathieu.fr\/wordpress\/?p=1041"},"modified":"2020-12-01T19:57:48","modified_gmt":"2020-12-01T18:57:48","slug":"introduction-a-jmh-java-microbenchmark-harness","status":"publish","type":"post","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/introduction-a-jmh-java-microbenchmark-harness\/","title":{"rendered":"Introduction \u00e0 JMH &#8211; Java Microbenchmark Harness"},"content":{"rendered":"<p>Dans mon pr\u00e9c\u00e9dent article <a href=\"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/for-vs-stream\/\" target=\"_blank\" rel=\"noopener noreferrer\">For vs Stream<\/a>, j&rsquo;ai utilis\u00e9 <a href=\"https:\/\/openjdk.java.net\/projects\/code-tools\/jmh\/\" target=\"_blank\" rel=\"noopener noreferrer\">JMH \u2013 The Java Microbenchmark Harness<\/a>, un outil pour r\u00e9aliser des microbenchmarks de mani\u00e8re facile, et surtout, pertinente.<\/p>\n<p>Cet article \u00e0 pour but de vous pr\u00e9senter l&rsquo;outil et son utilisation.<\/p>\n<p>Mais tout d&rsquo;abord : c&rsquo;est quoi un microbenchmark ?<\/p>\n<h2>Microbenchmark<\/h2>\n<p>Benchmark ou banc d&rsquo;essai en fran\u00e7ais : un programme qui permet de mesurer les performances d&rsquo;un syst\u00e8me, pour le comparer \u00e0 d&rsquo;autres.<\/p>\n<p>Microbenchmark : un benchmark fait pour mesurer les performances d&rsquo;une tr\u00e8s petite, et tr\u00e8s sp\u00e9cifique, portion de code.<\/p>\n<p>Le probl\u00e8me d&rsquo;un microbenchmark est que sa pertinence est fortement li\u00e9e \u00e0 son environnement d&rsquo;ex\u00e9cution, en Java : la JVM.<\/p>\n<p>En effet, celle-ci contient de nombreux composants (interpr\u00e9teur de code, Garbage Collector &#8211; GC, Just In Time Compiler &#8211; JIT, &#8230;), et r\u00e9alise de nombreuses optimisations au fil du temps, via le JIT principalement.<\/p>\n<p>Si on veut qu&rsquo;un microbenchmark soit pertinent, il faut que les composants de la JVM ne viennent pas interagir avec la mani\u00e8re dont on ex\u00e9cute et mesure le benchmark.<\/p>\n<p>La mesure en elle-m\u00eame est fort complexe, si ce qu&rsquo;on veut mesurer est proche de la milliseconde, alors un simple passage du GC peut la doubler. De plus, mesurer le temps n&rsquo;est pas facile; si vous voulez plus d&rsquo;information sur la mesure du temps en Java, voici un excellent article : <a href=\"https:\/\/www.javaadvent.com\/2019\/12\/measuring-time-from-java-to-kernel-and-back.html\" target=\"_blank\" rel=\"noopener noreferrer\">Measuring time from Java to kernel and back<\/a>.<\/p>\n<p>Donc, r\u00e9aliser un microbenchmark via un simple Java main ou via un test JUnit n&rsquo;est pas une bonne fa\u00e7on de faire. Comme utiliser <code>System.currentTimeInMillis()<\/code>!\nIl nous faut un outil plus puissant, et qui sache s&rsquo;interfacer avec les composants internes de la JVM.<\/p>\n<p>Cet outil, c&rsquo;est <a href=\"https:\/\/openjdk.java.net\/projects\/code-tools\/jmh\/\" target=\"_blank\" rel=\"noopener noreferrer\">JMH \u2013 The Java Microbenchmark Harness<\/a>, cr\u00e9\u00e9 par <a href=\"https:\/\/twitter.com\/shipilev\" target=\"_blank\" rel=\"noopener noreferrer\">Aleksey Shipil\u00ebv<\/a>.<\/p>\n<h2>Ecrire un microbenchmark avec JMH<\/h2>\n<p>Pour g\u00e9n\u00e9rer un projet, le plus simple est d&rsquo;utiliser l&rsquo;archetype Maven fournit par JMH.<\/p>\n<pre>mvn archetype:generate \\\n          -DinteractiveMode=false \\\n          -DarchetypeGroupId=org.openjdk.jmh \\\n          -DarchetypeArtifactId=jmh-java-benchmark-archetype \\\n          -DgroupId=fr.loicmathieu.jmh \\\n          -DartifactId=jmh-benchmarks \\\n          -Dversion=1.0\n<\/pre>\n<p>Il g\u00e9n\u00e8re une classe MyBenchmark comme suit :<\/p>\n<pre>import org.openjdk.jmh.annotations.Benchmark;\n\npublic class MyBenchmark {\n\n    @Benchmark\n    public void testMethod() {\n        \/\/ This is a demo\/sample template for building your JMH benchmarks. \n        \/\/ Edit as needed.\n        \/\/ Put your benchmark code here.\n    }\n\n}\n<\/pre>\n<p>Dans un projet JMH, on peut h\u00e9berger plusieurs groupes de benchmark, chacun dans sa propre classe.<\/p>\n<p>Prenons un exemple un peu plus complet, tir\u00e9 de mes tests For vs Stream :<\/p>\n<pre>\npublic class ForVsStream {\n\n    @Param({\"10\", \"1000\", \"10000\"}) \/\/ \n    int size;\n\n    List list;\n\n    @Setup  \/\/ \n    public void setup() {\n        list = new ArrayList(size);\n        for (int i = 0; i &lt; size; i++) { list.add(i); } \n    } \n\n    @Benchmark \/\/ \n    public void testForLoop_doNothing(Blackhole bh) {  \/\/ \n        for (Integer i : list) {\n            bh.consume(i);\n        }\n    }\n\n    @Benchmark  \/\/ \n    public int testForLoop_Accumulation() {\n        int acc = 0;\n        for (Integer i : list) {\n            acc += i;\n        }\n        return acc;\n    }\n}\n<\/pre>\n<ol><li><code>@Param<\/code> permet de param\u00e9trer le test. JMH va r\u00e9aliser une ex\u00e9cution de benchmark par valeur de param\u00e8tre (ici 3 fois).<\/li>\n\n<li><code>@Setup<\/code> permet d&rsquo;ex\u00e9cuter du code, une fois avant chaque ex\u00e9cution de benchmark, pour initialiser le benchmark (ici initialiser la liste). C&rsquo;est tr\u00e8s important de n&rsquo;inclure dans la m\u00e9thode de benchmark que le code que l&rsquo;on veut mesurer, et d\u00e9porter dans une m\u00e9thode annot\u00e9e par <code>@Setup<\/code> tout le code d&rsquo;initialisation, sinon les r\u00e9sultats seront fauss\u00e9s.<\/li>\n\n<li><code>@Benchmark<\/code> d\u00e9finit une m\u00e9thode de benchmark. On a donc ici 2 benchmarks diff\u00e9rents dans la m\u00eame classe de benchmark<\/li>\n\n<li>Une des principales optimisations du JIT est l&rsquo;\u00e9limination de code mort (Dead Code Elimination &#8211; DCE), pour \u00e9viter cela, JMH fournit une classe <code>Blackhole<\/code> que l&rsquo;on peut utiliser. Ici, si on n&rsquo;avait rien fait dans le boucle for, le JIT aurait fini par \u00e9liminer celle-ci, et le benchmark aurait test\u00e9 un appel de m\u00e9thode vide.<\/li>\n\n<li>Dans ce cas-ci, comme la m\u00e9thode retourne une valeur, on n&rsquo;a pas besoin de <code>Blackhole<\/code>, retourner un objet suffit.<\/li>\n<\/ol>\n<h2>Ex\u00e9cuter le microbenchmark<\/h2>\n<p>Pour ex\u00e9cuter nos benchmarks, il y a deux solutions : programmatiquement via l&rsquo;API de JMH, ou via un JAR de benchmark.<\/p>\n<p>Pour l\u2019ex\u00e9cuter programmatiquement il faut ajouter un <code>main<\/code> \u00e0 votre classe de benchmark. On peut alors lancer le main depuis son IDE par exemple.<\/p>\n<pre>\npublic static void main(String[] args) throws RunnerException {\n    Options opt = new OptionsBuilder()\n            .include(ForVsStream.class.getSimpleName())\n            .build();\n\n    new Runner(opt).run();\n}\n<\/pre>\n<p>Bien que pratique, cette m\u00e9thode n&rsquo;est pas la plus recommand\u00e9e, pour passer des param\u00e8tres au benchmark on doit les coder en dur.\nLe lancement depuis un IDE est d\u00e9conseill\u00e9 par le cr\u00e9ateur de JMH : il faut r\u00e9aliser un package Maven avant l&rsquo;appel de la m\u00e9thode <code>main<\/code> pour initialiser l&rsquo;infrastructure JMH, et certains IDEs pourraient ne pas le faire.<\/p>\n<p>La m\u00e9thode conseill\u00e9e est le lancement via la ligne de commande Maven, apr\u00e8s avoir cr\u00e9\u00e9 l&rsquo;artefact de benchmark via <code>mvn clean package<\/code>. L&rsquo;artefact Maven cr\u00e9\u00e9 contient vos benchmarks, ainsi que le code d&rsquo;infrastructure de JMH. Il sera cr\u00e9\u00e9 dans le r\u00e9pertoire target et aura comme nom <code>benchmarks.jar<\/code>.\nOn pourra ensuite le lancer de mani\u00e8re classique via la ligne de commande java en donnant en param\u00e8tre le nom du benchmark \u00e0 ex\u00e9cuter : <code>java -jar target\/benchmarks.jar ForVsStream<\/code>.<\/p>\n<p>Par d\u00e9faut, pour chaque m\u00e9thode de benchmark (et chaque valeur de param\u00e8tre), JMH ex\u00e9cute 5 it\u00e9rations de 10s de pr\u00e9chauffe (warmup) et 5 it\u00e9rations de 10s de mesure.<\/p>\n<p>Et voici un exemple de r\u00e9sultats produit :<\/p>\n<pre>ForVsStream.testForLoop_doNothing                  10  avgt    5      46,715 \u00b1     4,581  ns\/op\nForVsStream.testForLoop_doNothing                1000  avgt    5    4581,216 \u00b1   355,653  ns\/op\nForVsStream.testForLoop_doNothing               10000  avgt    5   45320,910 \u00b1   670,446  ns\/op\nForVsStream.testForLoop_Accumulation               10  avgt    5      12,960 \u00b1     0,809  ns\/op\nForVsStream.testForLoop_Accumulation             1000  avgt    5     749,318 \u00b1    14,473  ns\/op\nForVsStream.testForLoop_Accumulation            10000  avgt    5    6679,846 \u00b1    81,442  ns\/op\n<\/pre>\n<p>On a ici 6 colonnes :<\/p>\n<ol><li>Le nom du microbenchmark (le nom de la m\u00e9thode pr\u00e9fix\u00e9e par le nom de la classe).<\/li>\n\n<li>La valeur du param\u00e8tre.<\/li>\n\n<li>Le mode de mesure : ici <code>avgt<\/code> pour average time : le temps moyen d\u2019ex\u00e9cution.<\/li>\n\n<li>Le nombre d&rsquo;it\u00e9rations de mesure.<\/li>\n\n<li>Le r\u00e9sultat : ici en nanoseconde par op\u00e9ration.<\/li>\n\n<li>L&rsquo;erreur statistique : si celle-ci est importante par rapport \u00e0 vos r\u00e9sultats (20 &#8211; 30%), c&rsquo;est qu&rsquo;il y a un soucis.<\/li>\n<\/ol>\n<p>On peut modifier la mani\u00e8re dont JMH r\u00e9alise l&rsquo;ex\u00e9cution du benchmark, et mesure les r\u00e9sultats, de plusieurs mani\u00e8res diff\u00e9rentes : via des param\u00e8tres de ligne de commande, via l&rsquo;<code>OptionsBuilder<\/code> si vous utilisez un <code>main<\/code>, ou via des annotations \u00e0 placer sur votre classe de Benchmark ou sur une de vos m\u00e9thodes.\nC&rsquo;est g\u00e9n\u00e9ralement cette option que je pr\u00e9f\u00e8re, car la d\u00e9finition de l&rsquo;ex\u00e9cution et de la mesure d\u00e9pend g\u00e9n\u00e9ralement de ce que l&rsquo;on veut mesurer, et n&rsquo;a pas besoin de changer fr\u00e9quemment (sinon, privil\u00e9giez la ligne de commande).<\/p>\n<p>Si vous vous demandez quelles sont les options que l&rsquo;on peut passer \u00e0 la ligne de commande, il y a une aide int\u00e9gr\u00e9e via <code>java -jar target\/benchmarks.jar -h<\/code>.<\/p>\n<p>Voici les options que je passe le plus souvent \u00e0 un benchmark, elles peuvent \u00eatre d\u00e9finies au niveau de la classe ou par m\u00e9thode de benchmark :<\/p>\n<pre>@BenchmarkMode(Mode.AverageTime)\n@OutputTimeUnit(TimeUnit.NANOSECONDS)\n@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)\n@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)\n@Fork(1)\npublic class ForVsStream {\n    \/\/ benchmark ...\n}\n<\/pre>\n<ul><li><code>@BenchmarkMode(Mode.AverageTime)<\/code> : d\u00e9finit le mode de benchmark <em>temps moyen<\/em>.<\/li>\n\n<li><code>@OutputTimeUnit(TimeUnit.NANOSECONDS)<\/code> : d\u00e9finit l&rsquo;unit\u00e9 de mesure.<\/li>\n\n<li><code>@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)<\/code> : d\u00e9finit qu&rsquo;on r\u00e9alisera 3 it\u00e9rations de pr\u00e9chauffe de 3 secondes<\/li>\n\n<li><code>@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)<\/code> : d\u00e9finit qu&rsquo;on r\u00e9alisera 3 it\u00e9rations de mesure de 3 secondes<\/li>\n\n<li><code>@Fork(1)<\/code> : combien de fork de VM. On peut aussi passer des options \u00e0 la JVM via cette annotation. A chaque it\u00e9ration (warmup, measurement) la VM est fork\u00e9e pour \u00e9viter que les optimisations de la JVM interf\u00e8rent. 1 fork est le d\u00e9faut. 0 fork permet de d\u00e9sactiver le fork (d\u00e9conseill\u00e9).<\/li>\n<\/ul>\n<p>La plus importante des options est le mode de benchmark, il y a 4 modes diff\u00e9rents, et celui-ci devrait toujours \u00eatre sp\u00e9cifi\u00e9 car si on ne sait pas ce que l&rsquo;on veut mesurer &#8230; alors pourquoi faire un benchmark ?<\/p>\n<ul><li><code>Mode.AverageTime<\/code> : temps moyen pris par l&rsquo;op\u00e9ration.<\/li>\n\n<li><code>Mode.Throughput<\/code> : nombre d&rsquo;op\u00e9rations par unit\u00e9 de mesure.<\/li>\n\n<li><code>Mode.SampleTime<\/code> : sample les op\u00e9rations (donc ne les enregistre pas toutes) et en calcule les statistiques (y compris les percentiles).<\/li>\n\n<li><code>Mode.SingleShotTime<\/code> : enregistre uniquement le temps d&rsquo;ex\u00e9cution de la premi\u00e8re it\u00e9ration.<\/li>\n<\/ul>\n<p>Les modes les plus classiques sont AverageTime et Throughput. On peut utiliser plusieurs modes sur un seul benchmark.<\/p>\n<p>Plus d&rsquo;informations \u00e0 ce sujet dans le sample JMH suivant : <a href=\"https:\/\/hg.openjdk.java.net\/code-tools\/jmh\/file\/b6f87aa2a687\/jmh-samples\/src\/main\/java\/org\/openjdk\/jmh\/samples\/JMHSample_02_BenchmarkModes.java\" target=\"_blank\" rel=\"noopener noreferrer\">JMHSample_02_BenchmarkModes.java<\/a>.<\/p>\n<p>Quand vous lancez un benchmark via la ligne de commande Java, chaque option JVM va \u00eatre pass\u00e9e au moment de la r\u00e9alisation du benchmark, m\u00eame en cas de fork.\nVous pourrez le constater dans le log de JMH.<\/p>\n<h2>Utiliser le profiler de JMH<\/h2>\n<p>Faire des benchmarks c&rsquo;est bien, mais comprendre les r\u00e9sultats c&rsquo;est encore mieux :).<\/p>\n<p>Et c&rsquo;est l\u00e0 qu&rsquo;entre en jeu le profiler de JMH. Ce n&rsquo;est pas un profiler aussi complet qu&rsquo;un profiler comme celui que vous trouverez dans VisualVM, votre IDE ou qu&rsquo;un profiler du march\u00e9 tel que yourkit ou async-profiler, mais il permet d&rsquo;examiner certains aspects de performance de votre benchmark facilement.<\/p>\n<p>Le profiler de JMH accepte un ensemble de modes de profiling (en fait l&rsquo;option <code>-prof<\/code> permet de lancer un ensemble de profilers), qui d\u00e9pend de votre OS et des logiciels install\u00e9s sur votre machine. Pour en connaitre la liste, vous pouvez lancer la commande <code>java -jar target\/benchmarks.jar  -lprof<\/code>.<\/p>\n<p>Voici son r\u00e9sultat sur mon syst\u00e8me (perf est install\u00e9, ce qui offre plus de profiler):<\/p>\n<pre>Supported profilers:\n          cl: Classloader profiling via standard MBeans \n        comp: JIT compiler profiling via standard MBeans \n          gc: GC profiling via standard MBeans \n       hs_cl: HotSpot (tm) classloader profiling via implementation-specific MBeans \n     hs_comp: HotSpot (tm) JIT compiler profiling via implementation-specific MBeans \n       hs_gc: HotSpot (tm) memory manager (GC) profiling via implementation-specific MBeans \n       hs_rt: HotSpot (tm) runtime profiling via implementation-specific MBeans \n      hs_thr: HotSpot (tm) threading subsystem via implementation-specific MBeans \n      pauses: Pauses profiler \n        perf: Linux perf Statistics \n     perfasm: Linux perf + PrintAssembly Profiler \n    perfnorm: Linux perf statistics, normalized by operation count \n  safepoints: Safepoints profiler \n       stack: Simple and naive Java stack profiler \n\nUnsupported profilers:\n   dtraceasm:  \n[sudo: dtrace\u00a0: commande introuvable\n]\n    xperfasm:  \n[Cannot run program \"xperf\": error=2, Aucun fichier ou dossier de ce type]\n<\/pre>\n<p>Parlons rapidement des profilers les plus classiques :<\/p>\n<ul><li><code>-prof gc<\/code> : permet de profiler le garbage collector, et donc d&rsquo;une mani\u00e8re tr\u00e8s basique la m\u00e9moire. Il permet entre autre de connaitre l&rsquo;allocation m\u00e9moire d&rsquo;un benchmark.<\/li>\n\n<li><code>-prof stack<\/code> : permet de profiler les stack Java, et donc d&rsquo;une mani\u00e8re tr\u00e8s basique le CPU.<\/li>\n\n<li><code>-prof perf<\/code> : permet d&rsquo;utiliser l&rsquo;outil de statistique OS <code>perf<\/code>.<\/li>\n\n<li><code>-prof perfasm<\/code> : permet d&rsquo;acc\u00e9der au code assembleur g\u00e9n\u00e9r\u00e9 dans les hotspot du code uniquement.<\/li>\n<\/ul>\n<p>Exemple de r\u00e9sultat avec un profiler de type <code>gc<\/code>:<\/p>\n<pre>Benchmark                                                                   (size)  Mode  Cnt       Score      Error   Units\nForVsStream.testForLoop_Accumulation                                            10  avgt   25      13,820 \u00b1    0,323   ns\/op\nForVsStream.testForLoop_Accumulation:\u00b7gc.alloc.rate                             10  avgt   25      \u2248 10\u207b\u2074             MB\/sec\nForVsStream.testForLoop_Accumulation:\u00b7gc.alloc.rate.norm                        10  avgt   25      \u2248 10\u207b\u2076               B\/op\nForVsStream.testForLoop_Accumulation:\u00b7gc.count                                  10  avgt   25         \u2248 0             counts\nForVsStream.testForLoop_Accumulation                                          1000  avgt   25     654,724 \u00b1   27,606   ns\/op\nForVsStream.testForLoop_Accumulation:\u00b7gc.alloc.rate                           1000  avgt   25      \u2248 10\u207b\u2074             MB\/sec\nForVsStream.testForLoop_Accumulation:\u00b7gc.alloc.rate.norm                      1000  avgt   25      \u2248 10\u207b\u2074               B\/op\nForVsStream.testForLoop_Accumulation:\u00b7gc.count                                1000  avgt   25         \u2248 0             counts\nForVsStream.testForLoop_Accumulation                                         10000  avgt   25    6717,517 \u00b1  642,994   ns\/op\nForVsStream.testForLoop_Accumulation:\u00b7gc.alloc.rate                          10000  avgt   25      \u2248 10\u207b\u2074             MB\/sec\nForVsStream.testForLoop_Accumulation:\u00b7gc.alloc.rate.norm                     10000  avgt   25       0,001 \u00b1    0,001    B\/op\nForVsStream.testForLoop_Accumulation:\u00b7gc.count                               10000  avgt   25         \u2248 0             counts\nForVsStream.testForLoop_doNothing                                               10  avgt   25      49,177 \u00b1    1,296   ns\/op\nForVsStream.testForLoop_doNothing:\u00b7gc.alloc.rate                                10  avgt   25      \u2248 10\u207b\u2074             MB\/sec\nForVsStream.testForLoop_doNothing:\u00b7gc.alloc.rate.norm                           10  avgt   25      \u2248 10\u207b\u2075               B\/op\nForVsStream.testForLoop_doNothing:\u00b7gc.count                                     10  avgt   25         \u2248 0             counts\nForVsStream.testForLoop_doNothing                                             1000  avgt   25    4843,801 \u00b1   76,651   ns\/op\nForVsStream.testForLoop_doNothing:\u00b7gc.alloc.rate                              1000  avgt   25      \u2248 10\u207b\u2074             MB\/sec\nForVsStream.testForLoop_doNothing:\u00b7gc.alloc.rate.norm                         1000  avgt   25      \u2248 10\u207b\u00b3               B\/op\nForVsStream.testForLoop_doNothing:\u00b7gc.count                                   1000  avgt   25         \u2248 0             counts\nForVsStream.testForLoop_doNothing                                            10000  avgt   25   48239,677 \u00b1 1162,210   ns\/op\nForVsStream.testForLoop_doNothing:\u00b7gc.alloc.rate                             10000  avgt   25      \u2248 10\u207b\u2074             MB\/sec\nForVsStream.testForLoop_doNothing:\u00b7gc.alloc.rate.norm                        10000  avgt   25       0,004 \u00b1    0,001    B\/op\nForVsStream.testForLoop_doNothing:\u00b7gc.count                                  10000  avgt   25         \u2248 0             counts\n<\/pre>\n<p>Exemple de r\u00e9sultat avec un profiler de type <code>stack<\/code>:<\/p>\n<pre>Result \"fr.loicmathieu.jmh.ForVsStream.testStream_AccumulationByMap\":\n  88,852 \u00b1(99.9%) 9,101 ns\/op [Average]\n  (min, avg, max) = (85,933, 88,852, 91,581), stdev = 2,364\n  CI (99.9%): [79,751, 97,953] (assumes normal distribution)\n\nSecondary result \"fr.loicmathieu.jmh.ForVsStream.testStream_AccumulationByMap:\u00b7stack\":\nStack profiler:\n\n....[Thread state distributions]....................................................................\n 50,0%         RUNNABLE\n 50,0%         TIMED_WAITING\n\n....[Thread state: RUNNABLE]........................................................................\n 23,0%  46,0% java.util.stream.AbstractPipeline.wrapSink\n 11,8%  23,5% java.util.stream.IntPipeline.reduce\n 10,7%  21,3% java.util.ArrayList$ArrayListSpliterator.forEachRemaining\n  4,3%   8,5% fr.loicmathieu.jmh.generated.ForVsStream_testStream_AccumulationByMap_jmhTest.testStream_AccumulationByMap_avgt_jmhStub\n  0,1%   0,2% java.util.stream.ReduceOps.makeInt\n  0,1%   0,1% java.util.ArrayList.spliterator\n  0,1%   0,1% java.util.stream.ReferencePipeline.mapToInt\n  0,0%   0,1% java.util.stream.StreamSupport.stream\n  0,0%   0,0% java.util.stream.ReduceOps$6.makeSink\n\n....[Thread state: TIMED_WAITING]...................................................................\n 50,0% 100,0% java.lang.Object.wait\n<\/pre>\n<h2>Utilisation avanc\u00e9e<\/h2>\n<ul><li><code>@TearDown<\/code> permet d&rsquo;ex\u00e9cuter du code apr\u00e8s l&rsquo;ex\u00e9cution du benchmark. Cela peut servir \u00e0 r\u00e9aliser des v\u00e9rifications (des assertions par exemple) pour \u00eatre s\u00fbr que le code de votre benchmark est bon (de la m\u00eame mani\u00e8re que chaque test JUnit doit comprendre des assertions JUnit).<\/li>\n\n<li><code>@State<\/code> permet de d\u00e9finir la port\u00e9e de l&rsquo;\u00e9tat de votre benchmark. Si vous utilisez des param\u00e8tres, des constantes, etc&#8230;; en utilisant <code>@State<\/code> sur la classe de votre benchmark (ou une de ses m\u00e9thodes), vous pouvez d\u00e9finir si ces param\u00e8tres doivent \u00eatre initialis\u00e9s au benchmark ou au Thread. Vous pouvez aussi d\u00e9finir des classes de State que vous pouvez ensuite injecter dans vos benchmarks, plus d&rsquo;information dans le sample <a href=\"https:\/\/hg.openjdk.java.net\/code-tools\/jmh\/file\/b6f87aa2a687\/jmh-samples\/src\/main\/java\/org\/openjdk\/jmh\/samples\/JMHSample_03_States.java\" target=\"_blank\" rel=\"noopener noreferrer\">JMHSample_03_States.java<\/a>\n<a href=\"https:\/\/hg.openjdk.java.net\/code-tools\/jmh\/file\/b6f87aa2a687\/jmh-samples\/src\/main\/java\/org\/openjdk\/jmh\/samples\/JMHSample_03_States.java\" target=\"_blank\" rel=\"noopener noreferrer\">JMHSample_03_States.java<\/a>.<\/li>\n<\/ul>\n<p>Voici un article en anglais qui introduit quelques autres concepts avanc\u00e9s : <a href=\"http:\/\/tutorials.jenkov.com\/java-performance\/jmh.html\" target=\"_blank\" rel=\"noopener noreferrer\"><a href=\"http:\/\/tutorials.jenkov.com\/java-performance\/jmh.html\">http:\/\/tutorials.jenkov.com\/java-performance\/jmh.html<\/a><\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>JMH est le meilleur outil pour \u00e9crire vos microbenchmarks en Java. Il est simple d&rsquo;utilisation pour les cas les plus simples, mais il compte plein de fonctionnalit\u00e9s avanc\u00e9es qui vous permettent de contr\u00f4ler le d\u00e9roulement du benchmark et d&rsquo;en comprendre les r\u00e9sultats.<\/p>\n<p>Mes conseils pour l&rsquo;\u00e9criture de vos benchmark :<\/p>\n<ul><li>Faites bien attention \u00e0 l&rsquo;\u00e9limination de code mort, et utilisez toujours une valeur de retour ou l&rsquo;objet <code>Blackhole<\/code>.<\/li>\n\n<li>Ne r\u00e9alisez que des choses simples dans vos microbenchmarks, d&rsquo;autres outils existent pour r\u00e9aliser des benchmark qui ne sont pas <em>micro<\/em> (Gatling par exemple) ou pour tester le comportement multi-thread de votre application (JCStress).<\/li>\n\n<li>Exp\u00e9rimentez de mani\u00e8re it\u00e9rative.<\/li>\n\n<li>Amusez-vous ;).<\/li>\n<\/ul>\n<p>Pour aller plus loin, lisez la documentation qui se trouve dans les commentaires des <a href=\"https:\/\/hg.openjdk.java.net\/code-tools\/jmh\/file\/tip\/jmh-samples\/src\/main\/java\/org\/openjdk\/jmh\/samples\/\" target=\"_blank\" rel=\"noopener noreferrer\">samples<\/a>, vous pouvez aussi relire mon article <a title=\"For vs Stream\" href=\"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/for-vs-stream\/\" target=\"_blank\" rel=\"noopener noreferrer\">For vs Stream<\/a> qui vous permet de mieux comprendre \u00e0 quoi JMH peut servir.<\/p>\n<p>Et le mot de la fin du mot de la fin est pour <a href=\"https:\/\/twitter.com\/shipilev\" target=\"_blank\" rel=\"noopener noreferrer\">Aleksey Shipil\u00ebv<\/a>, voici le warning qui est affich\u00e9 en m\u00eame temps que le rapport JMH :<\/p>\n<blockquote>\nREMEMBER: The numbers below are just data. \nTo gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM\/OS\/HW level, ask for reviews from the domain experts.\nDo not assume the numbers tell you what you want them to tell.\n<\/blockquote>\n<p>Un remerciement tout sp\u00e9cial \u00e0 Logan pour sa relecture et la correction des nombreuses fautes d\u2019orthographe \ud83d\ude09<\/p>","protected":false},"excerpt":{"rendered":"<p>Dans mon pr\u00e9c\u00e9dent article For vs Stream, j&rsquo;ai utilis\u00e9 JMH \u2013 The Java Microbenchmark Harness, un outil pour r\u00e9aliser des microbenchmarks de mani\u00e8re facile, et surtout, pertinente. Cet article \u00e0 pour but de vous pr\u00e9senter l&rsquo;outil et son utilisation. Mais tout d&rsquo;abord : c&rsquo;est quoi un microbenchmark ? Microbenchmark Benchmark ou banc d&rsquo;essai en fran\u00e7ais : un programme qui permet de mesurer les performances d&rsquo;un syst\u00e8me, pour le comparer \u00e0 d&rsquo;autres. Microbenchmark : un benchmark fait pour mesurer les performances&#8230;<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/introduction-a-jmh-java-microbenchmark-harness\/\">Lire la suite<span class=\"screen-reader-text\"> Lire la suite<\/span><\/a><\/p><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[9],"tags":[178,11,159],"class_list":["post-1041","post","type-post","status-publish","format-standard","hentry","category-informatique","tag-benchmark","tag-java","tag-performance"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1180,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/benchmark-conversion-de-long-en-byte\/","url_meta":{"origin":1041,"position":0},"title":"Benchmark : conversion de long en byte[]","author":"admin","date":"mardi  8 d\u00e9cembre 2020","format":false,"excerpt":"J'utilise beaucoup Kafka ces derniers temps, et dans Kafka, beaucoup de choses sont des tableaux de bytes, m\u00eame les headers ! Comme j'ai de nombreux composants qui s'\u00e9changent des messages, j'ai ajout\u00e9 des headers pour aider au suivi des messages, et entre autres un header timestamp qui a comme valeur\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1030,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/for-vs-stream\/","url_meta":{"origin":1041,"position":1},"title":"For vs Stream","author":"admin","date":"mardi 21 avril 2020","format":false,"excerpt":"Cela faisait longtemps que je n'avais pas \u00e9crit un article de blog et pourtant, pour cause de confinement, j'ai du temps pour le faire! N'ayant pas d'id\u00e9e, j'ai demand\u00e9 \u00e0 ma twitosph\u00e8re de m'en donner, et j'ai eu une r\u00e9ponse int\u00e9ressante : Les diff\u00e9rences de perf entre stream et for\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1920,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/benchmark-concatener-des-listes\/","url_meta":{"origin":1041,"position":2},"title":"Benchmark : concat\u00e9ner des listes","author":"admin","date":"mercredi 12 mars 2025","format":false,"excerpt":"Il y a peu, je suis tomb\u00e9 sur ce bout de code qui concat\u00e8ne deux listes en utilisant la classe utilitaire Lists de Guava puis l'API Stream : Lists.newArrayList(collectionOriginal,collectionValue) .stream() .flatMap(Collection::stream) .toList(); Comme ce bout de code est dans le chemin critique de l'application et donc appel\u00e9 tr\u00e8s fr\u00e9quemment, je\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":739,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/les-optimisations-de-performances-de-java-9\/","url_meta":{"origin":1041,"position":3},"title":"Les optimisations de performances de Java 9","author":"admin","date":"vendredi 26 janvier 2018","format":false,"excerpt":"Dans un pr\u00e9c\u00e9dent article sur Java 9, j'avais parcouru les principales nouveaut\u00e9s \u00e0 destination des d\u00e9veloppeurs : http:\/\/www.loicmathieu.fr\/wordpress\/informatique\/les-nouveautes-de-java-9-pour-les-developeurs. Je vais ici parcourir les principales nouveaut\u00e9s ax\u00e9es sur la performance Je vais encore reprendre les principales JEP : JEP 143: Improve Contended Locking Optimisation des monitors Java (optimisation des locks) en\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1946,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/java-25-quoi-de-neuf\/","url_meta":{"origin":1041,"position":4},"title":"Java 25 : Quoi de neuf?","author":"admin","date":"vendredi  4 juillet 2025","format":false,"excerpt":"Maintenant que Java 25 est features complete (Rampdown Phase One au jour d\u2019\u00e9criture de l\u2019article), c\u2019est le moment de faire le tour des fonctionnalit\u00e9s qu\u2019apporte cette nouvelle version, \u00e0 nous, les d\u00e9veloppeurs. Cet article fait partie d\u2019une suite d\u2019article sur les nouveaut\u00e9s des derni\u00e8res versions de Java, pour ceux qui\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1330,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/informatique\/devoxx-france-2021-ledition-9-3-4\/","url_meta":{"origin":1041,"position":5},"title":"Devoxx France 2021 &#8211; l&rsquo;\u00e9dition 9 3\/4","author":"admin","date":"vendredi  1 octobre 2021","format":false,"excerpt":"Cette semaine, c'est Devoxx France. Et pour la premi\u00e8re fois depuis pas mal de temps, je sors de chez moi, et j'y vais ! Je vous \u00e9cris ces mots dans le train de retour de la deuxi\u00e8me journ\u00e9e, pas de troisi\u00e8me pour moi cette ann\u00e9e. J'ai assist\u00e9 \u00e0 quelques talks,\u2026","rel":"","context":"Dans &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/posts\/1041","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/comments?post=1041"}],"version-history":[{"count":0,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/posts\/1041\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/media?parent=1041"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/categories?post=1041"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/wp-json\/wp\/v2\/tags?post=1041"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}