Configura tracciamento di sistema

Puoi configurare il tracciamento del sistema per acquisire un profilo di CPU e thread della tua app in un breve periodo di tempo. Poi puoi utilizzare il report di output di un tracciamento del sistema per migliorare le prestazioni del tuo gioco.

Configurare un tracciamento del sistema basato sul gioco

Lo strumento Systrace è disponibile in due modi:

Systrace è uno strumento di basso livello che:

  • Fornisce il dato di fatto. Systrace acquisisce l'output direttamente dal kernel, quindi le metriche che acquisisce sono quasi identiche a quelle che riporterebbe una serie di chiamate di sistema.
  • Consuma poche risorse. Systrace introduce un sovraccarico molto basso sul dispositivo, in genere inferiore all'1%, perché trasmette i dati in un buffer in memoria.

Impostazioni ottimali

È importante fornire allo strumento un insieme ragionevole di argomenti:

  • Categorie: il miglior insieme di categorie da attivare per una traccia di sistema basata sul gioco sono: {sched, freq, idle, am, wm, gfx, view, sync, binder_driver, hal, dalvik}.
  • Dimensione del buffer:una regola generale è che una dimensione del buffer di 10 MB per core della CPU consente una traccia di circa 20 secondi. Ad esempio, se un dispositivo ha due CPU quad-core (8 core totali), un valore appropriato da passare al programma systrace è 80.000 KB (80 MB).

    Se il gioco esegue un gran numero di cambi di contesto, aumenta il buffer a 15 MB per core della CPU.

  • Eventi personalizzati:se definisci eventi personalizzati da acquisire nel gioco, attiva il flag -a, che consente a Systrace di includere questi eventi personalizzati nel report di output.

Se utilizzi il programma a riga di comando systrace, utilizza il seguente comando per acquisire un tracciamento del sistema che applichi le best practice per il set di categorie, le dimensioni del buffer e gli eventi personalizzati:

python systrace.py -a com.example.myapp -b 80000 -o my_systrace_report.html \
  sched freq idle am wm gfx view sync binder_driver hal dalvik

Se utilizzi l'app di sistema Systrace su un dispositivo, completa i seguenti passaggi per acquisire un tracciamento del sistema che applichi le best practice per l'insieme di categorie, le dimensioni del buffer e gli eventi personalizzati:

  1. Attiva l'opzione Traccia applicazioni sottoponibili a debug.

    Per utilizzare questa impostazione, il dispositivo deve avere 256 MB o 512 MB disponibili (a seconda che la CPU abbia 4 o 8 core) e ogni blocco di memoria da 64 MB deve essere disponibile come blocco contiguo.

  2. Scegli Categorie, poi attiva le categorie nel seguente elenco:

    • am: Activity Manager
    • binder_driver: Binder Kernel driver
    • dalvik: Dalvik VM
    • freq: frequenza della CPU
    • gfx: Grafica
    • hal: Moduli hardware
    • idle: CPU inattiva
    • sched: Pianificazione della CPU
    • sync: Sincronizzazione
    • view: Visualizza sistema
    • wm: Window Manager
  3. Attiva Registra tracciamento.

  4. Carica il gioco.

  5. Esegui le interazioni nel gioco corrispondenti al gameplay di cui vuoi misurare le prestazioni del dispositivo.

  6. Subito dopo aver riscontrato un comportamento indesiderato nel gioco, disattiva la tracciatura del sistema.

Hai acquisito le statistiche sulle prestazioni necessarie per analizzare ulteriormente il problema.

Per risparmiare spazio su disco, le tracce di sistema sul dispositivo salvano i file in un formato di traccia compresso (*.ctrace). Per decomprimere questo file durante la generazione di un report, utilizza il programma a riga di comando e includi l'opzione --from-file:

python systrace.py --from-file=/data/local/traces/my_game_trace.ctrace \
  -o my_systrace_report.html

Migliorare aree di rendimento specifiche

Questa sezione mette in evidenza diversi problemi comuni di prestazioni nei giochi mobile e descrive come identificare e migliorare questi aspetti del tuo gioco.

Velocità di caricamento

I giocatori vogliono entrare nell'azione del tuo gioco il più rapidamente possibile, quindi è importante migliorare i tempi di caricamento del gioco il più possibile. Le seguenti misure di solito aiutano i tempi di caricamento:

  • Esegui il caricamento lento. Se utilizzi gli stessi asset in scene o livelli consecutivi del gioco, caricali una sola volta.
  • Riduci le dimensioni delle risorse. In questo modo, puoi raggruppare le versioni non compresse di questi asset con l'APK del tuo gioco.
  • Utilizza un metodo di compressione efficiente per il disco. Un esempio di questo metodo è zlib.
  • Utilizza IL2CPP anziché mono. (Valido solo se utilizzi Unity.) IL2CPP offre prestazioni di esecuzione migliori per gli script C#.
  • Rendi il tuo gioco multithread. Per maggiori dettagli, consulta la sezione Coerenza del frame rate.

Coerenza del frame rate

Uno degli elementi più importanti dell'esperienza di gioco è ottenere un framerate costante. Per raggiungere più facilmente questo obiettivo, segui le tecniche di ottimizzazione descritte in questa sezione.

Multi-threading

Quando sviluppi per più piattaforme, è naturale inserire tutta l'attività all'interno del gioco in un unico thread. Sebbene questo metodo di esecuzione sia semplice da implementare in molti motori grafici, non è ottimale per l'esecuzione su dispositivi Android. Di conseguenza, i giochi single-thread spesso vengono caricati lentamente e non hanno un framerate costante.

Il Systrace mostrato nella Figura 1 mostra un comportamento tipico di un gioco in esecuzione su una sola CPU alla volta:

Diagramma dei thread
all'interno di un tracciamento del sistema

Figura 1. Report Systrace per un gioco single-thread

Per migliorare le prestazioni del tuo gioco, rendilo multithreaded. In genere, il modello migliore prevede due thread:

  • Un game thread, che contiene i moduli principali del gioco e invia i comandi di rendering.
  • Un thread di rendering, che riceve i comandi di rendering e li traduce in comandi grafici che la GPU di un dispositivo può utilizzare per visualizzare una scena.

L'API Vulkan espande questo modello, data la sua capacità di eseguire il push di due buffer comuni in parallelo. Utilizzando questa funzionalità, puoi distribuire più thread di rendering su più CPU, migliorando ulteriormente il tempo di rendering di una scena.

Puoi anche apportare alcune modifiche specifiche del motore per migliorare le prestazioni multithreading del gioco:

  • Se stai sviluppando il tuo gioco utilizzando il motore grafico Unity, attiva le opzioni Rendering multithread e GPU Skinning.
  • Se utilizzi un motore di rendering personalizzato, assicurati che la pipeline dei comandi di rendering e la pipeline dei comandi grafici siano allineate correttamente. In caso contrario, potresti introdurre ritardi nella visualizzazione delle scene del gioco.

Dopo aver applicato queste modifiche, dovresti vedere il tuo gioco occupare almeno due CPU contemporaneamente, come mostrato nella Figura 2:

Diagramma dei thread
all'interno di un tracciamento del sistema

Figura 2. Report Systrace per un gioco multithread

Caricamento dell'elemento UI

Diagramma di uno stack di frame all'interno di un tracciamento del sistema
Figura 3. Report Systrace per un gioco che esegue il rendering di decine di elementi dell'interfaccia utente contemporaneamente

Quando si crea un gioco ricco di funzionalità, è allettante mostrare al giocatore molte opzioni e azioni diverse contemporaneamente. Per mantenere un frame rate costante, tuttavia, è importante considerare le dimensioni relativamente ridotte dei display dei dispositivi mobili e mantenere la UI il più semplice possibile.

Il report Systrace mostrato nella Figura 3 è un esempio di frame dell'interfaccia utente che tenta di eseguire il rendering di troppi elementi rispetto alle funzionalità di un dispositivo mobile.

Un buon obiettivo è ridurre il tempo di aggiornamento della UI a 2-3 millisecondi. Puoi ottenere aggiornamenti così rapidi eseguendo ottimizzazioni simili alle seguenti:

  • Aggiorna solo gli elementi sullo schermo che sono stati spostati.
  • Limita il numero di texture e livelli della UI. Valuta la possibilità di combinare le chiamate grafiche, come shader e texture, che utilizzano lo stesso materiale.
  • Rimanda le operazioni di animazione degli elementi alla GPU.
  • Esegui un frustum e un culling dell'occlusione più aggressivi.
  • Se possibile, esegui le operazioni di disegno utilizzando l'API Vulkan. Il sovraccarico delle chiamate di disegno è inferiore su Vulkan.

Consumo energetico

Anche dopo aver apportato le ottimizzazioni descritte nella sezione precedente, potresti notare che il framerate del gioco peggiora nei primi 45-50 minuti di gioco. Inoltre, il dispositivo potrebbe iniziare a surriscaldarsi e consumare più batteria nel tempo.

In molti casi, questo insieme indesiderabile di termiche e consumo energetico è correlato al modo in cui il carico di lavoro del gioco viene distribuito tra le CPU di un dispositivo. Per aumentare l'efficienza del consumo energetico del tuo gioco, applica le best practice mostrate nelle sezioni seguenti.

Mantenere i thread che consumano molta memoria su una sola CPU

Su molti dispositivi mobili, le cache L1 si trovano su CPU specifiche, mentre le cache L2 si trovano sul set di CPU che condividono un clock. Per massimizzare i successi della cache L1, è in genere meglio mantenere il thread principale del gioco, insieme a qualsiasi altro thread che utilizza molta memoria, in esecuzione su una singola CPU.

Rimandare il lavoro di breve durata a CPU a basso consumo

La maggior parte dei motori grafici, incluso Unity, sa come rimandare le operazioni dei thread di lavoro a una CPU diversa rispetto al thread principale del gioco. Tuttavia, il motore non conosce l'architettura specifica di un dispositivo e non può prevedere il carico di lavoro del tuo gioco come puoi fare tu.

La maggior parte dei dispositivi system-on-a-chip ha almeno due clock condivisi, uno per le CPU veloci del dispositivo e uno per le CPU lente del dispositivo. Una conseguenza di questa architettura è che, se una CPU veloce deve funzionare alla massima velocità, anche tutte le altre CPU veloci funzionano alla massima velocità.

Il report di esempio mostrato nella Figura 4 mostra un gioco che sfrutta le CPU veloci. Tuttavia, questo elevato livello di attività genera rapidamente molta energia e calore.

Diagramma dei thread
all'interno di un tracciamento del sistema

Figura 4. Report Systrace che mostra un'assegnazione non ottimale dei thread alle CPU del dispositivo

Per ridurre il consumo energetico complessivo, è consigliabile suggerire allo scheduler di rimandare all'insieme di CPU lente di un dispositivo i lavori di durata inferiore, come il caricamento dell'audio, l'esecuzione di thread di lavoro e l'esecuzione del coreografo. Trasferisci la maggior parte di questo lavoro sulle CPU lente mantenendo il frame rate desiderato.

La maggior parte dei dispositivi elenca le CPU lente prima di quelle veloci, ma non puoi presumere che il SOC del tuo dispositivo utilizzi questo ordine. Per verificare, esegui comandi simili a quelli mostrati in questo codice di rilevamento della topologia della CPU su GitHub.

Dopo aver identificato le CPU lente sul dispositivo, puoi dichiarare le affinità per i thread di breve durata, che lo scheduler del dispositivo segue. Per farlo, aggiungi il seguente codice all'interno di ogni thread:

#include <sched.h>
#include <sys/types.h>
#include <unistd.h>

pid_t my_pid; // PID of the process containing your thread.

// Assumes that cpu0, cpu1, cpu2, and cpu3 are the "slow CPUs".
cpu_set_t my_cpu_set;
CPU_ZERO(&my_cpu_set);
CPU_SET(0, &my_cpu_set);
CPU_SET(1, &my_cpu_set);
CPU_SET(2, &my_cpu_set);
CPU_SET(3, &my_cpu_set);
sched_setaffinity(my_pid, sizeof(cpu_set_t), &my_cpu_set);

Stress termico

Quando i dispositivi si surriscaldano troppo, potrebbero limitare la CPU e/o la GPU, il che può influire sui giochi in modi inaspettati. I giochi che incorporano grafica complessa, calcoli pesanti o attività di rete sostenuta hanno maggiori probabilità di riscontrare problemi.

Utilizza l'API Thermal per monitorare le variazioni di temperatura sul dispositivo e intervenire per mantenere un consumo energetico inferiore e una temperatura del dispositivo più bassa. Quando il dispositivo segnala stress termico, interrompi le attività continue per ridurre il consumo energetico. Ad esempio, riduci la frequenza fotogrammi o la tassellatura dei poligoni.

Innanzitutto, dichiara l'oggetto PowerManager e inizializzalo nel metodo onCreate(). Aggiungi un listener di stato termico all'oggetto.

Kotlin

class MainActivity : AppCompatActivity() {
    lateinit var powerManager: PowerManager

    override fun onCreate(savedInstanceState: Bundle?) {
        powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
        powerManager.addThermalStatusListener(thermalListener)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    PowerManager powerManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        powerManager.addThermalStatusListener(thermalListener);
    }
}

Definisci le azioni da intraprendere quando il listener rileva una modifica dello stato. Se il tuo gioco utilizza C/C++, aggiungi codice ai livelli di stato termico in onThermalStatusChanged() per chiamare il codice di gioco nativo utilizzando JNI o utilizza l'API Thermal nativa.

Kotlin

val thermalListener = object : PowerManager.OnThermalStatusChangedListener() {
    override fun onThermalStatusChanged(status: Int) {
        when (status) {
            PowerManager.THERMAL_STATUS_NONE -> {
                // No thermal status, so no action necessary
            }

            PowerManager.THERMAL_STATUS_LIGHT -> {
                // Add code to handle light thermal increase
            }

            PowerManager.THERMAL_STATUS_MODERATE -> {
                // Add code to handle moderate thermal increase
            }

            PowerManager.THERMAL_STATUS_SEVERE -> {
                // Add code to handle severe thermal increase
            }

            PowerManager.THERMAL_STATUS_CRITICAL -> {
                // Add code to handle critical thermal increase
            }

            PowerManager.THERMAL_STATUS_EMERGENCY -> {
                // Add code to handle emergency thermal increase
            }

            PowerManager.THERMAL_STATUS_SHUTDOWN -> {
                // Add code to handle immediate shutdown
            }
        }
    }
}

Java

PowerManager.OnThermalStatusChangedListener thermalListener =
    new PowerManager.OnThermalStatusChangedListener () {

    @Override
    public void onThermalStatusChanged(int status) {

        switch (status)
        {
            case PowerManager.THERMAL_STATUS_NONE:
                // No thermal status, so no action necessary
                break;

            case PowerManager.THERMAL_STATUS_LIGHT:
                // Add code to handle light thermal increase
                break;

            case PowerManager.THERMAL_STATUS_MODERATE:
                // Add code to handle moderate thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SEVERE:
                // Add code to handle severe thermal increase
                break;

            case PowerManager.THERMAL_STATUS_CRITICAL:
                // Add code to handle critical thermal increase
                break;

            case PowerManager.THERMAL_STATUS_EMERGENCY:
                // Add code to handle emergency thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SHUTDOWN:
                // Add code to handle immediate shutdown
                break;
        }
    }
};

Latenza dal tocco al display

I giochi che eseguono il rendering dei frame il più rapidamente possibile creano uno scenario in cui la GPU è vincolata, in cui il frame buffer diventa troppo pieno. La CPU deve attendere la GPU, il che causa un ritardo notevole tra l'input di un giocatore e l'applicazione dell'input sullo schermo.

Per determinare se puoi migliorare il frame pacing del tuo gioco, completa i seguenti passaggi:

  1. Genera un report Systrace che includa le categorie gfx e input. Queste categorie comprendono misurazioni particolarmente utili per determinare la latenza dal tocco alla visualizzazione.
  2. Controlla la sezione SurfaceView di un report Systrace. Un buffer troppo pieno fa sì che il numero di estrazioni del buffer in attesa oscilli tra 1 e 2, come mostrato nella Figura 5:

    Diagramma della
coda di buffer all'interno di un tracciamento del sistema

    Figura 5. Report Systrace che mostra un buffer troppo pieno che periodicamente è troppo pieno per accettare comandi di disegno

Per mitigare questa incoerenza nel pacing dei frame, completa le azioni descritte nelle sezioni seguenti:

Integrare l'API Android Frame Pacing nel gioco

L'API Android Frame Pacing ti aiuta a eseguire scambi di frame e definire un intervallo di scambio in modo che il gioco mantenga una frequenza fotogrammi più coerente.

Riduci la risoluzione degli asset non UI del gioco

I display dei dispositivi mobili moderni contengono molti più pixel di quanti un lettore possa elaborare, quindi è accettabile eseguire il downsampling in modo che una sequenza di 5 o anche 10 pixel contenga un solo colore. Data la struttura della maggior parte delle cache display, è meglio ridurre la risoluzione solo lungo una dimensione.

Tuttavia, non ridurre la risoluzione degli elementi dell'interfaccia utente del gioco. È importante preservare lo spessore della linea di questi elementi per mantenere un touch target sufficientemente grande per tutti i tuoi giocatori.

Fluidità del rendering

Quando SurfaceFlinger si aggancia a un buffer di visualizzazione per mostrare una scena nel gioco, l'attività della CPU aumenta momentaneamente. Se questi picchi di attività della CPU si verificano in modo irregolare, è possibile che il gioco subisca dei rallentamenti. Il diagramma nella Figura 6 mostra il motivo per cui si verifica questo problema:

Diagramma dei frame
a cui manca una finestra Vsync perché hanno iniziato a disegnare troppo tardi

Figura 6. Report Systrace che mostra come un frame può perdere un Vsync

Se un frame inizia il rendering troppo tardi, anche di pochi millisecondi, potrebbe non rientrare nella finestra di visualizzazione successiva. Il frame deve quindi attendere la successiva sincronizzazione verticale per essere visualizzato (33 millisecondi quando si esegue un gioco a 30 FPS), il che causa un ritardo notevole dal punto di vista del giocatore.

Per risolvere questo problema, utilizza l'API Android Frame Pacing, che presenta sempre un nuovo frame su un fronte d'onda VSync.

Stato memoria

Quando esegui il gioco per un periodo di tempo prolungato, è possibile che il dispositivo riscontri errori di memoria insufficiente.

In questa situazione, controlla l'attività della CPU in un report Systrace e verifica la frequenza con cui il sistema effettua chiamate al daemon kswapd. Se ci sono molte chiamate durante l'esecuzione del gioco, è meglio esaminare più da vicino il modo in cui il gioco gestisce e pulisce la memoria.

Per saperne di più, consulta Informazioni sulla gestione della memoria.

Stato del thread

Quando navighi tra gli elementi tipici di un report Systrace, puoi visualizzare la quantità di tempo che un determinato thread ha trascorso in ogni possibile stato del thread selezionando il thread all'interno del report, come mostrato nella Figura 7:

Diagramma di un
report Systrace

Figura 7. Report Systrace che mostra come la selezione di un thread fa sì che il report visualizzi un riepilogo dello stato per quel thread

Come mostrato nella figura 7, potresti notare che i thread del tuo gioco non si trovano nello stato "in esecuzione" o "eseguibile" con la frequenza prevista. Il seguente elenco mostra diversi motivi comuni per cui un determinato thread potrebbe passare periodicamente a uno stato insolito:

  • Se un thread è inattivo per un periodo di tempo prolungato, potrebbe essere in attesa di un blocco o di attività della GPU.
  • Se un thread è costantemente bloccato sull'I/O, stai leggendo troppi dati dal disco alla volta o il gioco sta eseguendo un thrashing.

Risorse aggiuntive

Per scoprire di più su come migliorare le prestazioni del tuo gioco, consulta le seguenti risorse aggiuntive:

Video