Microbenchmarking avec Java
1. introduction
Cet article rapide porte sur JMH (le harnais Java Microbenchmark). Cela a été ajouté au JDK en commençant par JDK 12; pour les versions antérieures, nous devons ajouter explicitement les dépendances à nos projets.
En termes simples, JMH s’occupe de choses telles que le réchauffement de la machine virtuelle Java et les chemins d’optimisation du code, ce qui rend le benchmarking aussi simple que possible.
2. Commencer
Pour commencer, nous pouvons continuer à travailler avec Java 8 et simplement définir les dépendances:
org.openjdk.jmh
jmh-core
1.19
org.openjdk.jmh
jmh-generator-annprocess
1.19
Les dernières versions desJMH Core etJMH Annotation Processor se trouvent dans Maven Central.
Ensuite, créez un benchmark simple en utilisant l'annotation@Benchmark (dans n'importe quelle classe publique):
@Benchmark
public void init() {
// Do nothing
}
Ensuite, nous ajoutons la classe principale qui lance le processus d'analyse comparative:
public class BenchmarkRunner {
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
Maintenant, exécuterBenchmarkRunner exécutera notre benchmark sans doute quelque peu inutile. Une fois l'exécution terminée, un tableau récapitulatif est présenté:
# Run complete. Total time: 00:06:45
Benchmark Mode Cnt Score Error Units
BenchMark.init thrpt 200 3099210741.962 ± 17510507.589 ops/s
3. Types de benchmarks
JMH prend en charge certains benchmarks possibles:Throughput,AverageTime,SampleTime etSingleShotTime. Ceux-ci peuvent être configurés via l'annotation@BenchmarkMode:
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void init() {
// Do nothing
}
La table résultante aura une mesure de temps moyenne (au lieu du débit):
# Run complete. Total time: 00:00:40
Benchmark Mode Cnt Score Error Units
BenchMark.init avgt 20 ≈ 10⁻⁹ s/op
4. Configuration du préchauffage et de l'exécution
En utilisant l'annotation@Fork, nous pouvons configurer le déroulement de l'exécution du benchmark: le paramètrevalue contrôle combien de fois le benchmark sera exécuté, et le paramètrewarmup contrôle combien de fois un benchmark fonctionnera à sec avant la collecte des résultats, par exemple:
@Benchmark
@Fork(value = 1, warmups = 2)
@BenchmarkMode(Mode.Throughput)
public void init() {
// Do nothing
}
JMH doit alors exécuter deux fourchettes d’échauffement et écarter les résultats avant de passer à une analyse comparative chronométrée.
De plus, l'annotation@Warmup peut être utilisée pour contrôler le nombre d'itérations de préchauffage. Par exemple,@Warmup(iterations = 5) indique à JMH que cinq itérations de préchauffage suffiront, par opposition à la valeur par défaut 20.
5. Etat
Voyons maintenant comment une tâche moins triviale et plus indicative d'analyse comparative d'un algorithme de hachage peut être effectuée en utilisantState. Supposons que nous décidions d'ajouter une protection supplémentaire contre les attaques par dictionnaire sur une base de données de mots de passe en hachant le mot de passe plusieurs centaines de fois.
Nous pouvons explorer l'impact sur les performances en utilisant un objetState:
@State(Scope.Benchmark)
public class ExecutionPlan {
@Param({ "100", "200", "300", "500", "1000" })
public int iterations;
public Hasher murmur3;
public String password = "4v3rys3kur3p455w0rd";
@Setup(Level.Invocation)
public void setUp() {
murmur3 = Hashing.murmur3_128().newHasher();
}
}
Notre méthode de référence ressemblera alors à:
@Fork(value = 1, warmups = 1)
@Benchmark
@BenchmarkMode(Mode.Throughput)
public void benchMurmur3_128(ExecutionPlan plan) {
for (int i = plan.iterations; i > 0; i--) {
plan.murmur3.putString(plan.password, Charset.defaultCharset());
}
plan.murmur3.hash();
}
Ici, le champiterations sera rempli avec les valeurs appropriées de l'annotation@Param par le JMH lorsqu'il est passé à la méthode de référence. La méthode annotée@Setup est appelée avant chaque appel du benchmark et crée un nouveauHasher assurant l'isolement.
Une fois l'exécution terminée, nous obtiendrons un résultat similaire à celui ci-dessous:
# Run complete. Total time: 00:06:47
Benchmark (iterations) Mode Cnt Score Error Units
BenchMark.benchMurmur3_128 100 thrpt 20 92463.622 ± 1672.227 ops/s
BenchMark.benchMurmur3_128 200 thrpt 20 39737.532 ± 5294.200 ops/s
BenchMark.benchMurmur3_128 300 thrpt 20 30381.144 ± 614.500 ops/s
BenchMark.benchMurmur3_128 500 thrpt 20 18315.211 ± 222.534 ops/s
BenchMark.benchMurmur3_128 1000 thrpt 20 8960.008 ± 658.524 ops/s
5. Conclusion
Ce didacticiel a porté sur et présenté le harnais de micro-analyse comparative de Java.
Comme toujours, des exemples de code peuvent être trouvéson GitHub.