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 premier pas, mais pour avoir une vision d'ensemble, vous devez saisir les nuances des activités Chrome.
L'outil about:tracing fournit des informations qui vous permettent d'éviter les solutions de contournement hâtives visant à améliorer les performances, mais qui sont essentiellement des suppositions bien intentionnées. Cela vous permettra d'économiser beaucoup de temps et d'énergie, de voir plus clairement ce que fait Chrome avec chaque image et d'utiliser ces informations pour optimiser votre jeu.
Présentation du traçage
L'outil about:tracing de Chrome vous offre une vue d'ensemble de toutes les activités de Chrome sur une période donnée avec un niveau de précision tel que vous pourriez vous sentir dépassé au premier abord. De nombreuses fonctions de Chrome sont instrumentées pour le traçage prêtes à l'emploi. Vous pouvez donc utiliser about:tracing pour suivre vos performances sans aucune instrumentation manuelle. (Voir une section ultérieure sur l'instrumentation manuelle de votre code JS)
Pour afficher la vue de traçage, il suffit de saisir "about:tracing". dans l'omnibox (barre d'adresse) de Chrome.
<ph type="x-smartling-placeholder">.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 peuvent ressembler:
<ph type="x-smartling-placeholder">.Oui, c’est déroutant. Parlons de la façon de la lire.
Chaque ligne représente un processus en cours de profilage. L'axe de gauche et de droite indique l'heure, et chaque case colorée représente un appel de fonction instrumentée. Il existe des lignes correspondant à différents types de ressources. CrGpuMain, qui montre ce que fait l'unité de traitement graphique (GPU), et CrRendererMain, sont les plus intéressants pour le profilage du jeu. 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.
<ph type="x-smartling-placeholder">.Dans cet exemple, les deux propositions sont: 2216 et 6516. Malheureusement, il n'existe actuellement aucune méthode soignée pour choisir 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, rechercher la ligne qui contient vos données de trace). Dans cet exemple, 6516 semble exécuter une boucle principale à partir de la fréquence des mises à jour. Si vous fermez tous les autres onglets avant de démarrer la trace, il sera plus facile de trouver le bon CrRendererMain. Toutefois, il se peut qu'il y ait toujours des lignes CrRendererMain pour des processus autres que votre jeu.
Trouver votre cadre
Une fois que vous avez trouvé 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 (d'avant en arrière dans le temps), et W et S pour effectuer un zoom avant ou arrière sur les données. Votre boucle principale est un schéma qui se répète toutes les 16 millisecondes si votre jeu fonctionne à 60 Hz.
<ph type="x-smartling-placeholder">.Une fois que vous avez localisé le rythme cardiaque de votre jeu, vous pouvez analyser ce que fait exactement votre code à chaque image. Utilisez W, A, S, D pour zoomer jusqu'à ce que vous puissiez lire le texte dans les zones de fonction.
<ph type="x-smartling-placeholder">.Cet ensemble de encadrés affiche une série d'appels de fonction, chacun d'entre eux étant représenté par une case colorée. Chaque fonction était appelée par la case située au-dessus. Dans ce cas, vous pouvez donc voir que MessageLoop::RunTask a appelé RenderWidget::OnSwapBuffersComplete, qui a lui-même appelé RenderWidget::DoDeferredUpdate, etc. En lisant ces données, vous pouvez obtenir une vue complète de la nature de la tâche et de la durée de chaque exécution.
Mais c’est là que ça devient un peu collant. Les informations exposées 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 vraiment conviviales. Il est utile de visualiser le flux global de votre frame, mais vous avez besoin d'un élément un peu plus lisible pour comprendre réellement 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 de nouvelles zones dans le nom de la vue de traçage avec les balises spécifiées. Par conséquent, si vous exécutez à nouveau l'application, le message "update" s'affiche. et "rendu" qui indiquent le temps écoulé entre les appels de début et de fin pour chaque balise.
<ph type="x-smartling-placeholder">.Vous pouvez ainsi créer des données de traçage lisibles par l'humain 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 processeur ? Avec chaque image, vous effectuerez un travail de rendu sur le GPU et une certaine logique sur le CPU ; Pour comprendre ce qui ralentit votre jeu, vous devez équilibrer le travail entre les deux ressources.
Dans la vue de traçage, recherchez tout d'abord la ligne "CrGPUMain" qui indique si le GPU est occupé à un moment donné.
Vous pouvez constater que chaque image de votre jeu entraîne le fonctionnement du processeur dans CrRendererMain ainsi que sur le GPU. La trace ci-dessus montre un cas d'utilisation très simple dans lequel le processeur et le GPU sont inactifs pour la plupart de chaque frame de 16 ms.
La vue de traçage devient vraiment utile lorsque vous avez un jeu qui s'exécute lentement et que vous ne savez pas quelle ressource vous utilisez. La clé du débogage est d'examiner les relations entre les lignes GPU et CPU. Reprenons l'exemple précédent, mais avec 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 à celle-ci s'affiche:
Que nous dit cette trace ? Comme nous pouvons le constater, le cadre sur l'image passe de 2 270 ms à 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 éclats de zones 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 constater que celui-ci est toujours inactif pour la plupart des images. Pour optimiser ce code, vous pouvez rechercher les opérations réalisables dans le code du nuanceur et les déplacer vers le GPU afin d'exploiter 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 ajoutons du travail dans le 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 ?
<ph type="x-smartling-placeholder">.Là encore, notez la durée d'une image. Ici, le schéma répétitif passe d'environ 2 750 ms à 2 950 ms, soit une durée de 200 ms (fréquence d'images d'environ 5 Hz). La ligne CrRendererMain est presque entièrement vide, ce qui signifie que le processeur est inactif la plupart du temps, alors que le GPU est surchargé. Cela indique que vos nuanceurs sont trop lourds.
Si vous ne saviez pas exactement pourquoi la fréquence d'images était basse, vous pourriez observer la mise à jour de 5 Hz et être tenté d'accéder au code du jeu pour essayer d'optimiser ou de supprimer la logique du jeu. Dans ce cas, cela ne servirait à rien, car la logique de la boucle de jeu n'est pas ce qui fait perdre du temps. En fait, cette trace indique qu'en faisant plus de travail sur le processeur, chaque trame serait essentiellement « libre ». dans la mesure où le CPU reste inactif. Par conséquent, donner plus de travail n'affectera pas la durée du frame.
Exemples concrets
Voyons maintenant à quoi ressemblent les données de traçage d'un vrai jeu. 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.
<ph type="x-smartling-placeholder">.Il semble que chaque image prend environ 20 ms, ce qui signifie que la fréquence d'images est d'environ 50 FPS. Comme vous pouvez le constater, la tâche est équilibrée entre le processeur et le GPU, mais le GPU est la ressource la plus demandée. Pour découvrir le profil d'exemples réels de jeux WebGL, essayez d'explorer les titres du Chrome Web Store créés avec WebGL, y compris:
Conclusion
Si vous souhaitez que votre jeu s'exécute avec une fréquence de 60 Hz, toutes vos opérations doivent correspondre à 16 ms de processeur et à 16 ms de temps GPU pour chaque frame. Vous disposez de deux ressources qui peuvent être utilisées en parallèle, et vous pouvez alterner les ressources de l'une à l'autre pour optimiser les performances. La vue about:tracing de Chrome est un outil inestimable pour mieux comprendre ce que fait votre code. Elle vous aidera à optimiser votre temps de développement en résolvant les problèmes appropriés.
Étape suivante
Outre le GPU, vous pouvez également suivre d'autres parties de l'environnement d'exécution de Chrome. Chrome Canary, la version préliminaire de Chrome, est instrumenté pour tracer IO, IndexedDB et plusieurs autres activités. Pour en savoir plus sur l'état actuel des événements de traçage, consultez cet article Chromium.
Si vous développez des jeux sur le Web, regardez la vidéo ci-dessous. Il s'agit d'une présentation de l'équipe Google Game Developer Advocate lors de la conférence GDC 2012 sur l'optimisation des performances pour les jeux Chrome: