Configura tracciamento di sistema

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

Configura una traccia di sistema basata sul gioco

Lo strumento Systrace è disponibile in due modi:

Systrace è uno strumento di basso livello che:

  • Fornisce dati empirici reali. Systrace acquisisce l'output direttamente dal kernel, quindi le metriche che acquisisce sono quasi identiche a quelle segnalate da una serie di chiamate di sistema.
  • Consuma poche risorse. Systrace genera un overhead molto basso sul dispositivo, di solito inferiore all'1%, perché trasmette il flusso di 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 basato su giochi è: {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 in totale), un valore appropriato da passare al programma systrace è 80.000 kB (80 MB).

    Se il tuo gioco esegue molte operazioni di cambio di contesto, aumenta il buffer a 15 MB per core CPU.

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

Se usi il programma a riga di comando systrace, usa il seguente comando per acquisire una traccia del sistema che applichi le best practice per set di categorie, dimensioni del buffer ed 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 una traccia del sistema che applichi le best practice per set di categorie, dimensioni del buffer ed eventi personalizzati:

  1. Attiva l'opzione Traccia applicazioni di cui è possibile eseguire il debug.

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

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

    • am: Gestore attività
    • binder_driver: driver del kernel binnder
    • dalvik: Dalvik VM
    • freq: frequenza CPU
    • gfx: grafica
    • hal: moduli hardware
    • idle: CPU inattiva
    • sched: pianificazione della CPU
    • sync: sincronizzazione
    • view: visualizza sistema
    • wm: gestore di finestre
  3. Attiva Tracciamento dei record.

  4. Carica il gioco.

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

  6. Poco dopo aver riscontrato comportamenti indesiderati nel gioco, disattiva il tracciamento del sistema.

Hai acquisito le statistiche sul rendimento necessarie per analizzare ulteriormente il problema.

Per risparmiare spazio su disco, le tracce del 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 specifiche aree di rendimento

Questa sezione evidenzia diversi problemi comuni relativi al rendimento dei giochi mobile e descrive come identificare e migliorare questi aspetti del gioco.

Velocità di caricamento

I giocatori vogliono entrare in azione il più rapidamente possibile, quindi è importante migliorare il più possibile i tempi di caricamento del gioco. In genere, le misure che seguono consentono di migliorare i tempi di caricamento:

  • Esegui il caricamento lento. Se utilizzi gli stessi asset in scene o livelli consecutivi nel gioco, carica questi asset una sola volta.
  • Riduci le dimensioni degli asset. In questo modo puoi raggruppare le versioni non compresse di questi asset nell'APK del gioco.
  • Utilizza un metodo di compressione efficiente su disco. Un esempio di questo metodo è zlib.
  • Utilizza IL2CPP anziché mono. Si applica solo se utilizzi Unity. IL2CPP offre migliori prestazioni di esecuzione per gli script C#.
  • Rendi il tuo gioco multithread. Per maggiori dettagli, consulta la sezione sulla coerenza della frequenza frame.

Coerenza della frequenza fotogrammi

Uno degli elementi più importanti dell'esperienza di gameplay è il raggiungimento di una frequenza fotogrammi coerente. Per semplificare il raggiungimento dell'obiettivo, segui le tecniche di ottimizzazione illustrate in questa sezione.

Multi-threading

Quando sviluppi per più piattaforme, è naturale inserire tutte le attività all'interno del gioco in un unico thread. Sebbene questo metodo di esecuzione sia semplice da implementare in molti motori di gioco, non è ottimale se eseguito su dispositivi Android. Di conseguenza, i giochi a thread singolo spesso vengono caricati lentamente e non hanno una frequenza fotogrammi coerente.

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

Diagramma di thread
in una traccia di sistema

Figura 1. Report Systrace per un gioco con un unico thread

Per migliorare le prestazioni del tuo gioco, imposta il gioco in modalità multithread. In genere, il modello migliore prevede 2 thread:

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

L'API Vulkan amplia questo modello, data la sua capacità di eseguire il push di 2 buffer comuni in parallelo. Con questa funzione 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 del multithreading del tuo gioco:

  • Se stai sviluppando il tuo gioco utilizzando il motore di gioco Unity, attiva le opzioni Rendering multithread e Skinning GPU.
  • Se usi un motore di rendering personalizzato, assicurati che la pipeline del comando di rendering e la pipeline dei comandi per la grafica siano allineate correttamente; in caso contrario, potrebbero verificarsi ritardi nella visualizzazione delle scene del gioco.

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

Diagramma di thread
in una traccia di sistema

Figura 2. Report Systrace per un gioco multi-thread

Caricamento elemento UI

Diagramma di uno stack di frame all'interno di una traccia di sistema
Figura 3. Report Systrace relativo a un gioco che visualizza contemporaneamente decine di elementi dell'interfaccia utente

Quando crei un gioco ricco di funzionalità, si ha la tentazione di mostrare contemporaneamente al giocatore tante opzioni e azioni diverse. Tuttavia, per mantenere una frequenza fotogrammi coerente, è importante considerare le dimensioni relativamente ridotte dei display per dispositivi mobili e mantenere l'interfaccia utente il più semplice possibile.

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

Un buon obiettivo è ridurre il tempo di aggiornamento dell'interfaccia utente 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 dell'interfaccia utente. Prova a combinare chiamate grafiche, ad esempio mesh e texture, che utilizzano lo stesso materiale.
  • Rimanda le operazioni di animazione degli elementi alla GPU.
  • Eseguire un culling più aggressivo del tronco e dell'occlusione.
  • Se possibile, esegui operazioni di disegno utilizzando l'API Vulkan. L'overhead della chiamata di disegno è più basso su Vulkan.

Consumo energetico

Anche dopo aver effettuato le ottimizzazioni discusse nella sezione precedente, potresti notare che la frequenza fotogrammi del gioco si deteriora entro i primi 45-50 minuti del gameplay. Inoltre, il dispositivo potrebbe iniziare a riscaldarsi e a consumare più batteria nel tempo.

In molti casi, questo insieme indesiderato di temperature e consumo energetico è correlato al modo in cui il carico di lavoro del gioco è 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.

Mantieni i thread che consumano molta memoria su un'unica CPU

Su molti dispositivi mobili, le cache L1 risiedono su CPU specifiche, mentre le cache L2 si trovano su un insieme di CPU che condividono un clock. Per massimizzare i successi della cache L1, in genere è preferibile mantenere in esecuzione il thread principale del gioco, insieme a eventuali altri thread con memoria elevata, su una singola CPU.

Rimanda il lavoro di breve durata a CPU con consumo inferiore

La maggior parte dei motori di gioco, incluso Unity, sa che deve rimandare le operazioni dei thread di worker su una CPU diversa rispetto al thread principale del gioco. Tuttavia, il motore non è a conoscenza dell'architettura specifica di un dispositivo e non può prevedere come te il carico di lavoro del tuo gioco.

La maggior parte dei dispositivi system-on-a-chip ha almeno 2 orologi 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 operano alla massima velocità.

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

Diagramma di thread
in una traccia di sistema

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

Per ridurre il consumo energetico complessivo, è meglio suggerire allo scheduler che il lavoro di durata inferiore, come il caricamento dell'audio, l'esecuzione di thread worker ed l'esecuzione del coreografo, deve essere differito al set di CPU lente su un dispositivo. Trasferisci il più possibile questo lavoro sulle CPU lente, mantenendo la frequenza fotogrammi desiderata.

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

Dopo aver scoperto quali CPU sono le CPU lente sul tuo dispositivo, puoi dichiarare le affinità per i thread di breve durata, come spiegato dallo scheduler del dispositivo. 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, possono limitare la CPU e/o la GPU, con ripercussioni sui giochi in modi imprevisti. I giochi che includono grafica complessa, elaborazione intensiva o attività di rete prolungata hanno maggiori probabilità di riscontrare problemi.

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

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 eseguire quando il listener rileva una modifica dello stato. Se il gioco utilizza C/C++, aggiungi codice ai livelli di stato termico in onThermalStatusChanged() per chiamare il codice nativo del tuo gioco utilizzando JNI oppure usa 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 touch-to-display

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

Per determinare se è possibile migliorare il pacing del frame del gioco, svolgi i passaggi che seguono:

  1. Genera un report di Systrace che includa le categorie gfx e input. Queste categorie comprendono misurazioni particolarmente utili per determinare la latenza touch-to-display.
  2. Controlla la sezione SurfaceView di un report di Systrace. Un buffer overstuffed fa sì che il numero di buffer in attesa oscilli tra 1 e 2, come mostrato nella Figura 5:

    Diagramma della coda del buffer
all&#39;interno di una traccia di sistema

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

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

Integra l'API Android Frame Pacing nel tuo gioco

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

Ridurre la risoluzione delle risorse non UI del gioco

I display dei dispositivi mobili moderni contengono molti più pixel di quelli che possono essere elaborati da un player, quindi è corretto eseguire il downgrade in modo che un'esecuzione di 5 o anche 10 pixel contenga un solo colore. Data la struttura della maggior parte delle cache display, è preferibile ridurre la risoluzione in base a una sola dimensione.

Tuttavia, non ridurre la risoluzione degli elementi dell'interfaccia utente del gioco. È importante preservare lo spessore della linea su questi elementi per mantenere dimensioni del touch target sufficientemente grandi per tutti i tuoi player.

Uniformità del rendering

Quando SurfaceFlinger si blocca su un buffer del display per mostrare una scena nel gioco, l'attività della CPU aumenta temporaneamente. Se questi picchi nell'attività della CPU si verificano in modo non uniforme, è possibile che si verifichino delle interruzioni nel gioco. Il diagramma nella Figura 6 illustra il motivo per cui ciò si verifica:

Diagramma dei frame senza finestra Vsync perché hanno iniziato a disegnare troppo tardi

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

Se un frame inizia a disegnare troppo tardi, anche di pochi millisecondi, potrebbe non vedere la finestra di visualizzazione successiva. Il frame deve quindi attendere fino alla visualizzazione del Vsync successivo (33 millisecondi quando si esegue un gioco a 30 FPS), il che causa un ritardo notevole dal punto di vista del giocatore.

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

Stato memoria

Quando il gioco viene eseguito per un lungo periodo di tempo, potrebbero verificarsi errori di memoria insufficiente sul dispositivo.

In questo caso, controlla l'attività della CPU in un report di Systrace per vedere con quale frequenza il sistema effettua chiamate al daemon kswapd. Se durante l'esecuzione del gioco ci sono molte chiamate, è meglio dare un'occhiata più da vicino al modo in cui il gioco gestisce e cancella la memoria.

Per saperne di più, vedi Gestire in modo efficace la memoria nei giochi.

Stato thread

Quando esplori gli elementi tipici di un report Systrace, puoi visualizzare la quantità di tempo trascorsa da un determinato thread in ogni possibile stato dei thread selezionando il thread all'interno del report, come mostrato nella Figura 7:

Diagramma di un report di Systrace

Figura 7. Report Systrace che mostra come la selezione di un thread determina la visualizzazione nel report di un riepilogo dello stato per quel thread.

Come mostra la Figura 7, potresti scoprire che i thread del tuo gioco non sono nello stato "in esecuzione" o "eseguibile" tutte le volte che dovrebbero. Il seguente elenco mostra diversi motivi comuni per cui un determinato thread potrebbe passare periodicamente a uno stato insolito:

  • Se un thread rimane in stato di sospensione per un periodo di tempo prolungato, potrebbe essere interessato da un conflitto di blocchi o da attesa dell'attività della GPU.
  • Se un thread è costantemente bloccato all'I/O, significa che stai leggendo troppi dati dal disco alla volta o che il tuo gioco è in fase di thrapping.

Risorse aggiuntive

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

Video