Microbenchmarking avec Java

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.