Analizza con il rendering GPU del profilo

Lo strumento di profilo GPU per il rendering indica il tempo relativo impiegato da ogni fase della pipeline di rendering per eseguire il rendering del frame precedente. Queste informazioni possono aiutarti a identificare i colli di bottiglia nella pipeline in modo da sapere cosa ottimizzare per migliorare le prestazioni di rendering della tua app.

Questa pagina spiega brevemente cosa succede durante ogni fase della pipeline e illustra i problemi che possono causare colli di bottiglia in questa fase. Prima di leggere questa pagina, dovresti acquisire familiarità con le informazioni presentate in Rendering GPU del profilo. Inoltre, per capire come tutte le fasi si combinano insieme, potrebbe essere utile rivedere come funziona la pipeline di rendering.

Rappresentazione visiva

Lo strumento Profile GPU Rendering mostra le fasi e i relativi tempi sotto forma di grafico: un istogramma codificato per colore. La Figura 1 mostra un esempio di questo tipo di display.

Figura 1. Grafico di rendering GPU del profilo

Ogni segmento di ogni barra verticale visualizzato nel grafico di rendering GPU del profilo rappresenta una fase della pipeline ed è evidenziato con un colore specifico nel grafico a barre. La Figura 2 mostra una chiave per il significato di ogni colore visualizzato.

Figura 2. Legenda del grafico del grafico di rendering GPU del profilo

Dopo aver capito cosa significa ogni colore, puoi scegliere come target aspetti specifici della tua app per provare a ottimizzare le prestazioni del rendering.

Fasi e loro significato

Questa sezione spiega cosa succede durante ogni fase corrispondente a un colore nella Figura 2, nonché le cause dei collo di bottiglia da considerare.

Gestione input

La fase di gestione dell'input della pipeline misura il tempo che l'app ha dedicato alla gestione degli eventi di input. Questa metrica indica il tempo trascorso dall'app per eseguire il codice chiamato in seguito ai callback di eventi di input.

Quando il segmento è di grandi dimensioni

I valori elevati in questa area sono in genere il risultato di troppo lavoro o di lavoro troppo complesso che si verifica all'interno dei callback eventi di input-handler. Poiché questi callback si verificano sempre nel thread principale, le soluzioni a questo problema si concentrano sull'ottimizzazione diretta del lavoro o sulla distribuzione del lavoro in un thread diverso.

Vale anche la pena notare che RecyclerView in questa fase può apparire lo scorrimento. RecyclerView scorre immediatamente quando consuma l'evento tocco. Di conseguenza, può aumentare o completare nuove visualizzazioni degli articoli. Per questo motivo è importante effettuare questa operazione il più rapidamente possibile. Strumenti di profilazione come Traceview o Systrace possono aiutarti a indagare ulteriormente.

Animazione

La fase Animazioni mostra quanto tempo è stato necessario per valutare tutti gli animatori in esecuzione in quel frame. Gli animatori più comuni sono ObjectAnimator, ViewPropertyAnimator e Transizioni.

Quando il segmento è di grandi dimensioni

I valori elevati in quest'area sono generalmente dovuti a lavori in corso a causa di modifiche delle proprietà dell'animazione. Ad esempio, un'animazione scorrevole, che fa scorrere ListView o RecyclerView, causa un aumento delle visualizzazioni e un aumento della popolazione.

Misurazione/layout

Per poter disegnare gli elementi delle viste sullo schermo, Android esegue due operazioni specifiche nei layout e nelle viste nella gerarchia delle visualizzazioni.

Innanzitutto, il sistema misura gli elementi visualizzati. Ogni vista e ogni layout dispone di dati specifici che descrivono le dimensioni dell'oggetto sullo schermo. Alcune viste possono avere dimensioni specifiche; altre hanno una dimensione che si adatta alle dimensioni del contenitore di layout principale

In secondo luogo, il sistema presenta gli elementi visualizzati. Una volta che il sistema ha calcolato le dimensioni delle viste secondarie, può procedere con il layout, il dimensionamento e il posizionamento delle visualizzazioni sullo schermo.

Il sistema esegue misurazioni e layout non solo per le viste da tracciare, ma anche per le relative gerarchie principali, fino alla vista principale.

Quando il segmento è di grandi dimensioni

Se la tua app trascorre molto tempo per frame in quest'area, di solito è a causa dell'enorme volume di visualizzazioni che devono essere strutturate o a problemi come la doppia tassazione che si collocano nel punto sbagliato della gerarchia. In entrambi i casi, gestire le prestazioni significa migliorare le prestazioni delle gerarchie delle visualizzazioni.

Anche il codice che hai aggiunto a onLayout(boolean, int, int, int, int) o onMeasure(int, int) può causare problemi di prestazioni. Traceview e Systrace possono aiutarti a esaminare gli stack di chiamate per identificare i problemi che il codice potrebbe avere.

Disegna

La fase di disegno traduce le operazioni di rendering di una vista, ad esempio il disegno di uno sfondo o il disegno di un testo, in una sequenza di comandi di disegno nativi. Il sistema acquisisce questi comandi in un elenco di visualizzazione.

La barra di disegno registra il tempo necessario per completare l'acquisizione dei comandi nell'elenco di visualizzazione per tutte le viste che dovevano essere aggiornate sullo schermo per questo frame. Il tempo misurato si applica a qualsiasi codice che hai aggiunto agli oggetti UI nella tua app. Esempi di questo codice possono essere onDraw(), dispatchDraw() e i vari draw ()methods appartenenti alle sottoclassi della classe Drawable.

Quando il segmento è di grandi dimensioni

In termini semplificati, puoi comprendere questa metrica come un indicatore del tempo necessario per eseguire tutte le chiamate a onDraw() per ogni vista invalidata. Questa misurazione include il tempo trascorso a inviare comandi di disegno a bambini e file disegnabili che potrebbero essere presenti. Per questo motivo, quando noti un picco di queste barre, la causa potrebbe essere che un gruppo di visualizzazioni è stato improvvisamente invalidato. La convalida rende necessario rigenerare gli elenchi di visualizzazione delle visualizzazioni. In alternativa, un periodo di tempo prolungato potrebbe essere il risultato di alcune viste personalizzate che hanno una logica estremamente complessa nei metodi onDraw().

Sincronizza/carica

La metrica Sincronizzazione e caricamento rappresenta il tempo necessario per trasferire gli oggetti bitmap dalla memoria della CPU a quella della GPU durante il frame corrente.

Dal momento che processori diversi, la CPU e la GPU hanno aree RAM diverse dedicate all'elaborazione. Quando disegni una bitmap su Android, il sistema trasferisce la bitmap alla memoria della GPU prima che quest'ultima possa visualizzarla sullo schermo. Quindi, la GPU memorizza nella cache la bitmap in modo che il sistema non debba trasferire nuovamente i dati a meno che la texture non venga rimossa dalla cache delle texture della GPU.

Nota: sui dispositivi Lollipop, questa fase è viola.

Quando il segmento è di grandi dimensioni

Tutte le risorse di un frame devono risiedere nella memoria GPU prima di poter essere utilizzate per disegnare un frame. Ciò significa che un valore elevato per questa metrica potrebbe indicare un numero elevato di carichi di risorse di piccole dimensioni o un numero ridotto di risorse molto grandi. Un caso comune è quando un'app visualizza una singola bitmap vicina alle dimensioni dello schermo. Un altro caso è quando un'app mostra un numero elevato di miniature.

Per ridurre questa soglia, puoi utilizzare tecniche quali:

  • Assicurati che le risoluzioni bitmap non siano molto superiori alle dimensioni di visualizzazione. Ad esempio, l'app non deve mostrare un'immagine 1024 x 1024 come immagine 48 x 48.
  • Utilizzo di prepareToDraw() per precaricare in modo asincrono una bitmap prima della fase di sincronizzazione successiva.

Esegui comandi

Il segmento Comandi di problema rappresenta il tempo necessario per inviare tutti i comandi necessari per tracciare sullo schermo elenchi di visualizzazione.

Affinché il sistema disegna elenchi di visualizzazioni sullo schermo, invia i comandi necessari alla GPU. In genere, esegue questa azione tramite l'API OpenGL ES.

Questo processo richiede del tempo, poiché il sistema esegue la trasformazione e il ritaglio finali per ogni comando prima di inviare il comando alla GPU. Inoltre, si verifica un overhead aggiuntivo sul lato GPU, che calcola i comandi finali. Questi comandi includono le trasformazioni finali e i tagli aggiuntivi.

Quando il segmento è di grandi dimensioni

Il tempo impiegato in questa fase è una misura diretta della complessità e della quantità di elenchi di visualizzazione visualizzati dal sistema in un determinato frame. Ad esempio, avere molte operazioni di disegno, soprattutto nei casi in cui è presente un piccolo costo intrinseco per ogni primitiva di disegno, questa volta potrebbe gonfiare. Ecco alcuni esempi:

Kotlin

for (i in 0 until 1000) {
    canvas.drawPoint()
}

Java

for (int i = 0; i < 1000; i++) {
    canvas.drawPoint()
}

è molto più costoso da pubblicare rispetto a:

Kotlin

canvas.drawPoints(thousandPointArray)

Java

canvas.drawPoints(thousandPointArray);

Non esiste sempre una correlazione 1:1 tra l'invio di comandi e l'effettivo disegno di elenchi di visualizzazione. A differenza di Issue Commands, che acquisisce il tempo necessario per inviare i comandi di disegno alla GPU, la metrica Draw rappresenta il tempo necessario per acquisire i comandi emessi nell'elenco di visualizzazione.

Questa differenza si verifica perché gli elenchi di visualizzazione vengono memorizzati nella cache dal sistema, ove possibile. Di conseguenza, ci sono situazioni in cui uno scorrimento, una trasformazione o un'animazione richiedono che il sistema invii nuovamente un elenco di visualizzazione, ma non deve ricostruire effettivamente l'elenco (ovvero i comandi di disegno) da zero. Di conseguenza, puoi vedere una barra "Issue Commands" alta senza vedere una barra Disegna comandi alta.

Buffer di processo/scambia

Dopo che Android ha terminato di inviare alla GPU tutto l'elenco di visualizzazioni, il sistema emette un ultimo comando per comunicare al driver di grafica che l'operazione è stata completata con il frame corrente. A questo punto, il conducente può finalmente presentare sullo schermo l'immagine aggiornata.

Quando il segmento è di grandi dimensioni

È importante capire che la GPU esegue il lavoro in parallelo con la CPU. I problemi del sistema Android distraggono i comandi alla GPU e poi passano all'attività successiva. La GPU legge i comandi di disegno da una coda e li elabora.

Nelle situazioni in cui la CPU invia comandi più velocemente del consumo da parte della GPU, la coda di comunicazione tra i processori può diventare piena. In questo caso, la CPU si blocca e attende che ci sia spazio nella coda per inserire il comando successivo. Questo stato di coda completa si verifica spesso durante la fase dei buffer di scambio, perché a quel punto sono stati inviati comandi per un intero frame.

La chiave per attenuare questo problema è ridurre la complessità del lavoro svolto sulla GPU, in modo simile a quello che si farebbe per la fase "Issue Commands" (comandi di problema).

Vari

Oltre al tempo impiegato dal sistema di rendering per svolgere il suo lavoro, c'è un ulteriore insieme di operazioni che si svolge sul thread principale e non ha nulla a che fare con il rendering. Il tempo utilizzato da questo lavoro è riportato come tempo diverso. I tempi diversi in genere rappresentano il lavoro che potrebbe verificarsi nel thread dell'interfaccia utente tra due frame consecutivi di rendering.

Quando il segmento è di grandi dimensioni

Se questo valore è alto, è probabile che la tua app presenti callback, intent o altre operazioni che dovrebbero essere in corso su un altro thread. Strumenti come il tracciamento del metodo o Systrace possono fornire visibilità sulle attività in esecuzione nel thread principale. Queste informazioni possono aiutarti a migliorare il rendimento.