Hallo habr Mein Name ist Artyom Dobrovinsky und ich bin Android-Entwickler bei FINCH .
Einmal habe ich mich in den Rauch einer Morgenzigarre gewickelt und den Quellcode eines ORM für Android studiert. Als ich dort ein Paket mit dem Namen benchmarks
sah, war Log.d(System.nanoTime())
sofort überrascht, dass alle Auswertungen mit Log.d(System.nanoTime())
. Dies ist nicht das erste Mal, dass ich das gesehen habe. Um ehrlich zu sein, habe ich sogar Benchmarks gesehen, die mit System.currentTimeMillis()
. Das zusammengebrochene Bewusstsein, dass etwas geändert werden muss, zwang mich, ein Glas Whisky beiseite zu stellen und mich an die Tastatur zu setzen.
Warum ist dieser Artikel geschrieben
Die Situation mit dem Verständnis, wie man die Code-Leistung in Android misst, ist traurig.
Sprechen Sie nicht über Profiler, aber im Jahr 2019 bleibt jemand zuversichtlich, dass die JVM alles tut, was der Entwickler geschrieben hat, und zwar in der genauen Reihenfolge, in der der Code geschrieben wurde. In Wirklichkeit ist nichts weiter von der Wahrheit entfernt.
Tatsächlich bekämpft die unglückliche virtuelle Maschine eine Milliarde sorgloser Tastenleser, die ihren eigenen Code schreiben, ohne sich ein einziges Mal darum zu bemühen, wie der Prozessor damit umgehen wird. Dieser Kampf dauert mehrere Jahre an und sie hat eine Million kniffliger Optimierungen im Ärmel, die (wenn sie ignoriert wird) jede Messung der Programmleistung in Zeitverschwendung verwandeln.
Das heißt, Entwickler halten es manchmal nicht für erforderlich, die Leistung des Codes zu messen, und noch häufiger wissen sie nicht, wie. Die Schwierigkeit liegt darin, dass für die Durchführung einer Leistungsbeurteilung für alle Fälle möglichst ähnliche und ideale Bedingungen geschaffen werden müssen - nur so erhalten Sie nützliche Informationen. Diese Bedingungen werden durch Lösungen geschaffen, die nicht auf das Knie geschrieben sind.
Wenn Sie Argumente darüber benötigen, ob Sie Frameworks von Drittanbietern zur Messung der Leistung verwenden möchten , können Sie jederzeit Alexei Shipilev lesen und sich über die Tiefe des Problems wundern. Alles ist in dem Artikel als Referenz enthalten: Warum muss vor dem Durchführen des Benchmarks System.currentTimeMillis()
Warum kann System.currentTimeMillis()
beim Zählen der verstrichenen Zeit überhaupt nicht vertraut werden?
Warum kann ich darüber reden?
Tatsache ist, dass ich ein umfassend entwickelter Entwickler bin: Ich besitze nicht nur das Android SDK, als wäre es mein Lieblingsprojekt, sondern ich habe einen weiteren Monat lang Code für das Backend geschrieben.
Als ich meinen ersten Microservice zum Review brachte und es kein Benchmarking in README
, sah er mich mit einem Missverständnis an. Ich erinnerte mich daran und wiederholte diesen Fehler nie wieder. Weil er in einer Woche gegangen ist.
Lass uns gehen.
Was messen wir?
Im Rahmen des Benchmarking von Datenbanken für Android habe ich beschlossen, die Initialisierungsgeschwindigkeit und die Schreib- / Lesegeschwindigkeit für ORMs wie Paper, Hawk, Realm und Room zu messen.
Ja, ich messe in einem Test NoSQL und eine relationale Datenbank - was ist die nächste Frage?
Dann messen wir
Es scheint, dass, wenn wir über die JVM sprechen, die Wahl offensichtlich ist - es gibt eine verherrlichte , perfektionierte und fehlerfrei dokumentierte JMH . Aber nein, es werden keine Instrumentierungstests für Android gestartet.
Google Calipher folgt ihnen - mit dem gleichen Ergebnis.
Es gibt eine Gabel von Calipher namens Spanner - die seit vielen Jahren Zeppercay ist und die Verwendung von Androidx Benchmark fördert .
Konzentrieren wir uns auf Letzteres. Wenn nur, weil wir keine Wahl hatten.
Wie alles, was zu Jetpack hinzugefügt und bei der Migration aus der Support-Bibliothek nicht überdacht wurde, sieht Androidx Benchmark so aus und verhält sich so, als wäre es in anderthalb Wochen als Testaufgabe geschrieben worden, und niemand anderes wird es jemals anfassen. Außerdem ist diese Bibliothek etwas vergangen - weil sie eher zur Auswertung von UI-Tests dient. Aber aus Mangel an dem Besten können Sie mit ihr arbeiten. Dies erspart uns zumindest offensichtliche Fehler und hilft auch beim Aufwärmen.
Um die Lächerlichkeit der Ergebnisse zu verringern, führe ich alle Tests zehnmal durch und berechne den Durchschnitt.
Testgerät - Xiaomi A1. Nicht die schwächste auf dem Markt, "sauber" Android.
Verbinden einer Bibliothek mit einem Projekt
Es gibt ausgezeichnete Anweisungen zum Verbinden von Andoridx Benchmark mit einem Projekt. Ich rate Ihnen dringend, nicht faul zu sein und ein separates Modul für Messungen anzuschließen.
Versuchsfortschritt
Alle unsere Benchmarks werden in der folgenden Reihenfolge ausgeführt:
- Zunächst initiieren wir die Datenbank im Testkörper.
- Anschließend generieren wir im Block
benchmarkRule.scope.runWithTimingDisabled
Daten, die wir der Datenbank zuführen. Der in dieser Schaltung platzierte Code wird bei der Auswertung nicht berücksichtigt. - In demselben Abschluß fügen wir die Logik des Löschens der Datenbank hinzu. Stellen Sie sicher, dass die Datenbank leer ist, bevor Sie schreiben.
- Es folgt die Logik des Schreibens und Lesens. Stellen Sie sicher, dass die Variable mit dem Ergebnis des Lesens initialisiert wird, damit die JVM diese Logik nicht als nicht verwendet aus der Ausführungszählung entfernt.
- Wir messen die Leistung der Datenbankinitialisierung in einer separaten Funktion.
- Wir fühlen uns wie ein Mann der Wissenschaft.
Den Code finden Sie hier . Wenn Sie faul sind zu gehen, sieht die Messfunktion für PaperDb folgendermaßen aus:
@Test fun paperdbInsertReadTest() = benchmarkRule.measureRepeated {
Benchmarks für den Rest des ORM sehen ähnlich aus.
Ergebnisse
Initialisierung
Der Gewinner ist Realm, auf dem zweiten Platz steht Paper. Was Room macht, können Sie sich immer noch vorstellen, dass Hawk fast genauso viel Zeit hat - es ist völlig unverständlich.
Schreiben und lesen
Hier wieder der Sieger von Realm, aber in diesen Ergebnissen scheitert es.
Der vierfache Unterschied zwischen den beiden "langsamsten" Datenbanken und der sechzehnfache zwischen dem "schnellsten" und dem "langsamsten" ist sehr verdächtig. Auch unter Berücksichtigung der Tatsache, dass der Unterschied stabil ist.
Fazit
Das Messen der Leistung Ihres Codes ist zumindest aus Neugierde. Auch wenn es sich um die branchenweit am häufigsten vorgestellten Fälle handelt (z. B. die Auswertung von Instrumententests für Android).
Es gibt jeden Grund, für dieses Geschäft Frameworks von Drittanbietern zu verwenden (anstatt Ihre eigenen mit Timing und Cheerleadern zu schreiben).
Die Situation in Codebasen ist so, dass jeder versucht, in einer sauberen Architektur zu schreiben. Für die Mehrheit ist das Modul mit Geschäftslogik ein Java-Modul. Um ein Modul mit JMH in der Nähe zu verbinden und den Code auf Engpässe zu überprüfen, funktioniert es für einen Tag. Und die Vorteile - für viele Jahre.
Viel Spaß beim Codieren!
PS: Wenn ein aufmerksamer Leser das Framework für die Durchführung von Benchmarks für Instrumentaltests für Android kennt, die nicht im Artikel aufgeführt sind, teilen Sie dies bitte in den Kommentaren mit.
PPS: Das Test- Repository ist offen für Pull-Anfragen.