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.
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:
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.
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.
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.
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.
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é.
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:
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 ?
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.
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: