Java - Guide de Démarrage pour les Microbenchmarks
Bonjour à tous, futurs sorciers Java ! ? Aujourd'hui, nous allons entreprendre un voyage passionnant dans le monde des microbenchmarks Java. Ne vous inquiétez pas si vous n'avez jamais écrit une ligne de code avant - nous commencerons desde le début et nous progresserons ensemble. Alors, prenez une tasse de café (ou de thé, si c'est ce qui vous arrange), et plongeons-y !
Qu'est-ce que le Microbenchmarking ?
Avant d'entrer dans les détails du microbenchmarking Java, comprenons d'abord ce que représente vraiment le microbenchmarking.
Imaginez-vous être un chef essayant de parfaire une recette. Vous ne goûteriez pas seulement le plat final pour voir s'il est bon, n'est-ce pas ? Vous goûteriez chaque ingrédient, testeriez不同的temperatures de cuisson et essayeriez diverses techniques. C'est exactement ce que représente le microbenchmarking en programmation - c'est une manière de mesurer la performance de petits morceaux isolés de votre code.
Pourquoi le Benchmarking Java est-il Important ?
Maintenant, vous vous demandez peut-être, "Pourquoi devrais-je m'intéresser au benchmarking ?" Eh bien, laissez-moi vous raconter une petite histoire.
Il y a quelques temps, quand je был junior développeur, j'ai écrit un programme qui fonctionnait parfaitement... sur mon ordinateur. Mais lorsqu'on l'a déployé sur les serveurs de l'entreprise, il était plus lent qu'une tortue portant un sac à dos lourd ! C'est là que j'ai appris l'importance du benchmarking. Il nous aide à :
- Identifier les goulots d'étranglement de performance
- Comparer différentes implémentations
- Assurer que notre code s'exécute efficacement sur différents systèmes
Techniques de Benchmarking Java
Examinons quelques techniques courantes de benchmarking Java :
1. Mesure Manuelle du Temps
La manière la plus simple de faire du benchmarking est la mesure manuelle du temps. Voici un exemple de base :
public class ExempleDeMesureSimple {
public static void main(String[] args) {
long startTime = System.nanoTime();
// Votre code ici
for (int i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
long endTime = System.nanoTime();
long duration = (endTime - startTime);
System.out.println("Temps d'exécution : " + duration + " nanosecondes");
}
}
Dans cet exemple, nous utilisons System.nanoTime()
pour mesurer le temps nécessaire pour calculer la racine carrée des nombres de 0 à 999 999.
2. Utilisation de JMH (Java Microbenchmark Harness)
Bien que la mesure manuelle du temps soit simple, elle n'est pas toujours précise. C'est là que JMH entre en jeu. JMH est un harnais Java pour construire, exécuter et analyser des benchmarks nano/micro/milli/macro.
Pour utiliser JMH, vous devez l'ajouter à votre projet. Si vous utilisez Maven, ajoutez ces dépendances à votre pom.xml
:
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.35</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.35</version>
</dependency>
</dependencies>
Maintenant, écrivons un simple benchmark JMH :
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 3)
public class ExempleJMH {
@Benchmark
public void benchmarkMathSqrt() {
Math.sqrt(143);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(ExempleJMH.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
Ce benchmark mesure le temps moyen nécessaire pour calculer la racine carrée de 143. Voici la signification des annotations :
-
@BenchmarkMode
: Spécifie ce qu'il faut mesurer (temps moyen dans ce cas) -
@OutputTimeUnit
: Spécifie l'unité des résultats -
@State
: Définit le domaine dans lequel les objets "état" seront partagés -
@Fork
: Combien de fois il faut fork un seul benchmark -
@Warmup
et@Measurement
: Définissent combien d'itérations de chauffe et de mesure doivent être effectuées
Algorithmes des Collections Java
Alors que nous sommes sur le sujet du benchmarking, prenons une petite digression pour parler des Algorithmes des Collections Java. Ce sont des outils incroyablement utiles qui peuvent significativement impacter la performance de votre programme.
Voici un tableau des algorithmes courants :
Algorithme | Description | Cas d'Utilisation |
---|---|---|
Collections.sort() | Trie une liste | Lorsque vous avez besoin d'ordonner des éléments |
Collections.binarySearch() | Recherche dans une liste triée | Trouver un élément dans une grande liste triée |
Collections.reverse() | Inverse une liste | Lorsque vous avez besoin d'inverser l'ordre des éléments |
Collections.shuffle() | Permute aléatoirement une liste | Randomiser l'ordre des éléments |
Collections.fill() | Remplace tous les éléments par un élément spécifié | Initialiser une liste avec une valeur spécifique |
Benchmarkons la performance du tri d'une liste en utilisant Collections.sort()
:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 3)
public class BenchmarkDeTri {
@Param({"100", "1000", "10000"})
private int tailleDeListe;
private List<Integer> liste;
@Setup
public void setup() {
liste = new ArrayList<>(tailleDeListe);
Random rand = new Random();
for (int i = 0; i < tailleDeListe; i++) {
liste.add(rand.nextInt());
}
}
@Benchmark
public void benchmarkCollectionsSort() {
Collections.sort(liste);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(BenchmarkDeTri.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
Ce benchmark mesure le temps nécessaire pour trier des listes de différentes tailles (100, 1000 et 10000 éléments). L'exécution de cela vous donnera une bonne idée de la manière dont le temps de tri augmente avec la taille de la liste.
Conclusion
Et voilà, mes amis ! Nous avons apenas effleuré la surface des microbenchmarks Java. Rappelez-vous, le benchmarking ne concerne pas seulement l'écriture de code rapide - c'est avant tout comprendre les caractéristiques de performance de votre code et prendre des décisions éclairées.
Pendant que vous continuerez votre voyage Java, gardez le benchmarking dans votre boîte à outils. C'est comme une boussole de confiance qui vous aidera à naviguer dans les eaux parfois tumultueuses de la performance des logiciels.
Bonne programmation, et que vos benchmarks soient toujours instructifs ! ?????
Credits: Image by storyset