Benchmarking ORM utilisé lors de la création d'applications Android

Bonjour, Habr! Je m'appelle Artyom Dobrovinsky et je suis développeur Android chez FINCH .


Une fois, m'enveloppant dans la fumée d'un cigare du matin, j'ai étudié le code source d'un ORM pour Android. En y voyant un paquet appelé benchmarks immédiatement regardé, et j'ai été surpris que toutes les évaluations aient été effectuées en utilisant Log.d(System.nanoTime()) . Ce n'est pas la première fois que je vois ça. Pour être honnête, j'ai même vu des benchmarks réalisés à l'aide de System.currentTimeMillis() . La conscience effondrée que quelque chose doit être changé m'a forcé à mettre de côté un verre de whisky et à m'asseoir au clavier.


Pourquoi cet article est-il écrit


La situation de comprendre comment mesurer les performances du code dans Android est triste.
Combien ne parlent pas de profileurs, et en 2019, quelqu'un reste convaincu que la JVM fait tout ce que le développeur a écrit et dans l'ordre exact dans lequel le code est écrit. En réalité, il n'y a rien de plus éloigné de la vérité.


En fait, la malheureuse machine virtuelle repousse un milliard de lecteurs de boutons imprudents qui écrivent leur propre code, sans jamais forcer la façon dont le processeur fonctionnera avec tout cela. Cette bataille dure depuis plusieurs années, et elle a un million d'optimisations délicates dans sa manche qui (si elles sont ignorées) transformeront toute mesure de la performance du programme en une perte de temps.


C'est-à-dire que les développeurs ne considèrent parfois pas nécessaire de mesurer les performances du code, et encore plus souvent ne savent pas comment. La difficulté réside dans le fait que pour effectuer une évaluation des performances, il est nécessaire de créer les conditions les plus similaires et les plus idéales pour tous les cas - uniquement de cette façon, vous pouvez obtenir des informations utiles. Ces conditions sont créées par des solutions non écrites sur le genou.


Si vous avez besoin d'arguments sur l'opportunité d'utiliser des frameworks tiers pour mesurer les performances, vous pouvez toujours lire Alexei Shipilev et vous émerveiller de la profondeur du problème. Tout est dans l'article par référence: pourquoi l'échauffement est nécessaire avant d'effectuer le test, pourquoi System.currentTimeMillis() ne peut pas faire confiance du tout lors du comptage du temps écoulé et des blagues pour 300. Excellente lecture.


Pourquoi puis-je en parler?

Le fait est que je suis un développeur complètement développé: je possède non seulement le SDK Android comme s'il s'agissait de mon projet pour animaux de compagnie, mais pendant un autre mois, j'ai écrit du code pour le backend.


Lorsque j'ai apporté mon premier microservice à l'examen, et qu'il n'y avait pas d'analyse comparative dans README , il m'a regardé avec un malentendu. Je m'en suis souvenu et je n'ai plus jamais répété cette erreur. Parce qu'il est parti dans une semaine.


Allons-y.


Que mesurons-nous


Dans le cadre de l'analyse comparative des bases de données pour Android, j'ai décidé de mesurer la vitesse d'initialisation et la vitesse d'écriture / lecture pour des ORM tels que Paper, Hawk, Realm et Room.
Oui, je mesure en un seul test NoSQL et une base de données relationnelle - quelle est la question suivante?


Que nous mesurons


Il semblerait que si nous parlons de la JVM, alors le choix est évident - il existe un JMH glorifié , perfectionné et parfaitement documenté . Mais non, il ne démarre pas les tests d'instrumentation pour Android.


Google Calipher les suit - avec le même résultat.


Il existe une fourchette de Calipher appelée Spanner - qui depuis de nombreuses années est zeppercay et encourage l'utilisation d' Androidx Benchmark .


Concentrons-nous sur ce dernier. Ne serait-ce que parce que nous n'avions pas le choix.


Comme tout ce qui a été ajouté à Jetpack et non repensé lors de la migration à partir de la bibliothèque de support, Androidx Benchmark ressemble et se comporte comme s'il avait été écrit en une semaine et demie comme une tâche de test, et personne d'autre ne le touchera jamais. De plus, cette bibliothèque est un peu dépassée, car elle sert plutôt à évaluer les tests d'interface utilisateur. Mais à défaut du meilleur, vous pouvez travailler avec elle. Cela nous évitera au moins des erreurs évidentes et aidera également à l'échauffement.


Pour réduire le ridicule des résultats, je vais exécuter tous les tests 10 fois et calculer la moyenne.


Appareil de test - Xiaomi A1. Pas le plus faible du marché, Android "propre".


Connecter une bibliothèque à un projet


Il existe d' excellentes instructions pour connecter Andoridx Benchmark à un projet. Je vous conseille fortement de ne pas être paresseux et de connecter un module séparé pour faire des mesures.


Progression de l'expérience


Tous nos benchmarks seront exécutés dans l'ordre suivant:


  1. Tout d'abord, nous lançons la base de données dans le corps de test.
  2. Ensuite, dans le bloc benchmarkRule.scope.runWithTimingDisabled , nous générons des données que nous alimentons la base de données. Le code placé dans ce circuit ne sera pas pris en compte dans l'évaluation.
  3. Dans la même fermeture, nous ajoutons la logique d'effacement de la base de données; assurez-vous que la base de données est vide avant d'écrire.
  4. Ce qui suit est la logique de l'écriture et de la lecture. Assurez-vous d'initialiser la variable avec le résultat de la lecture afin que la JVM ne supprime pas cette logique du compte d'exécution comme inutilisée.
  5. Nous mesurons les performances de l'initialisation de la base de données dans une fonction distincte.
  6. Nous nous sentons comme un homme de science.

Le code peut être trouvé ici . Si vous êtes paresseux à marcher, la fonction de mesure de PaperDb ressemble à ceci:


 @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()) } } 

Les repères pour le reste de l'ORM se ressemblent.


Résultats


Initialisation

nom du testméchant12345678910
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

Le gagnant est Realm, en deuxième place est Paper. Ce que fait Room, vous pouvez toujours imaginer que Hawk fait presque le même temps - c'est complètement incompréhensible.


Écriture et lecture

nom du testméchant12345678910
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

Là encore le vainqueur de Realm, mais dans ces résultats, ça sent l'échec.


La différence de quatre fois entre les deux bases de données «les plus lentes» et seize fois entre la «plus rapide» et la «plus lente» est très suspecte. Même en tenant compte du fait que la différence est stable.


Conclusion


Mesurer les performances de votre code est au moins par curiosité. Même si nous parlons des cas les plus lancés par l'industrie (comme l'évaluation des tests instrumentaux pour Android).


Il y a toutes les raisons d'utiliser des frameworks tiers pour cette entreprise (plutôt que d'écrire le vôtre avec un timing et des pom-pom girls).


La situation dans les bases de code est telle que tout le monde essaie d'écrire dans une architecture propre, pour la majorité le module avec la logique métier est un module java - pour connecter un module avec JMH à proximité et vérifier le code pour les goulots d'étranglement - cela fonctionne pendant une journée. Et les avantages - pour de nombreuses années à venir.


Bon codage!


PS: Si un lecteur attentif connaît le cadre pour la conduite de tests de performance instrumentaux pour Android, non répertorié dans l'article - veuillez partager dans les commentaires.


PPS: le référentiel de test est ouvert pour les demandes d'extraction.

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


All Articles