Benchmarking ORM, das beim Erstellen von Android-Anwendungen verwendet wird

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:


  1. Zunächst initiieren wir die Datenbank im Testkörper.
  2. 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.
  3. 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.
  4. 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.
  5. Wir messen die Leistung der Datenbankinitialisierung in einer separaten Funktion.
  6. 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 { //   (     ) benchmarkRule.scope.runWithTimingDisabled { Paper.book().destroy() if (Paper.book().allKeys.isNotEmpty()) throw RuntimeException() } //    repository.store(persons, { list -> Paper.book().write(KEY_CONTACTS, list) }) val persons = repository.read { Paper.book().read<List<Person>>(KEY_CONTACTS, emptyList()) } } 

Benchmarks für den Rest des ORM sehen ähnlich aus.


Ergebnisse


Initialisierung

Testnamegemein12345678910
HawkInitTest49_51249_28250_02149_11950_14549_97050_04746_64950_23049_86349_794
PaperdbInitTest224223223223233223223223223223223
RealmInitTest218217217217217217217217227217217
RoomInitTest61_695.563_45059_71458_52759_17563_54462_98063_25259_67063_86862_775

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

Testnamegemein12345678910
HawkInsertReadTest278_736_469.2278_098_654283_956_846276_748_308282_447_384272_609_500284_699_653271_869_770278_719_693278_836_115279_378_769
PaperdbInsertReadTest173_519_957.3172_953_347174_702_000169_740_846174_401_192173_930_037174_179_616173_937_460173_739_115176_215_038171_400_922
RealmInsertReadTest111_644_042.3108_501_578110_616_078102_056_461112_946_577111_701_231114_922_962106_198_000118_742_498120_888_230109_866_808
RoomInsertReadTest1_863_499_483.3187_250_36141_837_078_6141_872_482_5381_827_338_4601_869_147_9991_857_126_2291_842_427_5371_870_630_6521_878_862_5381_907_396_652

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.

Source: https://habr.com/ru/post/de477940/


All Articles