Utilizza Simpleperf per valutare le prestazioni di un dispositivo. Simpleperf è uno strumento di profilazione nativo per app e processi nativi su Android. Utilizza CPU Profiler per esaminare l'utilizzo della CPU dell'app e l'attività dei thread in tempo reale.
Esistono due indicatori di rendimento visibili agli utenti:
- Prestazioni prevedibili e percepibili. L'interfaccia utente (UI) perde frame o viene visualizzata in modo coerente a 60 FPS? L'audio viene riprodotto senza artefatti o scoppiettii? Qual è il ritardo tra il momento in cui l'utente tocca lo schermo e il momento in cui l'effetto viene visualizzato sul display?
- Periodo di tempo necessario per operazioni più lunghe (ad esempio apertura di app).
Il primo è più evidente del secondo. In genere gli utenti notano il jank, ma non saranno in grado di distinguere un tempo di avvio dell'app di 500 ms da uno di 600 ms a meno che non stiano guardando due dispositivi uno accanto all'altro. La latenza del tocco è immediatamente percepibile e contribuisce in modo significativo alla percezione di un dispositivo.
Di conseguenza, in un dispositivo veloce, la pipeline dell'interfaccia utente è la cosa più importante del sistema, a parte ciò che è necessario per mantenerla funzionale. Ciò significa che la pipeline dell'interfaccia utente deve dare la precedenza a qualsiasi altro lavoro non necessario per un'interfaccia utente fluida. Per mantenere un'interfaccia utente fluida, la sincronizzazione in background, la distribuzione delle notifiche e attività simili devono essere ritardate se è possibile eseguire il lavoro dell'interfaccia utente. È accettabile scambiare le prestazioni di operazioni più lunghe (runtime HDR+, avvio dell'app e così via) per mantenere un'interfaccia utente fluida.
Capacità e jitter
Quando si valuta il rendimento del dispositivo, capacità e jitter sono due metriche significative.
Capacità
La capacità è la quantità totale di una risorsa che il dispositivo possiede in un determinato periodo di tempo. Possono essere risorse CPU, GPU, I/O, di rete, larghezza di banda della memoria o qualsiasi metrica simile. Quando esamini il rendimento dell'intero sistema, può essere utile astrarre i singoli componenti e presupporre una singola metrica che determina il rendimento (soprattutto quando metti a punto un nuovo dispositivo perché i carichi di lavoro eseguiti su quel dispositivo sono probabilmente fissi).
La capacità di un sistema varia in base alle risorse di computing online. La modifica della frequenza della CPU/GPU è il mezzo principale per modificare la capacità, ma ce ne sono altri, come la modifica del numero di core della CPU online. Di conseguenza, la capacità di un sistema corrisponde al consumo energetico; la modifica della capacità comporta sempre una variazione simile del consumo energetico.
La capacità richiesta in un determinato momento è determinata in modo schiacciante dall'app in esecuzione. Di conseguenza, la piattaforma può fare poco per regolare la capacità richiesta per un determinato carico di lavoro e i mezzi per farlo sono limitati ai miglioramenti del runtime (framework Android, ART, Bionic, compilatore/driver GPU, kernel).
Tremolio
Sebbene la capacità richiesta per un workload sia facile da vedere, il jitter è un concetto più nebuloso. Per una buona introduzione al jitter come impedimento per i sistemi veloci, ti consigliamo di leggere l'articolo intitolato The Case of the Missing Supercomputer Performance: Achieving Optimal Performance on the 8,192 processors of ASCI Q. (Si tratta di un'indagine sul motivo per cui il supercomputer ASCI Q non ha raggiunto le prestazioni previste ed è un'ottima introduzione all'ottimizzazione di sistemi di grandi dimensioni).
Questa pagina utilizza il termine tremolio per descrivere ciò che l'articolo ASCI Q chiama rumore. Il jitter è il comportamento casuale del sistema che impedisce l'esecuzione di un lavoro percepibile. Spesso si tratta di un lavoro che deve essere eseguito, ma potrebbe non avere requisiti di tempistica rigorosi che ne determinano l'esecuzione in un momento particolare. Poiché è casuale, è estremamente difficile confutare l'esistenza di jitter per un determinato carico di lavoro. Inoltre, è estremamente difficile dimostrare che una fonte nota di jitter sia stata la causa di un particolare problema di prestazioni. Gli strumenti più comunemente utilizzati per diagnosticare le cause del jitter (come il tracciamento o la registrazione) possono introdurre il proprio jitter.
Le fonti di jitter riscontrate nelle implementazioni reali di Android includono:
- Ritardo scheduler
- Gestori di interruzioni
- Il codice del driver è in esecuzione da troppo tempo con la preemption o gli interrupt disattivati
- Softirq a lunga esecuzione
- Conflitto blocco (app, framework, driver del kernel, blocco binder, blocco mmap)
- Contesa del descrittore del file in cui un thread a bassa priorità mantiene il blocco su un file, impedendo l'esecuzione di un thread ad alta priorità
- Esecuzione di codice critico per la UI in workqueue in cui potrebbe essere ritardato
- Transizioni di inattività della CPU
- Logging
- Ritardi I/O
- Creazione di processi non necessari (ad esempio, trasmissioni
CONNECTIVITY_CHANGE) - Thrashing della cache di pagine causato da memoria libera insufficiente
La quantità di tempo richiesta per un determinato periodo di jitter può diminuire o meno all'aumentare della capacità. Ad esempio, se un driver lascia gli interrupt disattivati mentre attende una lettura da un bus i2c, impiegherà un periodo di tempo fisso indipendentemente dal fatto che la CPU sia a 384 MHz o 2 GHz. Aumentare la capacità non è una soluzione fattibile per migliorare le prestazioni quando è coinvolto il jitter. Di conseguenza, processori più veloci di solito non migliorano le prestazioni in situazioni con vincoli di jitter.
Infine, a differenza della capacità, il jitter è quasi interamente di competenza del fornitore del sistema.
Consumo di memoria
Il consumo di memoria è tradizionalmente considerato la causa delle prestazioni scarse. Sebbene il consumo in sé non sia un problema di prestazioni, può causare jitter tramite l'overhead di LowMemoryKiller, i riavvii del servizio e l'eccessivo utilizzo della cache delle pagine. La riduzione del consumo di memoria può evitare le cause dirette di prestazioni scarse, ma potrebbero esserci altri miglioramenti mirati che evitano anche queste cause (ad esempio, il blocco del framework per impedirne la paginazione quando verrà paginato poco dopo).
Analizzare il rendimento iniziale del dispositivo
Partire da un sistema funzionale ma con prestazioni scarse e tentare di correggere il comportamento del sistema esaminando i singoli casi di prestazioni scarse visibili agli utenti non è una strategia valida. Poiché il rendimento scarso di solito non è facilmente riproducibile (ovvero jitter) o un problema dell'app, troppe variabili nel sistema completo impediscono l'efficacia di questa strategia. Di conseguenza, è molto facile identificare erroneamente le cause e apportare miglioramenti minori, perdendo opportunità sistemiche per risolvere i problemi di rendimento in tutto il sistema.
Utilizza invece il seguente approccio generale quando configuri un nuovo dispositivo:
- Avvia il sistema nell'interfaccia utente con tutti i driver in esecuzione e alcune impostazioni di base del governor della frequenza (se modifichi le impostazioni del governor della frequenza, ripeti tutti i passaggi riportati di seguito).
- Assicurati che il kernel supporti il punto di traccia
sched_blocked_reasone altri punti di traccia nella pipeline del display che indicano quando il frame viene inviato al display. - Acquisisci tracce lunghe dell'intera pipeline dell'interfaccia utente (dalla ricezione dell'input tramite un IRQ alla scansione finale) durante l'esecuzione di un workload leggero e coerente (ad esempio, UiBench o il test della palla in TouchLatency).
- Correggi i cali di frame rilevati nel workload leggero e coerente.
- Ripeti i passaggi 3-4 finché non riesci a correre senza frame persi per almeno 20 secondi alla volta.
- Passa ad altre origini di jank visibili all'utente.
Altre semplici operazioni che puoi eseguire all'inizio della configurazione del dispositivo includono:
- Assicurati che il kernel abbia la patch tracepoint sched_blocked_reason. Questo punto di traccia è abilitato con la categoria di traccia sched in systrace e fornisce la funzione responsabile del riposo quando il thread entra in modalità di sospensione non interrompibile. È fondamentale per l'analisi delle prestazioni perché il sonno ininterrotto è un indicatore molto comune di jitter.
- Assicurati di avere una traccia sufficiente per le pipeline GPU e display. Sui recenti SOC Qualcomm, i tracepoint vengono attivati utilizzando:
adb shell "echo 1 > /d/tracing/events/kgsl/enable"adb shell "echo 1 > /d/tracing/events/mdss/enable"
Questi eventi rimangono attivi quando esegui systrace, in modo da poter visualizzare ulteriori
informazioni nella traccia sulla pipeline di visualizzazione (MDSS) nella
sezione mdss_fb0. Sui SoC Qualcomm, non vedrai ulteriori
informazioni sulla GPU nella visualizzazione standard di systrace, ma i risultati sono
presenti nella traccia stessa (per i dettagli, vedi
Informazioni su
systrace).
Ciò che vuoi da questo tipo di tracciamento del display è un singolo evento che indichi direttamente che un frame è stato inviato al display. Da qui, puoi determinare se hai raggiunto correttamente la durata frame.Se l'evento Xn si verifica meno di 16,7 ms dopo l'evento Xn-1 (supponendo un display a 60 Hz), allora sai che non si è verificato jank. Se il tuo SOC non fornisce questi segnali, collabora con il tuo fornitore per ottenerli. Il debug del jitter è estremamente difficile senza un segnale definitivo di completamento del frame.
Utilizzare benchmark sintetici
I benchmark sintetici sono utili per garantire la presenza della funzionalità di base di un dispositivo. Tuttavia, considerare i benchmark come proxy per le prestazioni percepite del dispositivo non è utile.
In base all'esperienza con i SoC, le differenze nel rendimento dei benchmark sintetici tra i SoC non sono correlate a una differenza simile nel rendimento percepibile dell'interfaccia utente (numero di frame persi, tempo di frame al 99° percentile e così via). I benchmark sintetici sono benchmark solo di capacità; il jitter influisce sul rendimento misurato di questi benchmark solo sottraendo tempo all'operazione bulk del benchmark. Di conseguenza, i punteggi dei benchmark sintetici sono per lo più irrilevanti come metrica delle prestazioni percepite dall'utente.
Considera due SOC che eseguono il benchmark X che esegue il rendering di 1000 frame dell'interfaccia utente e riporta il tempo di rendering totale (un punteggio inferiore è migliore).
- SOC 1 esegue il rendering di ogni frame di Benchmark X in 10 ms e ottiene un punteggio di 10.000.
- SOC 2 esegue il rendering del 99% dei frame in 1 ms,ma dell'1% dei frame in 100 ms e ottiene un punteggio di 19.900, un punteggio notevolmente migliore.
Se il benchmark è indicativo delle prestazioni effettive della UI, SOC 2 sarebbe inutilizzabile. Supponendo una frequenza di aggiornamento di 60 Hz, SOC 2 avrebbe un frame instabile ogni 1,5 secondi di funzionamento. Nel frattempo, SOC 1 (il SOC più lento secondo Benchmark X) sarebbe perfettamente fluido.
Utilizzare le segnalazioni di bug
A volte i report sui bug sono utili per l'analisi delle prestazioni, ma poiché sono molto pesanti, raramente sono utili per il debug di problemi di jank sporadici. Possono fornire alcuni suggerimenti su cosa stava facendo il sistema in un determinato momento, soprattutto se il jank si è verificato durante una transizione dell'app (che viene registrata in una segnalazione di bug). I report sui bug possono anche indicare quando qualcosa non va nel sistema e potrebbe ridurne la capacità effettiva (ad esempio, limitazione termica o frammentazione della memoria).
Utilizzare TouchLatency
Diversi esempi di prestazioni scadenti provengono da TouchLatency, che è il
carico di lavoro periodico preferito utilizzato per Pixel e Pixel XL. È disponibile all'indirizzo
frameworks/base/tests/TouchLatency e ha due modalità: latenza del tocco
e palla che rimbalza (per cambiare modalità, fai clic sul pulsante nell'angolo in alto a destra).
Il test della palla che rimbalza è semplice come sembra: una palla rimbalza sullo schermo all'infinito, indipendentemente dall'input dell'utente. In genere è anche di gran lunga il test più difficile da eseguire perfettamente, ma più si avvicina all'esecuzione senza frame persi, migliori saranno le prestazioni del dispositivo. Il test della palla che rimbalza è difficile perché è un carico di lavoro banale ma perfettamente coerente che viene eseguito a una frequenza di clock molto bassa (si presuppone che il dispositivo abbia un governor di frequenza; se il dispositivo viene invece eseguito con clock fissi, riduci la frequenza della CPU/GPU a un valore vicino al minimo quando esegui il test della palla che rimbalza per la prima volta). Man mano che il sistema si spegne e gli orologi si avvicinano all'inattività, il tempo CPU/GPU richiesto per frame aumenta. Puoi guardare la palla e vedere le cose che si muovono a scatti e potrai vedere anche i frame mancanti in systrace.
Poiché il workload è molto coerente, puoi identificare la maggior parte delle fonti di jitter molto più facilmente rispetto alla maggior parte dei workload visibili agli utenti monitorando esattamente ciò che viene eseguito sul sistema durante ogni frame mancante anziché la pipeline dell'interfaccia utente. I clock più bassi amplificano gli effetti del jitter, rendendo più probabile che qualsiasi jitter causi un frame perso. Di conseguenza, più TouchLatency si avvicina a 60 FPS, meno è probabile che si verifichino comportamenti anomali del sistema che causano jank sporadici e difficili da riprodurre nelle app più grandi.
Poiché il jitter è spesso (ma non sempre) invariante alla velocità di clock, utilizza un test che venga eseguito a velocità di clock molto basse per diagnosticare il jitter per i seguenti motivi:
- Non tutto il jitter è invariante alla velocità di clock; molte fonti consumano solo tempo della CPU.
- Il governor dovrebbe avvicinare la durata media del frame alla scadenza riducendo la velocità di clock, in modo che il tempo trascorso a eseguire operazioni non UI possa superare il limite e causare l'eliminazione di un frame.