Profiler votre jeu WebGL avec l'indicateur about:tracing

Lilli Thompson
Lilli Thompson

Si vous ne pouvez pas la mesurer, vous ne pouvez pas l'améliorer.

Lord Kelvin

Pour que vos jeux HTML5 s'exécutent plus rapidement, vous devez d'abord identifier les goulots d'étranglement qui affectent les performances, mais cela peut s'avérer difficile. L'évaluation des données d'images par seconde (FPS) est un bon début, mais pour avoir une vue d'ensemble, vous devez saisir les nuances propres aux activités Chrome.

L'outil about:tracing fournit les insights qui vous permettent d'éviter les solutions de contournement hâtives visant à améliorer les performances, qui sont essentiellement des approximations bien intentionnées. Cela vous permettra d'économiser beaucoup de temps et d'énergie, d'obtenir une vision plus claire de ce que fait Chrome à chaque image et d'utiliser ces informations pour optimiser votre jeu.

En savoir plus sur le traçage

L'outil about:tracing de Chrome vous offre une fenêtre sur l'ensemble des activités de Chrome sur une période de temps avec un niveau de précision tellement élevé que vous pouvez en avoir d'autres au premier abord. Bon nombre des fonctions de Chrome sont instrumentées pour le traçage directement. Par conséquent, sans instrumentation manuelle, vous pouvez toujours utiliser about:tracing pour suivre vos performances. Consultez la section suivante sur l'instrumentation manuelle de votre code JS.

Pour afficher la vue de traçage, il vous suffit de saisir "about:tracing" dans l'omnibox (barre d'adresse) de Chrome.

Champ polyvalent Chrome
Saisissez "about:tracing" dans l'omnibox Chrome

Depuis l'outil de traçage, vous pouvez commencer l'enregistrement, exécuter votre jeu pendant quelques secondes, puis afficher les données de trace. Voici un exemple de ce à quoi les données pourraient ressembler:

Résultat de traçage simple
Résultat de traçage simple

Oui, c’est déroutant, d’accord. Parlons de la lecture.

Chaque ligne représente un processus en cours de profilage, l'axe de gauche à droite indique l'heure, et chaque case de couleur est un appel de fonction instrumenté. Il existe des lignes pour différents types de ressources. Ceux qui sont les plus intéressants pour le profilage des jeux sont CrGpuMain, qui montre ce que fait le GPU, et CrRendererMain. Chaque trace contient des lignes CrRendererMain pour chaque onglet ouvert pendant la période de trace (y compris l'onglet about:tracing lui-même).

Lorsque vous lisez des données de trace, votre première tâche consiste à déterminer quelle ligne CrRendererMain correspond à votre jeu.

Résultat de traçage simple mis en surbrillance
Résultat de traçage simple mis en surbrillance

Dans cet exemple, les deux candidats sont 2216 et 6516. Malheureusement, il n'existe actuellement aucun moyen soigné de sélectionner votre application, sauf pour rechercher la ligne qui effectue de nombreuses mises à jour périodiques (ou si vous avez instrumenté manuellement votre code avec des points de trace, pour rechercher la ligne contenant vos données de trace). Dans cet exemple, il semble que 6516 exécute une boucle principale à partir de la fréquence des mises à jour. Si vous fermez tous les autres onglets avant de commencer la trace, il sera plus facile de trouver le bon CrRendererMain. Toutefois, il peut y avoir des lignes CrRendererMain pour des processus autres que votre jeu.

Trouver votre cadre

Une fois que vous avez localisé la ligne appropriée dans l'outil de traçage de votre jeu, l'étape suivante consiste à trouver la boucle principale. La boucle principale ressemble à un modèle répétitif dans les données de traçage. Vous pouvez parcourir les données de traçage à l'aide des touches W, A, S et D: A et D pour vous déplacer vers la gauche ou la droite (dans le temps) et W et S pour effectuer un zoom avant ou arrière sur les données. La boucle principale devrait être un schéma qui se répète toutes les 16 millisecondes si votre jeu s'exécute à 60 Hz.

Il semble s'agir de trois frames d'exécution.
Représente trois frames d'exécution

Une fois que vous avez localisé la pulsation de votre jeu, vous pouvez voir ce que fait votre code à chaque frame. Utilisez W, A, S, D pour faire un zoom avant jusqu'à ce que vous puissiez lire le texte dans les zones de fonction.

En savoir plus sur un frame d'exécution
Entrer en profondeur dans un frame d'exécution

Cet ensemble de cases montre une série d'appels de fonction, chaque appel étant représenté par une case colorée. Chaque fonction a été appelée par la case située au-dessus. Dans ce cas, vous pouvez donc voir que MessageLoop::RunTask a appelé RenderWidget::OnSwapBuffersComplete, qui à son tour s'appelle RenderWidget::DoDeferredUpdate, etc. En lisant ces données, vous pouvez obtenir une vue complète de ce que l'on appelle et la durée de chaque exécution.

Mais c'est là que ça devient un peu adhésif. Les informations fournies par about:tracing correspondent aux appels de fonction bruts à partir du code source de Chrome. Vous pouvez faire des suppositions éclairées sur ce que fait chaque fonction à partir du nom, mais les informations ne sont pas exactement conviviales. Il est utile de voir le flux global de votre cadre, mais vous avez besoin de quelque chose d'un peu plus lisible par l'homme pour comprendre ce qui se passe.

Ajouter des balises de trace

Heureusement, il existe un moyen simple d'ajouter une instrumentation manuelle à votre code pour créer des données de trace: console.time et console.timeEnd.

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

Le code ci-dessus crée des zones dans le nom de la vue de traçage, avec les balises spécifiées. Par conséquent, si vous réexécutez l'application, les zones "update" (mise à jour) et "render" (rendu) afficheront le temps écoulé entre les appels de début et de fin pour chaque balise.

Balises ajoutées manuellement
Balises ajoutées manuellement

Vous pouvez ainsi créer des données de traçage lisibles pour suivre les hotspots dans votre code.

GPU ou CPU ?

Avec l'accélération matérielle, l'une des questions les plus importantes que vous pouvez vous poser lors du profilage est la suivante: ce code est-il lié au GPU ou au CPU ? Pour chaque image, vous allez effectuer un travail de rendu sur le GPU et une certaine logique sur le CPU. Pour comprendre ce qui ralentit votre jeu, vous devez voir comment le travail est équilibré entre les deux ressources.

Tout d'abord, dans la vue de traçage, recherchez la ligne nommée CrGPUMain, qui indique si le GPU est occupé à un moment donné.

Traces du processeur et du GPU

Vous pouvez constater que chaque frame de votre jeu entraîne un travail du processeur dans CrRendererMain ainsi que sur le GPU. La trace ci-dessus montre un cas d'utilisation très simple où le processeur et le GPU sont inactifs pendant la majeure partie de chaque frame de 16 ms.

La vue de traçage s'avère très utile lorsque vous avez un jeu qui s'exécute lentement et que vous n'êtes pas sûr de savoir quelle ressource vous dépassez. La clé du débogage consiste à examiner le lien entre les lignes du GPU et celui du processeur. Reprenons l'exemple précédent, mais en ajoutant un peu plus de travail dans la boucle de mise à jour.

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

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

Une trace semblable à la suivante s'affiche:

Traces du processeur et du GPU

Que nous dit cette trace ? Nous pouvons voir que le cadre illustré va d'environ 2 270 à 2 320 ms, ce qui signifie que chaque image prend environ 50 ms (une fréquence d'images de 20 Hz). Vous pouvez voir des bandes de cases colorées représentant la fonction de rendu à côté de la zone de mise à jour, mais le cadre est entièrement dominé par la mise à jour elle-même.

Contrairement à ce qui se passe sur le processeur, vous pouvez voir que le GPU est toujours inactif pour la plupart des frames. Pour optimiser ce code, vous pouvez rechercher les opérations pouvant être effectuées dans le code du nuanceur et les déplacer vers le GPU afin d'utiliser au mieux les ressources.

Que se passe-t-il lorsque le code du nuanceur est lent et que le GPU est surchargé ? Que se passe-t-il si nous supprimons les tâches inutiles du processeur et que nous ajoutons du travail au code du nuanceur de fragments ? Voici un nuanceur de fragments inutilement coûteux:

#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);
  }
}

À quoi ressemble une trace de code utilisant ce nuanceur ?

Traces du GPU et du processeur en cas d&#39;utilisation de code GPU lent
Traces de GPU et de processeur en cas d'utilisation de code GPU lent

Encore une fois, notez la durée d'un frame. Ici, le schéma répétitif va d'environ 2 750 à 2 950 ms, soit une durée de 200 ms (fréquence d'images d'environ 5 Hz). La ligne CrRendererMain est presque complètement vide, ce qui signifie que le processeur est inactif la plupart du temps, alors que le GPU est surchargé. Cela signifie que vos nuanceurs sont trop lourds.

Si vous n'aviez pas de visibilité sur la cause exacte de la faible fréquence d'images, vous pourriez observer la mise à jour à 5 Hz et être tenté d'entrer dans le code du jeu et d'essayer d'optimiser ou de supprimer la logique du jeu. Dans le cas présent, cela ne ferait absolument rien, car la logique de la boucle de jeu n'est pas ce qui prend du temps. En fait, cette trace indique que faire plus de travail sur le processeur pour chaque frame serait essentiellement "libre" dans la mesure où le processeur reste inactif. Par conséquent, le fait de lui fournir plus de travail n'aura aucune incidence sur la durée du frame.

Exemples concrets

Voyons maintenant à quoi ressemblent les données de traçage d'un jeu réel. L'un des avantages des jeux conçus avec des technologies Web ouvertes est que vous pouvez voir ce qui se passe dans vos produits préférés. Si vous voulez tester les outils de profilage, vous pouvez choisir votre titre WebGL préféré sur le Chrome Web Store et le profiler avec about:tracing. Voici un exemple de trace tirée de l'excellent jeu WebGL, Skid Racer.

Traçage d&#39;un vrai jeu
Traçage d'un vrai jeu

Apparemment, chaque image prend environ 20 ms, ce qui signifie que la fréquence d'images est d'environ 50 FPS. Vous pouvez constater que le travail est équilibré entre le processeur et le GPU, mais que le GPU est la ressource la plus demandée. Pour découvrir comment profiler des exemples réels de jeux WebGL, essayez de jouer avec certains des titres du Chrome Web Store créés avec WebGL, y compris:

Conclusion

Si vous souhaitez que votre jeu s'exécute à 60 Hz, toutes vos opérations doivent tenir sur 16 ms de processeur et 16 ms de temps de GPU pour chaque frame. Vous disposez de deux ressources qui peuvent être utilisées en parallèle. Vous pouvez répartir le travail de l'une à l'autre pour optimiser les performances. La vue about:tracing de Chrome est un outil inestimable pour comprendre ce que fait réellement votre code et vous aider à optimiser votre temps de développement en résolvant les problèmes appropriés.

Étape suivante

Outre le GPU, vous pouvez suivre d'autres parties de l'environnement d'exécution de Chrome. Chrome Canary, version préliminaire de Chrome, permet de suivre les E/S, IndexedDB et plusieurs autres activités. Nous vous invitons à lire cet article de Chromium pour mieux comprendre l'état actuel des événements de traçage.

Si vous êtes développeur de jeux Web, veillez à regarder la vidéo ci-dessous. Il s'agit d'une présentation de l'équipe Google Game Developer Advocate à la conférence GDC 2012 consacrée à l'optimisation des performances pour les jeux Chrome: