Profilazione del gioco WebGL con il flag about:tracing

Lilli Thompson
Lilli Thompson

Se non riesci a misurarla, non puoi migliorarla.

Lord Kelvin

Per far funzionare i tuoi giochi HTML5 più velocemente, devi prima individuare i colli di bottiglia delle prestazioni, ma questo può essere difficile. La valutazione dei dati f/s (frame al secondo) è un inizio, ma per avere un quadro completo, devi cogliere le sfumature delle attività di Chrome.

Lo strumento about:tracing fornisce informazioni utili per evitare soluzioni alternative frettolose volte a migliorare le prestazioni, ma che sono essenzialmente congetture ben intenzionali. Puoi risparmiare molto tempo ed energia, ottenere un quadro più chiaro di ciò che sta facendo Chrome con ogni frame e utilizzare queste informazioni per ottimizzare il tuo gioco.

Ciao su:tracciamento

Lo strumento di tracciamento di Chrome ti offre una finestra su tutte le attività di Chrome in un periodo di tempo con un livello di granularità tale da far sembrare che all'inizio potresti sembrare complesso. Molte delle funzioni di Chrome sono strumentate per il tracciamento immediato, quindi senza alcuna strumentazione manuale puoi comunque utilizzare about:tracing per monitorare le tue prestazioni. (Vedi una sezione successiva sulla strumentazione manuale di JS)

Per attivare la visualizzazione di tracciamento, digita "about:tracing" nella omnibox (barra degli indirizzi) di Chrome.

omnibox di Chrome
Digita "about:tracing" nella omnibox di Chrome

Dallo strumento di tracciamento, puoi iniziare a registrare, eseguire il gioco per alcuni secondi e visualizzare i dati della traccia. Ecco un esempio di come potrebbero presentarsi i dati:

Risultato di tracciamento semplice
Risultato del tracciamento semplice

Esatto, non è un problema. Parliamo di come leggerlo.

Ogni riga rappresenta un processo che viene profilato, l'asse sinistro-destra indica il tempo e ogni casella colorata rappresenta una chiamata di funzione instrumentata. Sono presenti righe per una serie di diversi tipi di risorse. Quelli più interessanti per la profilazione dei giochi sono CrGpuMain, che mostra cosa sta facendo la Graphics Processing Unit (GPU), e CrRendererMain. Ogni traccia contiene righe CrRendererMain per ogni scheda aperta durante il periodo di traccia (inclusa la scheda about:tracing stessa).

Durante la lettura dei dati di traccia, la tua prima attività è determinare quale riga di CrRendererMain corrisponde al tuo gioco.

Risultato di tracciamento semplice evidenziato
Risultato del tracciamento semplice evidenziato

In questo esempio i due candidati sono: 2216 e 6516. Purtroppo al momento non esiste un modo elegante per scegliere la tua applicazione, se non per cercare la riga che viene sottoposta a molti aggiornamenti periodici (o se hai instrumentato manualmente il codice con i punti di traccia, per cercare la riga che contiene i dati di traccia). In questo esempio, sembra che 6516 stia eseguendo un loop principale dalla frequenza degli aggiornamenti. Se chiudi tutte le altre schede prima di iniziare la traccia, sarà più facile trovare il CrRendererMain corretto. Tuttavia, potrebbero esserci ancora righe CrRendererMain per processi diversi dal tuo gioco.

Trovare la cornice

Dopo aver individuato la riga corretta nello strumento di tracciamento del tuo gioco, il passaggio successivo consiste nel trovare il ciclo principale. Il loop principale appare come un pattern ricorrente nei dati di tracciamento. Puoi spostarti tra i dati di tracciamento utilizzando i tasti W, A, S e D: A e D per spostarti a sinistra o destra (avanti e indietro nel tempo) e W e S per aumentare e diminuire lo zoom dei dati. Ci aspetteresti che il tuo ciclo principale sia uno schema che si ripete ogni 16 millisecondi se il gioco viene eseguito a 60 Hz.

Sembra che ci siano tre frame di esecuzione
Sono visualizzati tre frame di esecuzione

Una volta individuato il battito cardiaco del tuo gioco, puoi capire in dettaglio cosa sta facendo esattamente il codice in ogni frame. Utilizza W, A, S, D per aumentare lo zoom finché non riesci a leggere il testo nelle caselle funzione.

Profondo in un frame di esecuzione
Dettaglio del frame di esecuzione

Questa raccolta di caselle mostra una serie di chiamate di funzione, ciascuna delle quali è rappresentata da una casella colorata. Ogni funzione è stata richiamata dalla casella in alto, quindi in questo caso è possibile vedere che MessageLoop::RunTask chiamato RenderWidget::OnStackBuffersComplete, che a sua volta ha chiamato RenderWidget::DoDeferredUpdate, e così via. Leggendo questi dati, puoi ottenere una visione completa di cosa ha chiamato cosa e della durata di ogni esecuzione.

Ma ecco dove diventa un po' appiccicoso. Le informazioni visualizzate da about:tracing sono le chiamate di funzione non elaborate dal codice sorgente di Chrome. Puoi fare supposizioni in merito a ciò che fa ogni funzione partendo dal nome, ma le informazioni non sono esattamente facili da usare. È utile vedere il flusso complessivo dell'inquadratura, ma hai bisogno di qualcosa di un po' più leggibile per capire davvero cosa sta succedendo.

Aggiunta di tag di traccia

Fortunatamente c'è un modo semplice per aggiungere la strumentazione manuale al codice per creare dati di traccia: console.time e console.timeEnd.

console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");

Il codice riportato sopra crea nuove caselle nel nome della vista di tracciamento con i tag specificati. Pertanto, se esegui nuovamente l'app, vedrai le caselle "Aggiorna" e "Visualizza" che mostrano il tempo trascorso tra le chiamate di inizio e di fine di ciascun tag.

Tag aggiunti manualmente
Tag aggiunti manualmente

In questo modo, puoi creare dati di tracciamento leggibili per tenere traccia degli hotspot nel tuo codice.

GPU o CPU?

Con la grafica con accelerazione hardware, una delle domande più importanti che puoi porti durante la profilazione è: questo codice è vincolato alla GPU o alla CPU? Con ogni frame lavorerai sul rendering sulla GPU e sulla CPU; per capire cosa rallenta il gioco, dovrai vedere come il lavoro è bilanciato tra le due risorse.

Innanzitutto, trova nella vista di tracciamento la riga denominata CrGPUMain, che indica se la GPU è occupata in un determinato momento.

Tracce di GPU e CPU

Puoi vedere che ogni frame del tuo gioco causa il funzionamento della CPU nel CrRendererMain e nella GPU. La traccia riportata sopra mostra un caso d'uso molto semplice in cui sia la CPU sia la GPU sono inattive per la maggior parte di ogni frame da 16 ms.

La visualizzazione di tracciamento diventa davvero utile quando hai un gioco che rallenta e non sai quale risorsa stai sfruttando al massimo. La relazione tra GPU e CPU è fondamentale per il debug. Considera lo stesso esempio di prima, ma aggiungi un po' di lavoro in più nel ciclo di aggiornamento.

console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");

console.time("render");
render();
console.timeEnd("render");

Ora vedrai una traccia simile alla seguente:

Tracce di GPU e CPU

Cosa ci dice questa traccia? Possiamo vedere che il frame nella foto passa da circa 2270 ms a 2320 ms, il che significa che ogni frame impiega circa 50 ms (una frequenza fotogrammi di 20 Hz). Puoi vedere frammenti di riquadri colorati che rappresentano la funzione di rendering accanto al riquadro di aggiornamento, ma il frame è interamente dominato dall'aggiornamento stesso.

A differenza di quanto avviene sulla CPU, puoi vedere che la GPU è ancora inattiva per quasi tutti i frame. Per ottimizzare questo codice, potresti cercare le operazioni che possono essere eseguite nel codice shabby e spostarle nella GPU per utilizzare al meglio le risorse.

E quando il codice dello shabby è lento e la GPU è sovraccaricata? Cosa succede se rimuoviamo il lavoro non necessario dalla CPU e invece aggiungiamo un po' di lavoro nel codice dello strumento di esplorazione dei frammenti? Ecco uno strumento di shadowing dei frammenti inutilmente costoso:

#ifdef GL_ES
precision highp float;
#endif
void main(void) {
  for(int i=0; i<9999; i++) {
    gl_FragColor = vec4(1.0, 0, 0, 1.0);
  }
}

Come si presenta una traccia di codice che utilizza lo Shadr?

Tracce di GPU e CPU quando viene utilizzato un codice GPU lento
Tracce GPU e CPU quando viene utilizzato un codice GPU lento

Ancora una volta, nota la durata di un frame. Qui il pattern ricorrente va da circa 2750ms a 2950ms, una durata di 200ms (frame rate di circa 5Hz). La riga CrRendererMain è quasi completamente vuota, il che significa che la CPU è inattiva la maggior parte del tempo, mentre la GPU è sovraccaricata. Questo è un segnale che indica chiaramente che gli utenti sono troppo gravosi.

Se non avessi visibilità sulla causa esatta della bassa frequenza fotogrammi, potresti osservare l'aggiornamento a 5 Hz e provare la tentazione di inserire il codice di gioco e provare a ottimizzare o rimuovere la logica di gioco. In questo caso, non sarebbe assolutamente positivo, perché la logica nel ciclo di gioco non è ciò che fa perdere tempo. Infatti, ciò che questa traccia indica è che facendo più lavoro sulla CPU ogni frame sarebbe essenzialmente "senza costi" in quanto la CPU è inattiva, quindi dargli più lavoro non influirà sulla durata del frame.

Esempi reali

Ora vediamo come appaiono i dati di tracciamento di un gioco reale. Una delle cose interessanti dei giochi sviluppati con tecnologie open web è che puoi vedere cosa succede nei tuoi prodotti preferiti. Se vuoi testare gli strumenti di profilazione, puoi scegliere il tuo titolo WebGL preferito dal Chrome Web Store e profilarlo con about:tracing. Questa è una traccia di esempio tratta dall'eccellente gioco WebGL Skid Racer.

Tracciare un gioco reale
Tracciamento di un gioco reale

Ogni fotogramma richiede circa 20 ms, il che significa che la frequenza fotogrammi è di circa 50 f/s. Puoi vedere che il lavoro è bilanciato tra CPU e GPU, ma la GPU è la risorsa più richiesta. Se vuoi vedere come si presentano esempi reali di giochi WebGL, prova a sperimentare con alcuni dei titoli del Chrome Web Store realizzati con WebGL, tra cui:

Conclusione

Se vuoi che il tuo gioco venga eseguito a 60 Hz, per ogni frame tutte le tue operazioni devono rientrare in 16 ms di CPU e 16 ms di tempo GPU. Hai due risorse che possono essere utilizzate in parallelo e puoi spostare il lavoro da una all'altra per massimizzare il rendimento. La vista about:tracing di Chrome è uno strumento prezioso per ottenere informazioni su ciò che sta realmente facendo il tuo codice e ti aiuterà a massimizzare il tempo di sviluppo affrontando i problemi giusti.

Passaggi successivi

Oltre alla GPU, puoi anche tracciare altre parti del runtime di Chrome. Chrome Canary, la versione in fase iniziale di Chrome, è instrumentato per tenere traccia di IO, IndexedDB e molte altre attività. Leggi questo articolo di Chromium per una comprensione più approfondita dello stato attuale del tracciamento degli eventi.

Se sei uno sviluppatore di giochi web, assicurati di guardare il video qui sotto. Presentazione del team Game Developer Advocate di Google alla GDC 2012 sull'ottimizzazione delle prestazioni per i giochi Chrome: