[JAVA] Micro benchmark réalisé avec l'API JFR

introduction

Tout le monde est un excellent enregistreur de vol, mais l'API JFR a été officiellement ajoutée à partir de Java 9. J'étais curieux, mais je ne pouvais pas l'essayer, mais ne serait-ce pas bien si je pouvais obtenir diverses informations de JFR cette fois, pas seulement l'heure d'exécution? Je pensais l'avoir fait en utilisant l'API JFR. Gist - koduki/TinyBenchmark.java

Comment l'utiliser, c'est comme ça.

public static void main(String[] args) throws IOException, ParseException {
    Path path = Paths.get("test.jfr");

    benchmarks(path, "ConcatString Benchmark", 3, (loop) -> {
        System.err.println("benchmarking...");
        benchmark(loop, "Concat all string by 'plus'", () -> ConcatString.fixedStringWithPlus(1_000), 1_000);
        benchmark(loop, "Concat all string by 'plus'", () -> ConcatString.fixedStringWithPlus(100_000), 10_000);
    });
    printReport(makeReport(path));
}

La méthode des benchmarks produit test.jfr, le makeReport analyse JFR, et non seulement l'événement personnalisé créé cette fois-ci, mais aussi la valeur GC acquise en standard est résumée et sortie en LTSV. Si JFR l'a, vous pouvez prendre n'importe quelle valeur, donc si vous voulez faire des E / S CPU ou fichier, vous êtes libre.

Résultat de l'exécution:

testcase:Concat all string by 'plus'	loop:0	arguments:[1000]	response(ms):113	gc_count:0	gc_total(ms):0	gc_max(ms):0	gc_avg(ms):0.000000
testcase:Concat all string by 'plus'	loop:0	arguments:[10000]	response(ms):20784	gc_count:237	gc_total(ms):250	gc_max(ms):12	gc_avg(ms):1.054852
testcase:Concat all string by 'plus'	loop:1	arguments:[1000]	response(ms):2	gc_count:0	gc_total(ms):0	gc_max(ms):0	gc_avg(ms):0.000000
testcase:Concat all string by 'plus'	loop:1	arguments:[10000]	response(ms):13782	gc_count:224	gc_total(ms):67	gc_max(ms):10	gc_avg(ms):0.299107
testcase:Concat all string by 'plus'	loop:2	arguments:[1000]	response(ms):0	gc_count:0	gc_total(ms):0	gc_max(ms):0	gc_avg(ms):0.000000
testcase:Concat all string by 'plus'	loop:2	arguments:[10000]	response(ms):12776	gc_count:225	gc_total(ms):34	gc_max(ms):2	gc_avg(ms):0.151111

Bien sûr, vous pouvez également le voir dans Mission Control. Cette fois, je l'ai ouvert avec JMC7. Il semble que cela ait beaucoup changé depuis 6 et il semble que le hotspot de performances soit analysé automatiquement dans une certaine mesure contrairement à 5.5 attaché à Java 8. Qu'est-ce que c'est incroyable.

001.png 002.png 003.png

Maintenant, continuons avec l'implémentation.

Créer un événement personnalisé

Tout d'abord, créez un événement personnalisé pour stocker les informations de référence.

@Label("Benchmark")
static class BenchmarkEvent extends Event {
    @Label("Test Case")
    String testcase;
    @Label("Loop Count")
    int loop;
    @Label("Response")
    long response;
    @Label("Arguments")
    String arguments;
}

Fondamentalement, il semble que cela puisse être géré en héritant de jdk.jfr.Event et en ajoutant les champs nécessaires. Il existe de nombreuses annotations autres que «Label», mais je n'ai pas encore pu les maintenir enfoncées. L'article ici est détaillé, mais le contenu spécifique ne peut être vu que dans javadoc et la source.

Enregistrement sur des événements personnalisés

Ensuite, comment enregistrer dans un événement personnalisé. C'est aussi assez simple. Cliquez ici pour un exemple qui a été réellement utilisé.

BenchmarkEvent event = new BenchmarkEvent();

event.begin();
long s = System.nanoTime();

callback.run();

long e = System.nanoTime();

event.loop = loopCount;
event.testcase = testcase;
event.response = (e - s);
List<String> args = Arrays.stream(arguments).map(x -> x.toString()).collect(Collectors.toList());
event.arguments = "[" + String.join(",", args) + "]";
event.commit();

Comme vous pouvez le voir, le nouveau BenchmarkEvent de l'événement personnalisé créé précédemment et s'exécute commence au moment du début de l'événement. Remplissez les champs avec les valeurs requises pour obtenir commit. C'est facile à comprendre. Il semble que la période entre le début et la validation soit enregistrée en tant que durée, donc cette fois, nous exécutons le traitement à évaluer pendant cette période.

Effectuer un enregistrement JFR à partir du programme

La manière habituelle d'utiliser JFR est de spécifier des arguments pour les options java-agent et JVM, non? Je pensais que c'était la seule chose, mais récemment, il semble qu'il puisse être appelé normalement à partir du code Java. Après l'avoir créé correctement en tant qu'outil, il n'est pas facile de spécifier les options appropriées, mais il est très pratique que vous n'ayez pas à vous soucier de l'attacher depuis JMC lorsque vous souhaitez comparer le code en cours de développement directement à partir de l'EDI.

Configuration jfrConfig = Configuration.getConfiguration("default");
try (Recording recording = new Recording(jfrConfig)) {
    recording.setName(name);
    recording.start();
    recording.enable(BenchmarkEvent.class);

    for (int i = 0; i < loopCount; i++) {
        callback.accept(i);
    }

    recording.stop();
    recording.dump(path);
}

Comme vous pouvez le voir, ouvrez jdk.jfr.Recording et il se présente sous la forme start / stop et dump. Un point à noter est que si vous n'obtenez pas Configuration et ne le spécifiez pas, seul l'événement spécifié par ʻenable` sera enregistré, vous devez donc le spécifier correctement si vous voulez des ressources CPU ou l'état de la mémoire.

Chargement de JFR

Cette fonctionnalité que j'attendais depuis longtemps! Eh bien, juste parce que je ne pouvais pas rattraper le retard, au moins de Java 9, peut-être même 8 si non officiel. Quoi qu'il en soit, c'est une méthode pour analyser le JFR tant attendu avec PG. Je suis vraiment content parce que je voulais convertir JFR en CSV.

Utilisez jdk.jfr.consumer.RecordingFile pour lire.

List<RecordedEvent> gcEvents = RecordingFile.readAllEvents(path).stream()
        .filter((e) -> e.getEventType().getName().equals("jdk.G1GarbageCollection"))
        .collect(Collectors.toList());

C'est une forme d'obtenir tous les événements inclus dans JFR avec readAllEvents et de filtrer avec Stream API si nécessaire.

RecordedEvent contient plusieurs valeurs. Les éléments suivants peuvent-ils être utilisés souvent?

Comme il est identique au nom de base, l'explication détaillée est omise, mais vous pouvez obtenir la valeur en entrant le nom du champ dans getValue. Veuillez noter qu'il ne s'agit pas d'un nom d'étiquette.

Résumé

J'ai fait un micro-benchmark en utilisant l'API JFR. N'est-ce pas pratique car vous pouvez voir non seulement le temps d'exécution, mais également l'état de la ressource? Je pense.

De plus, c'est la première fois que je touche l'API JFR, et ça fait du bien. C'est facile à utiliser.

Sans parler des événements personnalisés, mais si vous utilisez bien l'API d'analyse, vous pouvez créer des outils de traçage distribués basés sur des valeurs JFR, en faire des applications Web et les analyser avec Kibana. À partir de Java 11, JMC peut utiliser JFR normalement avec OSS et OpenJDK, j'espère donc qu'il se développera davantage à l'avenir.

Cependant, je ne trouve aucune documentation officielle autre que Javadoc. Si quelqu'un sait, faites-le moi savoir> <

Alors bon piratage!

référence

Recommended Posts

Micro benchmark réalisé avec l'API JFR
Compatible avec Android 10 (API 29)
Créer une API XML-RPC avec Wicket
Tester l'API Web avec junit
Utiliser l'API Bulk avec RestHighLevelClient
Test de l'API REST avec REST Assured
Lier l'API avec Spring + Vue.js
Diapositive de présentation réalisée avec Processing
J'ai créé un formulaire de recherche simple avec Spring Boot + GitHub Search API.