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:
- Primeiro, iniciamos o banco de dados no corpo de teste.
- 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. - No mesmo encerramento, adicionamos a lógica de limpar o banco de dados; verifique se o banco de dados está vazio antes de escrever.
- 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.
- Medimos o desempenho da inicialização do banco de dados em uma função separada.
- 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 {
Os benchmarks para o restante do ORM são semelhantes.
Resultados
Inicialização
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
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.