ORM de benchmarking usado ao criar aplicativos Android

Olá Habr! Meu nome é Artyom Dobrovinsky e sou desenvolvedor Android da FINCH .


Certa vez, me envolvendo na fumaça de um charuto da manhã, estudei o código fonte de um ORM para Android. Vendo lá um pacote chamado benchmarks imediatamente olhou para lá e ficou surpreso que todas as avaliações foram feitas usando Log.d(System.nanoTime()) . Não é a primeira vez que vejo isso. Para ser sincero, vi até benchmarks feitos com System.currentTimeMillis() . A consciência de que algo precisa ser mudado me obrigou a deixar de lado um copo de uísque e me sentar ao teclado.


Por que este artigo foi escrito


A situação com a compreensão de como medir o desempenho do código no Android é triste.
Não fale sobre profilers, mas em 2019 alguém permanece confiante de que a JVM faz tudo o que o desenvolvedor escreveu e na ordem exata em que o código foi escrito. Na realidade, não há nada mais longe da verdade.


De fato, a infeliz máquina virtual luta contra um bilhão de leitores de botões descuidados que escrevem seu próprio código, sem ter que se preocupar com como o processador funcionará com tudo isso. Essa batalha já dura vários anos e ela tem um milhão de otimizações complicadas na manga que (se ignoradas) transformarão qualquer medição do desempenho do programa em uma perda de tempo.


Ou seja, os desenvolvedores às vezes não consideram necessário medir o desempenho do código e, ainda mais frequentemente, não sabem como. A dificuldade reside no fato de que, para realizar uma avaliação de desempenho, é necessário criar as condições mais semelhantes e ideais para todos os casos - essa é a única maneira de obter informações úteis. Essas condições são criadas por soluções não escritas no joelho.


Se você precisar de argumentos para usar estruturas de terceiros para medir o desempenho, sempre poderá ler Alexei Shipilev e se maravilhar com a profundidade do problema. Tudo está no artigo por referência: por que o aquecimento é necessário antes de realizar o benchmark, por que System.currentTimeMillis() não pode ser totalmente confiável ao contar o tempo decorrido e faz piadas para 300. Excelente leitura.


Por que eu posso falar sobre isso?

O fato é que sou um desenvolvedor de desenvolvimento abrangente: não apenas possuo o SDK do Android como se fosse meu projeto de estimação, mas por mais um mês escrevi código para o back-end.


Quando levei meu primeiro microsserviço para a revisão e não havia benchmarking no README , ele olhou para mim com um mal-entendido. Lembrei-me disso e nunca mais repeti esse erro. Porque ele saiu em uma semana.


Vamos lá


O que estamos medindo


Como parte do caso dos bancos de dados de benchmarking para Android, decidi medir a velocidade de inicialização e a velocidade de gravação / leitura para ORMs como Paper, Hawk, Realm e Room.
Sim, avalio em um teste NoSQL e um banco de dados relacional - qual é a próxima pergunta?


Do que medimos


Parece que se estamos falando sobre a JVM, a escolha é óbvia - há uma JMH glorificada , aperfeiçoada e perfeitamente documentada . Mas não, ele não inicia os testes de instrumentação para o Android.


O Google Calipher os segue - com o mesmo resultado.


Existe um garfo do Calipher chamado Spanner - que por muitos anos foi zeppercay e incentiva o uso do Androidx Benchmark .


Vamos nos concentrar no último. Se apenas porque não tivemos escolha.


Como tudo o que foi adicionado ao Jetpack e não repensado ao migrar da Biblioteca de suporte, o Androidx Benchmark parece e se comporta como se tivesse sido escrito em uma semana e meia como uma tarefa de teste, e ninguém mais o tocará. Além disso, essa lib é um pouco passado - porque é mais para avaliar testes de interface do usuário. Mas, por falta do melhor, você pode trabalhar com ela. Isso nos salvará, pelo menos, de erros óbvios e também ajudará no aquecimento.


Para reduzir o ridículo dos resultados, executarei todos os testes 10 vezes e calcularei a média.


Dispositivo de teste - Xiaomi A1. Não é o mais fraco do mercado, o Android "limpo".


Conectando uma Biblioteca a um Projeto


Existem excelentes instruções sobre como conectar o Andoridx Benchmark a um projeto. Eu recomendo fortemente que você não seja preguiçoso e conecte um módulo separado para fazer medições.


Progresso da experiência


Todos os nossos benchmarks serão executados na seguinte ordem:


  1. Primeiro, iniciamos o banco de dados no corpo de teste.
  2. Em seguida, no bloco benchmarkRule.scope.runWithTimingDisabled , geramos dados que alimentamos o banco de dados. O código colocado neste circuito não será levado em consideração na avaliação.
  3. No mesmo encerramento, adicionamos a lógica de limpar o banco de dados; verifique se o banco de dados está vazio antes de escrever.
  4. A seguir, é apresentada a lógica da escrita e da leitura. Certifique-se de inicializar a variável com o resultado da leitura, para que a JVM não remova essa lógica da contagem de execução como não utilizada.
  5. Medimos o desempenho da inicialização do banco de dados em uma função separada.
  6. Nos sentimos como um homem da ciência.

O código pode ser encontrado aqui . Se você estiver com preguiça de andar, a função de medição do PaperDb será assim:


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

Os benchmarks para o restante do ORM são semelhantes.


Resultados


Inicialização

nome do testedizer12345678910
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

O vencedor é Realm, em segundo lugar é Paper. O que Room está fazendo, você ainda pode imaginar que Hawk faz quase a mesma quantidade de tempo - é completamente incompreensível.


Escrita e leitura

nome do testedizer12345678910
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

Aqui, novamente, o vencedor do Reino, mas nesses resultados parece um fracasso.


A diferença de quatro vezes entre os dois bancos de dados "mais lentos" e dezesseis vezes entre os "mais rápidos" e os "mais lentos" é muito suspeita. Mesmo considerando o fato de que a diferença é estável.


Conclusão


Medir o desempenho do seu código é pelo menos por curiosidade. Mesmo se estivermos falando dos casos mais lançados pelo setor (como a avaliação de testes instrumentais para Android).


Há todos os motivos para usar estruturas de terceiros para esse negócio (em vez de escrever sua própria com tempo e líderes de torcida).


A situação nas bases de código é tal que todos estão tentando escrever em uma arquitetura limpa, pois a maioria do módulo com lógica de negócios é um módulo java - para conectar um módulo ao JMH nas proximidades e verificar se há gargalos no código - funciona por um dia. E os benefícios - por muitos anos vindouros.


Feliz codificação!


PS: Se um leitor atento souber sobre a estrutura para a realização de benchmarks de testes instrumentais para Android, não listados no artigo - compartilhe nos comentários.


PPS: O repositório de teste está aberto para solicitações pull.

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


All Articles