创建Android应用程序时使用的基准ORM

哈Ha! 我叫Artyom Dobrovinsky,我是FINCH的一名Android开发人员。


有一次,我将自己包裹在一支雪茄中,研究了一个Android的ORM的源代码。 看到那里有一个称为benchmarks的程序包立即出现在那儿,感到惊讶的是所有评估都是使用Log.d(System.nanoTime()) 。 这不是我第一次看到这个。 老实说,我什至看到使用System.currentTimeMillis()基准测试。 对需要更改某些东西的意识崩溃,迫使我搁下一杯威士忌,坐在键盘旁。


为什么写这篇文章


了解如何在Android中衡量代码性能的情况令人遗憾。
有多少人没有谈论过探查器,在2019年,仍然有人相信JVM可以按照开发人员编写的所有程序并按照编写代码的确切顺序完成所有工作。 实际上,没有什么比真相更远了。


实际上,不幸的虚拟机与十亿个编写自己的代码的粗心按钮阅读器抗争,而不必担心处理器如何处理所有这些操作。 这场战斗已经进行了数年,她的袖子进行了数百万次棘手的优化,这些优化(如果被忽略)将把对程序性能的任何衡量都转化为浪费时间。


也就是说,开发人员有时不认为必须测量代码的性能,甚至更多时候不知道如何做。 困难在于,要进行绩效评估,必须为所有情况创造最相似,最理想的条件,这是获取有用信息的唯一途径。 这些条件是由没有写在膝盖上的解决方案造成的。


如果您需要关于是否使用第三方框架来衡量性能的争论,可以随时阅读Alexei Shipilev并惊叹于问题的严重性 。 一切均通过引用在本文中:为什么进行基准测试之前需要进行预热,为什么在计算经过的时间时根本无法信任System.currentTimeMillis() ,并且开玩笑说300次即可。


我为什么要谈论这个?

事实是,我是一个全面开发的开发人员:我不仅拥有自己的宠物项目一样拥有Android SDK,而且还为后端编写了一个月的代码。


当我将我的第一个微服务带到评论中,并且README中没有基准测试时,他误会了我。 我记得这一点,再也不会重复此错误。 因为他在一个星期内离开了。


走吧


我们在测量什么


作为Android基准数据库测试的一部分,我决定为Paper,Hawk,Realm和Room等ORM测量初始化速度和读写速度。
是的,我在一个测试NoSQL和一个关系数据库中进行了测量-下一个问题是什么?


比我们测量


看来,如果我们谈论的是JVM,那么选择是显而易见的-有一个荣耀完善完美记录的 JMH 。 但是不,它不会启动Android的检测测试。


Google Calipher遵循它们-结果相同。


Calipher有一个名为Spanner的分支-多年来一直处于崩溃状态,并鼓励使用Androidx Benchmark


让我们专注于后者。 如果仅仅是因为我们别无选择。


就像从支持库迁移时添加到Jetpack而不是重新考虑的所有内容一样,Androidx Benchmark的外观和行为就像是在一个半星期内作为测试任务编写的,其他任何人都不会碰它。 另外,这个库有点过去了-因为,它更多用于评估UI测试。 但是如果想要最好的,您可以和她一起工作。 这将至少使我们免于明显的错误 ,也有助于热身。


为了减少结果的可笑性,我将运行所有测试10次并计算平均值。


测试装置-小米A1。 不是市场上最弱的“清洁” Android。


将库连接到项目


有关将Andoridx Benchmark连接到项目的出色说明 。 我强烈建议您不要懒惰,并连接单独的模块进行测量。


实验进度


我们所有的基准测试将按以下顺序执行:


  1. 首先,我们在测试主体中启动数据库。
  2. 然后,在benchmarkRule.scope.runWithTimingDisabled块中,我们生成供数据库使用的数据。 评估中不会考虑放置在该电路中的代码。
  3. 在同一结束语中,我们添加了清除数据库的逻辑; 在写入之前,请确保数据库为空。
  4. 以下是读写的逻辑。 确保使用读取结果初始化变量,以便JVM不会因为未使用而从执行计数中删除此逻辑。
  5. 我们在一个单独的函数中测量数据库初始化的性能。
  6. 我们觉得自己像个科学人。

该代码可以在这里找到。 如果您不愿意走路,PaperDb的计量功能如下所示:


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

其余ORM的基准看起来相似。


结果


初始化

测试名称平均1个2345678910
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

赢家是Realm,第二名是Paper。 无论Room在做什么,您仍然可以想象Hawk花费的时间几乎相同-这是完全无法理解的。


写作和阅读

测试名称平均1个2345678910
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

这里再次成为Realm的赢家,但是在这些结果中却充满了失​​败。


两个“最慢”数据库之间的四倍差异以及“最快”和“最慢”数据库之间的十六倍差异非常可疑。 即使考虑到差异稳定的事实。


结论


衡量代码性能至少是出于好奇。 即使我们谈论的是业界发布的最多案例(例如,针对Android的工具测试的评估)。


有充分的理由为此事务使用第三方框架(而不是用时间和啦啦队写自己的框架)。


代码库中的情况使得每个人都试图用一种干净的体系结构编写代码,对于大多数带有业务逻辑的模块来说,它是一个Java模块-将模块与附近的JMH连接并检查代码中的瓶颈-它可以工作一天。 好处-未来很多年。


编码愉快!


PS:如果专心的读者了解进行Android工具测试基准测试的框架(本文未列出),请在评论中分享。


PPS:测试存储库已打开以接受拉取请求。

Source: https://habr.com/ru/post/zh-CN477940/


All Articles