Analizza con il rendering GPU del profilo

Lo strumento Profilo GPU 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 dell'app.

Questa pagina spiega brevemente cosa succede durante ciascuna 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 nel rendering della GPU del profilo. Inoltre, per capire come tutte le fasi si inseriscono l'una nell'altra, può essere utile esaminare 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

Ciascun segmento di ogni barra verticale visualizzata nel grafico Rendering GPU profilo rappresenta una fase della pipeline ed è evidenziato utilizzando 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 di rendering GPU del profilo

Una volta compreso il significato di ogni colore, puoi scegliere come target aspetti specifici della tua app per cercare di ottimizzare le prestazioni di rendering.

Fasi e relativi significati

Questa sezione spiega cosa succede durante ogni fase corrispondente a un colore nella Figura 2, nonché le cause di colli di bottiglia da tenere d'occhio.

Gestione dell'input

La fase di gestione degli input della pipeline misura il tempo trascorso dall'app a gestire gli eventi di input. Questa metrica indica il tempo impiegato dall'app per eseguire il codice chiamato a seguito dei callback degli eventi di input.

Quando questo segmento è di grandi dimensioni

I valori elevati in quest'area sono in genere il risultato di un lavoro eccessivo o troppo complesso, che si verifica all'interno dei callback eventi del gestore di input. Poiché questi callback vengono sempre eseguiti nel thread principale, le soluzioni a questo problema si concentrano sull'ottimizzazione diretta del lavoro o sul suo trasferimento a un thread diverso.

Vale anche la pena notare che RecyclerView in questa fase può essere visualizzato lo scorrimento. RecyclerView si scorre immediatamente quando consuma l'evento touch. Di conseguenza, può gonfiare o compilare le visualizzazioni dei nuovi articoli. Per questo motivo, è importante effettuare questa operazione il più rapidamente possibile. Gli strumenti di profilazione come TraceView o Systrace possono aiutarti a eseguire analisi più approfondite.

Animazione

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

Quando questo segmento è di grandi dimensioni

I valori elevati in questa area sono in genere il risultato di un'attività in esecuzione a causa di alcune modifiche alle proprietà dell'animazione. Ad esempio, un'animazione con movimento brusco, che fa scorrere ListView o RecyclerView, causa un aumento eccessivo delle visualizzazioni e della popolazione.

Misurazione/layout

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

In primo luogo, il sistema misura gli elementi della visualizzazione. Ogni visualizzazione e layout include dati specifici che descrivono le dimensioni dell'oggetto sullo schermo. Alcune viste possono avere una dimensione specifica, altre hanno una dimensione che si adatta a quelle del contenitore di layout principale

In secondo luogo, il sistema dispone gli elementi della visualizzazione. Una volta calcolate le dimensioni delle visualizzazioni dei bambini, il sistema può procedere con il layout, le dimensioni 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, fino alla vista principale.

Quando questo segmento è di grandi dimensioni

Se la tua app impiega molto tempo per frame in questa area, solitamente è a causa dell'elevato volume di visualizzazioni da visualizzare o di problemi come la doppia tassazione nel punto sbagliato della gerarchia. In entrambi i casi, la gestione del rendimento implica il miglioramento delle 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 rendimento. Traceview e Systrace possono aiutarti a esaminare le callstack per identificare i problemi del codice.

Disegna

La fase di disegno traduce le operazioni di rendering di una vista, come 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 visualizzazioni che dovevano essere aggiornate sullo schermo in 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 questo segmento è di grandi dimensioni

In termini semplificati, puoi considerare questa metrica come un indicatore del tempo impiegato per eseguire tutte le chiamate a onDraw() per ogni visualizzazione non valida. Questa misura include il tempo speso per l'invio di comandi di disegno a bambini e disegnabili che potrebbero essere presenti. Per questo motivo, quando vedi questo picco nella barra, la causa potrebbe essere che un gruppo di visualizzazioni è diventato improvvisamente non valido. L'invalidazione obbliga a rigenerare gli elenchi di visualizzazione delle visualizzazioni. In alternativa, un tempo di elaborazione elevato potrebbe essere il risultato di alcune visualizzazioni personalizzate con una logica estremamente complessa nei metodi onDraw().

Sincronizza/carica

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

In quanto 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 nella memoria GPU prima che la GPU possa eseguirne il rendering sullo schermo. La GPU memorizza nella cache il bitmap in modo che il sistema non debba nuovamente trasferire i dati, a meno che la trama non venga espulsa dalla cache della trama della GPU.

Nota: sui dispositivi Lollipop, questa fase è viola.

Quando questo segmento è di grandi dimensioni

Tutte le risorse di un frame devono risiedere nella memoria GPU prima di poter essere utilizzate per tracciare un frame. Ciò significa che un valore elevato per questa metrica può indicare un numero elevato di caricamenti di risorse di piccole dimensioni o un numero ridotto di risorse molto grandi. Un caso comune è quando un'app mostra una singola bitmap di dimensioni simili a quelle dello schermo. Un altro caso è quando un'app mostra un gran numero di miniature.

Per ridurre questa barra, puoi utilizzare tecniche come:

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

Comandi per i problemi

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

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

Questa operazione richiede del tempo, in quanto il sistema esegue la trasformazione finale e il clipping per ogni comando prima di inviarlo alla GPU. L'overhead aggiuntivo si verifica quindi sul lato GPU, che calcola i comandi finali. Questi comandi includono trasformazioni finali e ulteriori ritagliamenti.

Quando questo segmento è di grandi dimensioni

Il tempo trascorso 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 esista un piccolo costo intrinseco per ogni primitiva di disegno, potrebbe aumentare questo tempo. Ad esempio:

Kotlin

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

Java

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

è molto più costoso di:

Kotlin

canvas.drawPoints(thousandPointArray)

Java

canvas.drawPoints(thousandPointArray);

Non sempre esiste una correlazione 1:1 tra l'invio di comandi e il disegno effettivo degli 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 annunci vengono memorizzati nella cache dal sistema, ove possibile. Di conseguenza, ci sono situazioni in cui un scorrimento, una trasformazione o un'animazione richiedono al sistema di inviare di nuovo un elenco di visualizzazione, ma non di ricostruirlo effettivamente, ovvero di acquisire di nuovo i comandi di disegno da zero. Di conseguenza, puoi vedere una barra "Problemi relativi ai comandi" alta senza vedere una barra Disegna comandi alta.

Buffer di processo/swap

Una volta che Android ha finito di inviare tutto il suo elenco di visualizzazioni alla GPU, il sistema invia un comando finale per comunicare al driver di grafica che ha finito con il frame corrente. A questo punto, il conducente può finalmente presentare la nuova immagine sullo schermo.

Quando questo segmento è di grandi dimensioni

È importante capire che la GPU esegue il lavoro in parallelo con la CPU. Il sistema Android emette comandi di disegno alla GPU e poi passa all'attività successiva. La GPU legge questi comandi di disegno da una coda e li elabora.

Nelle situazioni in cui la CPU emette comandi più velocemente di quanto la GPU li consumi, la coda di comunicazione tra i processori può diventare piena. In questo caso, la CPU si blocca e attende che nella coda sia presente spazio per inserire il comando successivo. Questo stato di coda completa si verifica spesso durante la fase di scambio di buffer, perché a quel punto è stato inviato l'intero valore di comandi di un frame.

Il segreto per mitigare questo problema è ridurre la complessità del lavoro sulla GPU, in modo simile a quello che si farebbe per la fase "Issue Commands".

Vari

Oltre al tempo impiegato dal sistema di rendering per eseguire il proprio lavoro, c'è un altro insieme di operazioni che si verifica sul thread principale e non ha nulla a che fare con il rendering. Il tempo consumato da questo lavoro è riportato come tempo variegato. Il tempo varia in genere rappresenta il lavoro che potrebbe essere svolto sul thread dell'interfaccia utente tra due frame di rendering consecutivi.

Quando questo segmento è di grandi dimensioni

Se questo valore è elevato, è probabile che la tua app abbia callback, intent o altro lavoro che dovrebbe essere eseguito su un altro thread. Strumenti come Tracciamento del metodo o Systrace possono fornire visibilità sulle attività in esecuzione sul thread principale. Queste informazioni possono aiutarti a scegliere come target i miglioramenti del rendimento.