Exemplos de medição e análise de desempenho

Estes exemplos mostram como usar o rastreamento do sistema com a Macrobenchmark, junto com a criação de perfil de memória, para avaliar e melhorar determinados tipos de problemas de performance.

Como depurar a inicialização do app com o Systrace

Ao depurar o tempo de inicialização, recomendamos o uso de registros do Systrace. O Systrace é um sistema que usa código pré-instrumentado para descobrir quanto tempo determinados eventos durarão. Esses traces permitem que você veja o que está acontecendo no seu aplicativo ou mesmo em outros processos do sistema. A plataforma Android e as bibliotecas do Jetpack têm instrumentação em muitos eventos importantes de um aplicativo, e todos eles são registrados de acordo com isso. Você também pode instrumentar seus aplicativos com seus rastros personalizados, que vão ser mostrados nas mesmas ferramentas de visualização do Systrace, oferecendo uma visão geral do que aconteceu no aplicativo.

Como usar o Systrace ou o Perfetto

Para saber mais sobre o uso básico do Systrace, consulte o vídeo abaixo: Como depurar a performance do aplicativo (em inglês).

Para analisar o tempo de inicialização, primeiro é preciso entender o que acontece nesse momento. Se você quiser mais informações do que o explicado nesta página, leia a documentação sobre tempo de inicialização do app para ter uma visão geral desse processo.

Estas são as etapas da inicialização do app:

  • Início do processo
  • Inicialização de objetos genéricos do aplicativo
  • Criação e inicialização da atividade
  • Inflação do layout
  • Exibição do primeiro frame

Os tipos de inicialização têm as seguintes etapas:

  • Inicialização a frio: ocorre quando o aplicativo está sendo iniciado pela primeira vez desde a inicialização ou desde que o processo do aplicativo foi encerrado pelo usuário ou pelo sistema. A inicialização cria um novo processo sem estado salvo.
  • Inicialização lenta com o app aberto em segundo plano: ocorre quando o aplicativo já está em execução em segundo plano, mas a atividade precisa ser recriada e colocada em primeiro plano. A atividade é recriada ao reutilizar o processo existente, ou o processo é recriado com o estado salvo. A biblioteca de testes Macrobenchmark é compatível com testes consistentes de inicialização lenta usando a primeira opção.
  • Inicialização a quente: ocorre quando o processo e a atividade ainda estão em execução e só precisam ser colocados em primeiro plano, possivelmente recriando alguns objetos conforme necessário e renderizando a nova atividade em primeiro plano. Este é o cenário de inicialização mais curto.

Recomendamos a captura de systraces usando o app de rastreamento do sistema no dispositivo disponível em "Opções do desenvolvedor". Se você quiser usar ferramentas de linha de comando, o Perfetto está disponível para uso com o Android 10 (nível 29 da API) e versões mais recentes, enquanto dispositivos com versões anteriores precisam usar o systrace.

Observe que o termo "primeiro frame" não é exatamente correto, já que aplicativos podem variar significativamente na maneira como processam a inicialização depois de criar a atividade inicial. Alguns aplicativos continuam a inflação por vários frames, enquanto outros iniciam uma atividade secundária imediatamente.

Quando possível, recomendamos incluir uma chamada reportFullyDrawn (disponível no Android 10 e em versões mais recentes) quando a inicialização for concluída do ponto de vista do aplicativo.

Veja algumas coisas que precisam ser procuradas no rastreamento do sistema:

Contenção do monitoramento
Figura 1. A concorrência por recursos protegidos pelo monitor pode introduzir um atraso significativo na inicialização do app.

Transações de binder síncronas
Figura 2. Procure transações desnecessárias no caminho crítico do aplicativo.

Coleta de lixo simultânea
Figura 3. A coleta de lixo simultânea é comum e tem impacto relativamente baixo. No entanto, se ela estiver acontecendo com frequência, investigue-a com o Memory Profiler do Android Studio.

E/S na inicialização
Figura 4. Confira a E/S durante a inicialização e procure paradas mais longas.

Observe na Figura 4 que outros processos que executam E/S ao mesmo tempo podem causar uma contenção de E/S. Assim, confira se outros processos não estão em execução.

Atividades significativas em outras linhas de execução podem interferir na linha de execução de IU. Preste atenção no trabalho em segundo plano durante a inicialização. Os dispositivos podem ter diferentes configurações de CPU. Portanto, o número de linhas de execução que podem ser executadas em paralelo pode variar entre os dispositivos.

Consulte também o guia sobre fontes comuns de instabilidade.

Usar o Memory Profiler do Android Studio

O Memory Profiler do Android Studio é uma ferramenta avançada para reduzir a pressão da memória que pode ser causada por vazamentos de memória ou padrões de uso inadequados. Ele oferece uma visualização ao vivo de alocações e coleções de objetos.

Para corrigir problemas de memória no app, use o Memory Profiler para acompanhar o motivo e a frequência das coletas de lixo, bem como se há possíveis vazamentos de memória fazendo com que o heap aumente constantemente.

A criação de perfis de memória no app é dividida nestas etapas:

1. Detecção de problemas de memória

Para detectar problemas de memória, comece gravando uma sessão de criação de perfil de memória para o app. Em seguida, procure um objeto cujo consumo de memória esteja aumentando, e com o tempo, acionando um evento de coleta de lixo.

Contagem crescente de objetos
Figura 5. O Memory Profiler está mostrando aumento na alocação de objetos ao longo do tempo.

Coletas de lixo
Figura 6. O Memory Profiler está mostrando eventos de coleta de lixo.{.:image-caption}

Depois de identificar um caso de uso que está aumentando a pressão sobre a memória, comece a procurar as causas raiz.

2. Diagnóstico de pontos de acesso de pressão sobre a memória

Selecione um intervalo na linha do tempo para visualizar as alocações e o tamanho superficial.

Visualizar as alocações e o tamanho superficial
Figura 7. O Memory Profiler está mostrando alocações e tamanhos para um intervalo selecionado na linha do tempo.

Há várias maneiras de classificar esses dados. As seções a seguir mostram exemplos de como cada visualização pode ajudar você a analisar problemas.

Organização por classe

A organização por classe é útil quando você quer encontrar classes que estão gerando objetos que deveriam ser armazenados em cache ou reutilizados de um pool de memória.

Por exemplo, imagine um app criando 2 mil objetos de classe chamados "Vertex" a cada segundo. Isso aumentaria a contagem de alocações em 2 mil por segundo, e você veria isso acontecer ao classificar por classe. Esses objetos precisam ser reutilizados para evitar a geração de lixo? Se a resposta for sim, a implementação de um pool de memória provavelmente será necessária.

Organização por pilha de chamadas

A organização por pilha de chamadas é útil quando há um caminho em que a memória está sendo alocada, como dentro de um loop ou de uma função específica que realiza grande parte da alocação. A visualização por pilha de chamadas permitirá que você veja esses pontos de acesso de alocação.

Tamanho superficial versus retido

O tamanho superficial rastreia apenas a memória do próprio objeto. Portanto, é mais útil para rastrear classes simples compostas principalmente por primitivos.

O tamanho retido mostra a memória total alocada pelo objeto diretamente, bem como outros objetos alocados que são apenas referenciados pelo objeto. Ele é útil para rastrear a pressão sobre a memória causada por objetos complexos que exigem a alocação de outros objetos, e não apenas de campos primitivos. Para conseguir esse valor, crie um despejo de memória usando o Memory Profiler. Os objetos alocados nesse heap são adicionados à exibição.

Despejo de memória cheio
Figura 8. Você pode criar um despejo de memória a qualquer momento clicando no botão "Dump Java heap" na barra de ferramentas do Memory Profiler.

adicionado como uma coluna
Figura 9. A criação de um despejo de memória exibe uma coluna que mostra as alocações de objetos nesse heap.

3. Medição do impacto de uma otimização

Uma melhoria na otimização de memória fácil de medir é a coleta de lixo. Quando uma otimização reduz a pressão sobre a memória, você vê menos coletas de lixo (GCs, na sigla em inglês). Para isso, meça o tempo entre as GCs na linha do tempo do Memory Profiler. Você verá durações mais longas entre GCs após as otimizações de memória.

Veja o impacto final de melhorias de memória como essas:

  • O app será encerrado com menos frequência devido a problemas de falta de memória se ele não tiver pressão constante sobre a memória.
  • A redução de GCs melhora as métricas de instabilidade. Isso ocorre porque as GCs causam contenção de CPU, o que pode adiar tarefas de renderização durante a GC.