A ferramenta Criação de perfil de renderização de GPU indica o tempo relativo que cada fase do pipeline de renderização leva para renderizar o frame anterior. Esse conhecimento pode ajudar a identificar gargalos no pipeline. Assim, você descobre o que precisa ser otimizado para melhorar o desempenho de renderização do app.
Esta página explica brevemente o que acontece durante cada fase de pipeline e discute problemas que podem causar gargalos. Antes de ler esta página, você precisa se familiarizar com as informações apresentadas em Criar perfil de velocidade de renderização de GPU. Além disso, para entender como todas as fases se encaixam, pode ser útil revisar como o pipeline de renderização funciona.
Representação visual
A ferramenta Criação de perfil de renderização de GPU exibe as fases do pipeline e os tempos relativos delas na forma de um gráfico: um histograma codificado por cor. A Figura 1 mostra um exemplo.
Cada segmento de cada barra vertical exibida no gráfico da Criação de perfil de renderização da GPU representa uma fase de pipeline e está destacada com uma cor específica no gráfico de barras. A Figura 2 mostra uma legenda para o significado de cada cor.
Depois de entender o que cada cor significa, é possível segmentar aspectos específicos do seu app para tentar otimizar o desempenho de renderização.
As fases e o que elas significam
Esta seção explica o que acontece em cada etapa e as causas de gargalos a serem observadas.
Gerenciamento de entradas
A fase de controle de entrada do pipeline mede quanto tempo o app passou controlando eventos de entrada. Essa métrica indica o tempo que o app passou executando o código chamado como resultado de callbacks de eventos de entrada.
Quando esse segmento é grande
Valores altos nessa área costumam ser resultado de muito trabalho ou de trabalho complexo demais e ocorrem nos callbacks de eventos de controle de entrada. Como esses callbacks sempre ocorrem na linha de execução principal, as soluções para esse problema se concentram em otimizar o trabalho diretamente ou descarregar o trabalho em uma linha de execução diferente.
A rolagem por um
LazyColumn
ou LazyRow
também pode aparecer nessa fase. Quando um toque do usuário se qualifica como uma rolagem, a lista
preguiçosa consome eventos de toque para compor e dispor itens dinamicamente. Se o app
realizar trabalho personalizado respondendo a mudanças na posição de rolagem, é importante fazer essa operação o mais rápido possível para evitar quedas
de frames. Ferramentas de criação de perfil, como o CPU Profiler no Android Studio ou o Perfetto, podem ajudar você a investigar mais detalhadamente. Consulte a Visão geral do rastreamento do sistema para mais informações.
Animações
A fase de animações mostra o tempo necessário para avaliar todos os estados de animação
que estavam sendo executados no frame. Algumas APIs de animação comuns no Compose são
animate*AsState,
Transition
e Animatable.
Além disso, o Recomposer é executado durante essa fase para processar mudanças de estado
de snapshot e atualizar composições. Isso significa que a sobrecarga de recomposição geralmente aparece diretamente na etapa de animação.
Para interfaces do Jetpack Compose, inclua a biblioteca Compose Runtime Tracing para ver rastreamentos detalhados de composição junto com eventos do sistema.
Quando esse segmento é grande
Valores altos nessa área costumam ser resultado de trabalho que está sendo executado devido
a mudanças de estado impulsionadas pela animação. Por exemplo, uma animação de deslizar rapidamente, que rola seu LazyColumn
ou LazyRow, causa composição, medição e alocação rápidas de novos itens da lista.
Medida
Para desenhar os elementos combináveis na tela, o Android executa três fases em nós de layout na árvore de UI.
Primeiro, o sistema mede os nós de layout. Cada elemento combinável tem restrições e modificadores específicos que descrevem os limites de tamanho do objeto na tela. Alguns elementos combináveis podem ter um tamanho específico e fixo, enquanto outros têm um tamanho que se adapta às restrições transmitidas pelo contêiner de layout pai.
Em segundo lugar, o sistema coloca os nós de layout. Depois que o Compose calcula os tamanhos dos nós filhos durante a fase de medição, ele pode prosseguir com a fase de posicionamento, em que dimensiona e posiciona os nós de layout na tela.
O sistema sempre realiza esse layout de passagem única para eficiência. Quando um layout combinável é invalidado, o Compose mede esse nó específico e propaga atualizações de layout apenas para hierarquias de elementos principais se o elemento filho mudar de tamanho ou restrições.
Quando esse segmento é grande
Um segmento grande nessa área significa que o app está passando muito tempo na fase de layout, que consiste em posicionar e determinar o tamanho dos nós de layout. Essas operações incluem a execução de modificadores de medição e posicionamento para elementos combináveis, o que pode atrasar a preparação de frames se a árvore de layout for muito complexa. Nesses casos, para melhorar o desempenho, faça um comparativo de mercado do seu app do Compose e siga as práticas recomendadas de desempenho.
Use o CPU Profiler no Android Studio ou o Perfetto para inspecionar as transmissões de layout e identificar gargalos. Consulte Visão geral do rastreamento do sistema para mais informações.
Desenhar
A fase de desenho traduz operações de renderização, como desenho de um plano de fundo, uma forma ou um texto, em uma sequência de comandos nativos de desenho. O sistema organiza esses comandos em uma lista de exibição para execução na GPU.
A barra Draw registra quanto tempo leva para concluir a organização dos comandos na lista de exibição em relação a todos os nós de layout que precisavam de atualização na tela desse quadro. O tempo medido também se aplica a qualquer lógica de desenho personalizada que você possa ter dentro de modificadores de desenho ou um elemento combinável Canvas.
Quando esse segmento é grande
Em termos simplificados, você pode entender essa métrica como uma demonstração de quanto tempo levou para executar todos os comandos de desenho de cada nó de layout invalidado.
Essa medida inclui qualquer tempo gasto para despachar esses comandos para nós filhos e drawables vetoriais. Por esse motivo, quando você vir essa barra em destaque, poderá ser porque muitos combináveis se tornaram inválidos de repente. A invalidação faz com que seja necessário reexecutar comandos de desenho e regenerar as listas de exibição dos nós de layout. Como alternativa, um tempo longo pode ser resultado de alguns elementos combináveis
ou telas personalizadas que têm alguma lógica extremamente complexa na
implementação de
DrawScope.
Além disso, o Compose geralmente processa as medições internas e as transmissões de layout no que a plataforma considera a fase de renderização. Consequentemente, uma barra de renderização elevada pode ser causada por operações de medição/layout internas caras ou excessivas, e não apenas por comandos de renderização. Em caso de dúvida, capture um rastreamento do Perfetto para ver se o excesso vem de rotinas de desenho ou de medições e transmissões de layout do Compose.
Fazer upload
A métrica de upload representa o tempo necessário para transferir objetos de bitmap da memória de CPU para a memória de GPU durante o frame atual.
Como processadores diferentes, a CPU e a GPU têm diferentes áreas de RAM dedicadas ao processamento. Quando você desenha um bitmap no Android, o sistema transfere o bitmap para a memória de GPU antes que a GPU possa renderizá-lo na tela. Depois disso, a GPU armazena o bitmap em cache para que o sistema não precise transferir os dados novamente, a não ser que a textura seja removida do cache de textura da GPU.
Observação: em dispositivos Lollipop, essa fase é roxa.
Quando esse segmento é grande
Todos os recursos de um frame precisam residir na memória da GPU antes de serem usados para desenhar um frame. Isso significa que um valor alto para essa métrica pode representar um grande número de pequenas cargas de recurso ou um pequeno número de recursos muito grandes. Um caso comum é quando um app exibe um único bitmap próximo ao tamanho da tela. Outro caso é quando um app exibe um grande número de miniaturas.
Para reduzir essa barra, você pode empregar as seguintes técnicas:
- Garantir que as resoluções de bitmap não sejam muito maiores que o tamanho em que serão exibidas. Por exemplo, evite exibir uma imagem de 1024x1024 como 48x48.
- Aproveitar bibliotecas modernas como Coil para fazer o pré-upload assíncrono de um bitmap antes da próxima fase de sincronização.
Mandar comandos (Issue commands)
O segmento "Emitir comandos" representa o tempo necessário para emitir todos os comandos necessários para desenhar listas de exibição na tela.
Para que o sistema desenhe listas de exibição na tela, ele envia os comandos necessários para a GPU. Normalmente, ele executa essa ação usando a API OpenGL ES.
Esse processo leva algum tempo, já que o sistema realiza a transformação e o corte finais para cada comando antes de enviar o comando para a GPU. Há então uma sobrecarga adicional no lado da GPU, que computa os comandos finais. Esses comandos incluem transformações finais e outros recortes.
Quando esse segmento é grande
O tempo gasto nessa fase é uma medida direta da complexidade e da quantidade de listas de exibição renderizadas pelo sistema em um determinado frame. Por exemplo, um excesso de operações de desenho, principalmente em casos em que existe um pequeno custo inerente para cada desenho básico, pode inflar esse tempo. Exemplo:
for (i in 0 until 1000) { canvas.drawPoint() }
é muito mais caro para emitir do que:
canvas.drawPoints(thousandPointArray)
Nem sempre há uma correlação direta entre a emissão de comandos e o desenho de listas de exibição. Ao contrário da barra de comandos de emissão, que captura o tempo para enviar comandos de desenho à GPU, a métrica de desenho representa o tempo necessário para capturar os comandos emitidos na lista de exibição.
Essa diferença surge porque as listas de exibição são armazenadas em cache pelo sistema sempre que possível. Como resultado, existem situações em que uma rolagem, transformação ou animação precisa que o sistema reenvie uma lista de exibição, mas sem reconstruí-la (recapturar os comandos de desenho) do zero. Por isso, você pode ver uma alta na barra "Issue commands" sem uma alta na barra "Draw commands".
Trocar buffers
Quando o Android termina de enviar a lista de exibição para a GPU, o sistema emite um comando final para informar ao driver de gráficos que está tudo terminado no frame atual. Nesse ponto, o driver pode finalmente apresentar a imagem atualizada à tela.
Quando esse segmento é grande
É importante entender que a GPU executa trabalhos em paralelo com a CPU. O sistema Android emite comandos de desenho para a GPU e segue para a tarefa seguinte. A GPU lê esses comandos de desenho em uma fila e os processa.
Em situações em que a CPU emite comandos mais rápido do que a GPU consegue consumi-los, a fila de comunicações entre os processadores pode ficar cheia. Quando isso ocorre, a CPU bloqueia a emissão e espera até que haja espaço na fila para colocar o próximo comando. Esse estado de fila cheia acontece com frequência durante a fase de troca de buffers porque, nesse momento, todo um frame de comandos foi enviado.
O segredo para atenuar esse problema é reduzir a complexidade de trabalho que ocorre na GPU, de forma parecida com o que você faria na fase de comandos de emissão.
Diversos
Além do tempo que o sistema de renderização leva para executar o trabalho, há um conjunto adicional de trabalhos da linha de execução principal que não tem nada a ver com a renderização. O tempo que esses trabalhos consomem é registrado como tempo diverso. O tempo diverso geralmente representa trabalhos que podem estar ocorrendo na linha de execução de interface entre dois frames de renderização consecutivos.
Quando esse segmento é grande
Se esse valor é alto, é provável que seu app tenha callbacks, intents ou outros trabalhos que deveriam estar acontecendo em outra linha de execução. Ferramentas como o CPU Profiler no Android Studio ou o Perfetto podem fornecer visibilidade para as tarefas em execução na linha de execução principal. Essa informação pode ajudar você a focar em melhorias de desempenho. Consulte Visão geral do rastreamento do sistema para mais informações.