Avaliando o Desempenho

Use Simpleperf para avaliar o desempenho de um dispositivo. Simpleperf é uma ferramenta de criação de perfil nativa para aplicativos e processos nativos no Android. Use o CPU Profiler para inspecionar o uso da CPU do aplicativo e a atividade do thread em tempo real.

Existem dois indicadores de desempenho visíveis ao usuário:

  • Desempenho previsível e perceptível . A interface do usuário (IU) elimina quadros ou é renderizada consistentemente a 60FPS? O áudio é reproduzido sem artefatos ou estalos? Quanto tempo leva o atraso entre o toque do usuário na tela e o efeito exibido no display?
  • Tempo necessário para operações mais longas (como abertura de aplicativos).

O primeiro é mais perceptível que o segundo. Os usuários normalmente percebem instabilidade, mas não serão capazes de saber o tempo de inicialização do aplicativo entre 500 ms e 600 ms, a menos que estejam olhando para dois dispositivos lado a lado. A latência do toque é imediatamente perceptível e contribui significativamente para a percepção de um dispositivo.

Como resultado, em um dispositivo rápido, o pipeline de UI é a coisa mais importante no sistema, além do que é necessário para manter o pipeline de UI funcional. Isso significa que o pipeline da UI deve antecipar qualquer outro trabalho que não seja necessário para uma UI fluida. Para manter uma IU fluida, a sincronização em segundo plano, a entrega de notificações e trabalhos semelhantes devem ser adiados se o trabalho da IU puder ser executado. É aceitável negociar o desempenho de operações mais longas (tempo de execução HDR+, inicialização de aplicativos, etc.) para manter uma UI fluida.

Capacidade vs jitter

Ao considerar o desempenho do dispositivo, a capacidade e o jitter são duas métricas significativas.

Capacidade

Capacidade é a quantidade total de algum recurso que o dispositivo possui durante um determinado período de tempo. Podem ser recursos de CPU, recursos de GPU, recursos de E/S, recursos de rede, largura de banda de memória ou qualquer métrica semelhante. Ao examinar o desempenho de todo o sistema, pode ser útil abstrair os componentes individuais e assumir uma única métrica que determina o desempenho (especialmente ao ajustar um novo dispositivo porque as cargas de trabalho executadas nesse dispositivo provavelmente são fixas).

A capacidade de um sistema varia de acordo com os recursos de computação online. Alterar a frequência da CPU/GPU é o principal meio de alterar a capacidade, mas existem outros, como alterar o número de núcleos da CPU online. Consequentemente, a capacidade de um sistema corresponde ao consumo de energia; alterar a capacidade sempre resulta em uma alteração semelhante no consumo de energia.

A capacidade necessária em um determinado momento é determinada em grande parte pelo aplicativo em execução. Como resultado, a plataforma pouco pode fazer para ajustar a capacidade necessária para uma determinada carga de trabalho, e os meios para fazer isso são limitados a melhorias de tempo de execução (estrutura Android, ART, Bionic, compilador/drivers de GPU, kernel).

Tremor

Embora a capacidade necessária para uma carga de trabalho seja fácil de ver, o jitter é um conceito mais nebuloso. Para uma boa introdução ao jitter como um impedimento para sistemas rápidos, consulte O CASO DO DESEMPENHO DO SUPERCOMPUTADOR FALTA: ATINGINDO O DESEMPENHO ÓTIMO NOS 8.192 PROCESSADORES DE ASCl Q . (É uma investigação sobre por que o supercomputador ASCI Q não atingiu o desempenho esperado e é uma ótima introdução à otimização de grandes sistemas.)

Esta página usa o termo jitter para descrever o que o artigo ASCI Q chama de ruído . Jitter é o comportamento aleatório do sistema que impede a execução de trabalho perceptível. Freqüentemente, é um trabalho que deve ser executado, mas pode não ter requisitos rígidos de tempo que o façam ser executado em um determinado momento. Por ser aleatório, é extremamente difícil refutar a existência de jitter para uma determinada carga de trabalho. Também é extremamente difícil provar que uma fonte conhecida de jitter foi a causa de um problema específico de desempenho. As ferramentas mais comumente usadas para diagnosticar causas de jitter (como rastreamento ou registro) podem introduzir seu próprio jitter.

As fontes de jitter experimentadas em implementações reais do Android incluem:

  • Atraso do agendador
  • Manipuladores de interrupção
  • Código do driver em execução por muito tempo com preempção ou interrupções desativadas
  • Softirqs de longa duração
  • Contenção de bloqueio (aplicativo, estrutura, driver de kernel, bloqueio de fichário, bloqueio mmap)
  • Contenção do descritor de arquivo em que um thread de baixa prioridade mantém o bloqueio em um arquivo, impedindo a execução de um thread de alta prioridade
  • Executando código crítico da UI em filas de trabalho onde poderia ser atrasado
  • Transições ociosas de CPU
  • Exploração madeireira
  • Atrasos de E/S
  • Criação desnecessária de processos (por exemplo, transmissões CONNECTIVITY_CHANGE)
  • Destruição do cache de página causada por memória livre insuficiente

A quantidade de tempo necessária para um determinado período de jitter pode ou não diminuir à medida que a capacidade aumenta. Por exemplo, se um driver deixar as interrupções desabilitadas enquanto espera por uma leitura em um barramento i2c, isso levará um tempo fixo, independentemente de a CPU estar em 384 MHz ou 2 GHz. Aumentar a capacidade não é uma solução viável para melhorar o desempenho quando há jitter envolvido. Como resultado, processadores mais rápidos normalmente não melhorarão o desempenho em situações com restrição de jitter.

Finalmente, diferentemente da capacidade, o jitter é quase inteiramente de domínio do fornecedor do sistema.

Consumo de memória

O consumo de memória é tradicionalmente responsabilizado pelo baixo desempenho. Embora o consumo em si não seja um problema de desempenho, ele pode causar instabilidade por meio de sobrecarga do lowmemorykiller, reinicializações de serviço e sobrecarga do cache de página. A redução do consumo de memória pode evitar as causas diretas do baixo desempenho, mas pode haver outras melhorias direcionadas que também evitam essas causas (por exemplo, fixar a estrutura para evitar que ela seja paginada quando for paginada logo depois).

Analisando o desempenho inicial do dispositivo

Partir de um sistema funcional, mas com baixo desempenho, e tentar corrigir o comportamento do sistema observando casos individuais de mau desempenho visível ao usuário não é uma estratégia sólida. Como o baixo desempenho geralmente não é facilmente reproduzível (ou seja, jitter) ou é um problema de aplicação, muitas variáveis ​​no sistema completo impedem que essa estratégia seja eficaz. Como resultado, é muito fácil identificar erroneamente as causas e fazer pequenas melhorias, perdendo ao mesmo tempo oportunidades sistêmicas para corrigir o desempenho de todo o sistema.

Em vez disso, use a seguinte abordagem geral ao criar um novo dispositivo:

  1. Faça com que o sistema inicialize na UI com todos os drivers em execução e algumas configurações básicas do regulador de frequência (se você alterar as configurações do regulador de frequência, repita todas as etapas abaixo).
  2. Certifique-se de que o kernel suporte o tracepoint sched_blocked_reason , bem como outros tracepoints no pipeline de exibição que indicam quando o quadro é entregue ao display.
  3. Faça longos rastreamentos de todo o pipeline da UI (desde o recebimento de entrada por meio de um IRQ até a verificação final) enquanto executa uma carga de trabalho leve e consistente (por exemplo, UiBench ou o teste de bola no TouchLatency) .
  4. Corrija as quedas de quadros detectadas na carga de trabalho leve e consistente.
  5. Repita as etapas 3 a 4 até que você possa executar sem nenhum quadro perdido por mais de 20 segundos por vez.
  6. Passe para outras fontes de instabilidade visíveis ao usuário.

Outras coisas simples que você pode fazer logo no início da inicialização do dispositivo incluem:

  • Certifique-se de que seu kernel tenha o patch de tracepoint sched_blocked_reason . Este tracepoint é habilitado com a categoria de rastreamento agendada no systrace e fornece a função responsável por dormir quando esse thread entra em suspensão ininterrupta. É fundamental para a análise de desempenho porque o sono ininterrupto é um indicador muito comum de instabilidade.
  • Certifique-se de ter rastreamento suficiente para a GPU e os pipelines de exibição. Em SOCs recentes da Qualcomm, os tracepoints são habilitados usando:
  • adb shell "echo 1 > /d/tracing/events/kgsl/enable"
    adb shell "echo 1 > /d/tracing/events/mdss/enable"
    

    Esses eventos permanecem habilitados quando você executa o systrace para que você possa ver informações adicionais no rastreamento sobre o pipeline de exibição (MDSS) na seção mdss_fb0 . Nos SOCs da Qualcomm, você não verá nenhuma informação adicional sobre a GPU na visualização do systrace padrão, mas os resultados estão presentes no próprio rastreamento (para obter detalhes, consulte Noções básicas sobre o systrace ).

    O que você deseja desse tipo de rastreamento de exibição é um único evento que indique diretamente que um quadro foi entregue à exibição. A partir daí, você pode determinar se atingiu o tempo de quadro com sucesso; se o evento X n ocorrer menos de 16,7 ms após o evento X n-1 (assumindo uma exibição de 60 Hz), então você sabe que não houve instabilidade. Se o seu SOC não fornecer esses sinais, trabalhe com seu fornecedor para obtê-los. A depuração do jitter é extremamente difícil sem um sinal definitivo de conclusão do quadro.

Usando benchmarks sintéticos

Os benchmarks sintéticos são úteis para garantir que a funcionalidade básica de um dispositivo esteja presente. No entanto, tratar os benchmarks como um proxy para o desempenho percebido do dispositivo não é útil.

Com base em experiências com SOCs, as diferenças no desempenho de benchmark sintético entre SOCs não estão correlacionadas com uma diferença semelhante no desempenho perceptível da UI (número de quadros perdidos, tempo de quadro do 99º percentil, etc.). Os benchmarks sintéticos são benchmarks apenas de capacidade; o jitter afeta o desempenho medido desses benchmarks apenas ao roubar tempo da operação em massa do benchmark. Como resultado, as pontuações de benchmark sintéticas são em sua maioria irrelevantes como métrica de desempenho percebido pelo usuário.

Considere dois SOCs executando o Benchmark X que renderiza 1.000 quadros de UI e relata o tempo total de renderização (pontuação mais baixa é melhor).

  • SOC 1 renderiza cada quadro do Benchmark X em 10 ms e pontua 10.000.
  • SOC 2 renderiza 99% dos frames em 1 ms, mas 1% dos frames em 100 ms e pontua 19.900, uma pontuação dramaticamente melhor.

Se o benchmark for indicativo do desempenho real da UI, o SOC 2 seria inutilizável. Assumindo uma taxa de atualização de 60 Hz, o SOC 2 teria um quadro instável a cada 1,5s de operação. Enquanto isso, o SOC 1 (o SOC mais lento de acordo com o Benchmark X) seria perfeitamente fluido.

Usando relatórios de bugs

Relatórios de bugs às vezes são úteis para análise de desempenho, mas por serem muito pesados, raramente são úteis para depurar problemas de instabilidade esporádicos. Eles podem fornecer algumas dicas sobre o que o sistema estava fazendo em um determinado momento, especialmente se a instabilidade ocorreu em torno de uma transição de aplicativo (que é registrada em um relatório de bug). Os relatórios de bugs também podem indicar quando algo está errado de forma mais ampla com o sistema que pode reduzir sua capacidade efetiva (como aceleração térmica ou fragmentação de memória).

Usando TouchLatency

Vários exemplos de mau comportamento vêm do TouchLatency, que é a carga de trabalho periódica preferida usada para Pixel e Pixel XL. Está disponível em frameworks/base/tests/TouchLatency e possui dois modos: latência de toque e bola quicando (para alternar os modos, clique no botão no canto superior direito).

O teste da bola quicando é exatamente tão simples quanto parece: uma bola quica na tela para sempre, independentemente da entrada do usuário. Geralmente também é de longe o teste mais difícil de executar perfeitamente, mas quanto mais próximo estiver de executar sem perda de quadros, melhor será o seu dispositivo. O teste da bola quicando é difícil porque é uma carga de trabalho trivial, mas perfeitamente consistente, que roda em um clock muito baixo (isso pressupõe que o dispositivo tenha um regulador de frequência; se o dispositivo estiver rodando com clocks fixos, faça downclock da CPU/GPU para quase o mínimo ao executar o teste da bola quicando pela primeira vez). À medida que o sistema é desativado e os clocks ficam mais próximos da inatividade, o tempo de CPU/GPU necessário por quadro aumenta. Você pode assistir a bola e ver as coisas erradas, e também poderá ver os frames perdidos no systrace.

Como a carga de trabalho é tão consistente, você pode identificar a maioria das fontes de jitter com muito mais facilidade do que na maioria das cargas de trabalho visíveis ao usuário, rastreando exatamente o que está sendo executado no sistema durante cada quadro perdido, em vez do pipeline da UI. Os clocks mais baixos amplificam os efeitos do jitter, tornando mais provável que qualquer jitter cause a perda de um quadro. Como resultado, quanto mais próximo o TouchLatency estiver de 60FPS, menor será a probabilidade de você ter comportamentos inadequados do sistema que causem instabilidades esporádicas e difíceis de reproduzir em aplicativos maiores.

Como o jitter é frequentemente (mas nem sempre) invariante à velocidade do clock, use um teste executado em clocks muito baixos para diagnosticar o jitter pelos seguintes motivos:

  • Nem todo jitter é invariante à velocidade do clock; muitas fontes apenas consomem tempo de CPU.
  • O governador deve obter o tempo médio do quadro próximo ao prazo, diminuindo o tempo, de modo que o tempo gasto na execução de trabalhos não relacionados à interface do usuário possa levá-lo ao limite e eliminar um quadro.