[JAVA] Mikro-Benchmark mit JFR-API

Einführung

Jeder ist ein großartiger Flugschreiber, aber die JFR-API wurde offiziell von Java 9 hinzugefügt. Ich war neugierig, aber ich konnte es nicht versuchen, aber wäre es nicht schön, wenn ich diesmal verschiedene Informationen von JFR erhalten könnte, nicht nur die Ausführungszeit? Ich dachte, ich hätte es mit der JFR-API geschafft. Gist - koduki/TinyBenchmark.java

Wie man es benutzt ist so.

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));
}

Die "Benchmark-Methode" gibt "test.jfr" aus, der makeReport analysiert JFR, und nicht nur das diesmal erstellte benutzerdefinierte Ereignis, sondern auch der standardmäßig erfasste GC-Wert wird zusammengefasst und als LTSV ausgegeben. Wenn JFR es hat, können Sie einen beliebigen Wert annehmen. Wenn Sie also CPU- oder Datei-E / A ausführen möchten, sind Sie frei.

Ausführungsergebnis:

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

Natürlich können Sie es auch in Mission Control sehen. Diesmal habe ich es mit JMC7 geöffnet. Es scheint, dass es sich seit 6 ziemlich verändert hat und dass der Performance-Hotspot im Gegensatz zu 5.5, das an Java 8 angehängt ist, bis zu einem gewissen Grad automatisch analysiert wird. Was ist das erstaunlich.

001.png 002.png 003.png

Fahren wir nun mit der Implementierung fort.

Erstellen Sie ein benutzerdefiniertes Ereignis

Erstellen Sie zunächst ein benutzerdefiniertes Ereignis, um die Benchmark-Informationen zu speichern.

@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;
}

Grundsätzlich scheint es, dass es gehandhabt werden kann, indem man "jdk.jfr.Event" erbt und die erforderlichen Felder hinzufügt. Es gibt viele andere Anmerkungen als "Label", aber ich konnte sie noch nicht gedrückt halten. Artikel hier ist detailliert, aber der spezifische Inhalt kann nur in Javadoc und der Quelle gesehen werden.

Aufzeichnung von benutzerdefinierten Ereignissen

Als Nächstes erfahren Sie, wie Sie ein benutzerdefiniertes Ereignis aufzeichnen. Das ist auch ziemlich einfach. Klicken Sie hier für ein Beispiel, das tatsächlich verwendet wurde.

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();

Wie Sie sehen können, wird das "BenchmarkEvent" des zuvor erstellten benutzerdefinierten Ereignisses neu erstellt und "start" zum Startzeitpunkt des Ereignisses ausgeführt. Füllen Sie die Felder mit den erforderlichen Werten, um "Commit" zu erhalten. Es ist leicht zu verstehen. Es scheint, dass der Zeitraum zwischen Start und Festschreiben als Dauer aufgezeichnet wird. Daher führen wir dieses Mal die Verarbeitung des Benchmark-Ziels während dieser Zeit aus.

Führen Sie eine JFR-Aufnahme aus dem Programm durch

Die übliche Art, JFR zu verwenden, besteht darin, Argumente für Java-Agent- und JVM-Optionen anzugeben, oder? Ich dachte, das wäre das einzige, aber in letzter Zeit scheint es, dass es normal aus Java-Code aufgerufen werden kann. Nachdem Sie es ordnungsgemäß als Tool erstellt haben, ist es nicht einfach, geeignete Optionen anzugeben. Es ist jedoch sehr praktisch, dass Sie sich nicht die Mühe machen müssen, es über JMC anzuhängen, wenn Sie den in der Entwicklung befindlichen Code direkt von der IDE aus bewerten möchten.

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);
}

Wie Sie sehen können, liegt es in Form von "jdk.jfr.Recording", "start / stop" und "dump" vor. Ein zu beachtender Punkt ist, dass, wenn Sie "Konfiguration" nicht erhalten und angeben, nur das durch "Aktivieren" angegebene Ereignis registriert wird. Sie müssen es daher ordnungsgemäß angeben, wenn Sie CPU-Ressourcen oder Speicherstatus wünschen.

Laden von JFR

Auf diese Funktion habe ich lange gewartet! Nun, nur weil ich nicht aufholen konnte, zumindest von Java 9, vielleicht sogar 8, wenn auch inoffiziell. Auf jeden Fall ist es eine Methode, um die lang erwartete JFR mit PG zu analysieren. Ich bin wirklich glücklich, weil ich JFR in CSV konvertieren wollte.

Verwenden Sie zum Lesen jdk.jfr.consumer.RecordingFile.

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

Es ist eine Form, alle in JFR enthaltenen Ereignisse mit "readAllEvents" abzurufen und bei Bedarf mit der Stream-API zu filtern.

RecordedEvent enthält mehrere Werte. Sind die folgenden Elemente, die Sie möglicherweise häufig verwenden?

Da es sich um den Basisnamen handelt, wird auf eine ausführliche Erläuterung verzichtet. Sie können den Wert jedoch abrufen, indem Sie den Namen des Felds in "getValue" eingeben. Bitte beachten Sie, dass es sich nicht um einen Labelnamen handelt.

Zusammenfassung

Ich habe mit der JFR-API einen Mikro-Benchmark erstellt. Ist es nicht praktisch, weil Sie nicht nur die Ausführungszeit, sondern auch den Ressourcenstatus sehen können? Ich denke.

Außerdem habe ich die JFR-API zum ersten Mal berührt, und sie fühlt sich ziemlich gut an. Es ist einfach zu bedienen.

Ganz zu schweigen von benutzerdefinierten Ereignissen. Wenn Sie die Analyse-API jedoch gut verwenden, werden sich Ihre Träume verbreiten, z. B. das Erstellen verteilter Ablaufverfolgungstools auf der Grundlage von JFR-Werten, das Verwandeln in Web-Apps und das Analysieren mit Kibana. Ab Java 11 kann JMC JFR normalerweise mit OSS und OpenJDK verwenden, daher hoffe ich, dass es sich in Zukunft weiterentwickeln wird.

Ich kann jedoch keine andere offizielle Dokumentation als Javadoc finden. Ist es ein Code-lesbarer Kleber? Wenn jemand weiß, lassen Sie es mich bitte wissen> <

Dann viel Spaß beim Hacken!

Referenz

Recommended Posts

Mikro-Benchmark mit JFR-API
Kompatibel mit Android 10 (API 29)
Erstellen Sie eine XML-RPC-API mit Wicket
Testen Sie die Web-API mit junit
Verwenden Sie die Bulk-API mit RestHighLevelClient
REST-API-Test mit REST Assured
API mit Spring + Vue.js verknüpfen
Präsentationsfolie mit Verarbeitung erstellt
Ich habe ein einfaches Suchformular mit Spring Boot + GitHub Search API erstellt.