
Conseils de profilage de performance pour les développeurs de jeux
Une performance fluide est essentielle pour créer des expériences de jeu immersives pour les joueurs. Pour garantir que votre jeu est optimisé, un flux de travail de profilage cohérent et complet est un "must have" pour un développement de jeu efficace, et cela commence par une procédure simple en trois points :
- Profilage avant de faire des changements majeurs : Établir une ligne de base.
- Profilage pendant le développement : Suivre et s'assurer que les changements ne nuisent pas à la performance ou aux budgets.
- Profilage après : Prouver que les changements ont eu l'effet désiré.
Cette page décrit un flux de travail de profilage général pour les développeurs de jeux. Il est extrait de l'e-book, Guide ultime du profilage des jeux Unity, disponible en téléchargement gratuit (la version Unity 6 du guide sera bientôt disponible). L'e-book a été créé par des experts Unity externes et internes en développement de jeux, profilage et optimisation.
Dans cet article, vous pouvez apprendre des objectifs utiles à définir avec le profilage, des goulets d'étranglement de performance courants, tels que le fait d'être limité par le CPU ou le GPU, et comment identifier et examiner ces situations plus en détail.

Définir un budget de trame
Les joueurs mesurent souvent la performance en utilisant le taux de rafraîchissement, ou les images par seconde (fps), mais en tant que développeur, il est généralement recommandé d'utiliser le temps de rendu en millisecondes à la place. Considérez le scénario simplifié suivant :
Pendant l'exécution, votre jeu rend 59 images en 0,75 seconde. Cependant, la prochaine image prend 0,25 seconde à rendre. Le taux de rafraîchissement moyen de 60 fps semble bon, mais en réalité, les joueurs remarqueront un effet de saccade puisque la dernière image prend un quart de seconde à rendre.
C'est l'une des raisons pour lesquelles il est important de viser un budget de temps spécifique par image. Cela vous fournit un objectif solide à atteindre lors du profilage et de l'optimisation de votre jeu, et finalement, cela crée une expérience plus fluide et plus cohérente pour vos joueurs.
Chaque image aura un budget de temps basé sur votre fps cible. Une application visant 30 fps devrait toujours prendre moins de 33,33 ms par image (1000 ms / 30 fps). De même, un objectif de 60 fps laisse 16,66 ms par image (1000 ms / 60 fps).
Vous pouvez dépasser ce budget pendant des séquences non interactives, par exemple, lors de l'affichage de menus UI ou du chargement de scènes, mais pas pendant le gameplay. Même une seule image qui dépasse le budget de temps cible provoquera des saccades.
Remarque : Un taux de rafraîchissement constamment élevé dans les jeux VR est essentiel pour éviter de provoquer des nausées ou de l'inconfort chez les joueurs, et est souvent nécessaire pour que votre jeu obtienne la certification du détenteur de la plateforme.
Images par seconde : Une métrique trompeuse
Une façon courante pour les joueurs de mesurer la performance est avec le taux de rafraîchissement, ou les images par seconde. Cependant, il est recommandé d'utiliser le temps de rendu en millisecondes à la place. Pour comprendre pourquoi, regardez le graphique ci-dessus des fps par rapport au temps de rendu.
Considérez ces chiffres :
1000 ms/sec / 900 fps = 1,111 ms par image
1000 ms/sec / 450 fps = 2,222 ms par image
1000 ms/sec / 60 fps = 16,666 ms par image
1000 ms/sec / 56,25 fps = 17,777 ms par image
Si votre application fonctionne à 900 fps, cela se traduit par un temps d'image de 1,111 millisecondes par image. À 450 fps, cela représente 2,222 millisecondes par image. Cela représente une différence de seulement 1,111 millisecondes par image, même si le taux d'images semble diminuer de moitié.
Si vous regardez les différences entre 60 fps et 56,25 fps, cela se traduit par 16,666 millisecondes par image et 17,777 millisecondes par image, respectivement. Cela représente également 1,111 millisecondes supplémentaires par image, mais ici, la baisse du taux d'images semble beaucoup moins dramatique en pourcentage.
C'est pourquoi les développeurs utilisent le temps moyen d'image pour évaluer la vitesse du jeu plutôt que le fps.
Ne vous inquiétez pas du fps à moins que vous ne tombiez en dessous de votre taux d'images cible. Concentrez-vous sur le temps d'image pour mesurer la rapidité de votre jeu, puis restez dans votre budget d'images.
Lisez l'article original, "Robert Dunlop’s fps versus frame time," pour plus d'informations.

Défis mobiles
Le contrôle thermique est l'un des domaines les plus importants à optimiser lors du développement d'applications pour appareils mobiles. Si le CPU ou le GPU passent trop de temps à pleine puissance en raison d'un code inefficace, ces puces vont chauffer. Pour éviter la surchauffe et les dommages potentiels aux puces, le système d'exploitation réduira la vitesse d'horloge de l'appareil pour lui permettre de refroidir, ce qui provoque des saccades d'images et une mauvaise expérience utilisateur. Cette réduction de performance est connue sous le nom de throttling thermique.
Des taux d'images plus élevés et une exécution de code accrue (ou des opérations d'accès DRAM) entraînent une augmentation de la consommation de batterie et de la génération de chaleur. Une mauvaise performance peut également rendre votre jeu injouable pour des segments entiers d'appareils mobiles bas de gamme, ce qui peut entraîner des occasions manquées sur le marché.
Lorsqu'il s'agit de résoudre le problème des thermiques, considérez le budget dont vous disposez comme un budget global du système.
Combattez le throttling thermique et la décharge de la batterie en profilant tôt pour optimiser votre jeu dès le départ. Ajustez les paramètres de votre projet pour le matériel de votre plateforme cible afin de lutter contre les problèmes de décharge thermique et de batterie.
Ajustez les budgets de trame sur mobile
Un conseil général pour lutter contre les problèmes thermiques des appareils lors de longues sessions de jeu est de laisser un temps d'inactivité de trame d'environ 35%. Cela donne aux puces mobiles le temps de refroidir et aide à prévenir une décharge excessive de la batterie. En utilisant un temps de trame cible de 33,33 ms par trame (pour 30 fps), le budget de trame pour les appareils mobiles sera d'environ 22 ms par trame.
Le calcul ressemble à ceci : (1000 ms / 30) * 0.65 = 21.66 ms
Pour atteindre 60 fps sur mobile en utilisant le même calcul, il faudrait un temps de trame cible de (1000 ms / 60) * 0.65 = 10.83 ms. C'est difficile à atteindre sur de nombreux appareils mobiles et cela déchargerait la batterie deux fois plus vite que de viser 30 fps. Pour ces raisons, de nombreux jeux mobiles visent 30 fps plutôt que 60. Utilisez Application.targetFrameRate pour contrôler ce paramètre, et consultez la section "Définir un budget de trame" dans l'e-book de profilage pour plus de détails sur le temps de trame.
L'échelle de fréquence sur les puces mobiles peut rendre difficile l'identification de vos allocations de budget de temps d'inactivité de trame lors du profilage. Vos améliorations et optimisations peuvent avoir un effet net positif, mais l'appareil mobile pourrait réduire la fréquence, et par conséquent, fonctionner plus frais. Utilisez des outils personnalisés tels que FTrace ou Perfetto pour surveiller les fréquences des puces mobiles, le temps d'inactivité et l'échelle avant et après les optimisations.
Tant que vous restez dans votre budget total de temps de trame pour votre fps cible (disons 33,33 ms pour 30 fps) et que vous voyez votre appareil travailler moins ou enregistrer des températures plus basses pour maintenir ce taux de trame, alors vous êtes sur la bonne voie.
Une autre raison d'ajouter de la marge au budget de trame sur les appareils mobiles est de tenir compte des fluctuations de température dans le monde réel. Par une journée chaude, un appareil mobile va chauffer et avoir du mal à dissiper la chaleur, ce qui peut entraîner un throttling thermique et une mauvaise performance du jeu. Réservez un pourcentage du budget de trame pour aider à éviter ce scénario.

Réduisez les opérations d'accès à la mémoire
L'accès à la DRAM est généralement une opération gourmande en énergie sur les appareils mobiles. Les conseils d'optimisation d'Arm pour le contenu graphique sur les appareils mobiles indiquent que l'accès à la mémoire LPDDR4 coûte environ 100 picojoules par octet.
Réduisez le nombre d'opérations d'accès à la mémoire par image en :
- Réduisant le taux de rafraîchissement
- Réduisant la résolution d'affichage lorsque cela est possible
- Utilisant des maillages plus simples avec un nombre de sommets réduit et une précision d'attribut réduite
- Utilisant la compression de textures et le mipmapping
Lorsque vous devez vous concentrer sur les appareils utilisant le matériel CPU ou GPU Arm, l'outil Arm Performance Studio (en particulier, Streamline Performance Analyzer) comprend d'excellents compteurs de performance pour identifier les problèmes de bande passante mémoire. Les compteurs disponibles sont listés et expliqués pour chaque génération de GPU Arm dans un guide utilisateur correspondant, par exemple, Guide de référence des compteurs de performance Mali-G710. Notez que le profilage GPU d'Arm Performance Studio nécessite un GPU Arm Immortalis ou Mali.
Établir des niveaux de matériel pour le benchmarking
En plus d'utiliser des outils de profilage spécifiques à la plateforme, établissez des niveaux ou un appareil de spécifications minimales pour chaque plateforme et niveau de qualité que vous souhaitez prendre en charge, puis profilez et optimisez les performances pour chacune de ces spécifications.
Par exemple, si vous ciblez des plateformes mobiles, vous pourriez décider de prendre en charge trois niveaux avec des contrôles de qualité qui activent ou désactivent des fonctionnalités en fonction du matériel cible. Vous optimisez ensuite pour la spécification de l'appareil la plus basse dans chaque niveau. Comme autre exemple, si vous développez un jeu pour consoles, assurez-vous de profiler à la fois sur les versions plus anciennes et plus récentes.
Notre dernier guide d'optimisation mobile contient de nombreux conseils et astuces qui vous aideront à réduire le throttling thermique et à augmenter la durée de vie de la batterie des appareils mobiles exécutant vos jeux.
Du profilage de haut niveau au profilage de bas niveau
Lors du profilage, vous devez vous assurer de concentrer votre temps et vos efforts sur les domaines où vous pouvez créer le plus d'impact. Il est donc recommandé de commencer par une approche de haut en bas lors du profilage, ce qui signifie que vous commencez par un aperçu général des catégories telles que le rendu, les scripts, la physique et les allocations de collecte des ordures (GC). Une fois que vous avez identifié les domaines de préoccupation, vous pouvez approfondir les détails. Utilisez ce passage de haut niveau pour collecter des données et prendre des notes sur les problèmes de performance les plus critiques, y compris les scénarios qui causent des allocations gérées indésirables ou une utilisation excessive du CPU dans votre boucle de jeu principale.
Vous devrez d'abord rassembler des piles d'appels pour les marqueurs GC.Alloc. Si vous n'êtes pas familier avec ce processus, trouvez quelques conseils et astuces dans la section intitulée "Localiser les allocations de mémoire récurrentes sur la durée de l'application" dans l'e-book.
Si les piles d'appels signalées ne sont pas suffisamment détaillées pour identifier la source des allocations ou d'autres ralentissements, vous pouvez effectuer une deuxième session de profilage avec le Profilage Profond activé afin de trouver la source des allocations. Nous couvrons le profilage profond plus en détail dans l'e-book, mais en résumé, c'est un mode dans le Profiler qui capture des données de performance détaillées pour chaque appel de fonction, fournissant des informations granulaires sur les temps d'exécution et les comportements, mais avec un surcoût significativement plus élevé par rapport au profilage standard.
Lorsque vous collectez des notes sur les "contrevenants" du temps de trame, assurez-vous de noter comment ils se comparent par rapport au reste de la trame. Cet impact relatif peut être déformé lorsque le profilage profond est activé, car le profilage profond ajoute un surcoût significatif en instrumentant chaque appel de méthode.
Profilage précoce
Bien que vous deviez toujours profiler tout au long du cycle de développement de votre projet, les gains les plus significatifs du profilage se font lorsque vous commencez dans les phases précoces.
Profilez tôt et souvent afin que vous et votre équipe compreniez et mémorisiez une "signature de performance" pour le projet que vous pouvez utiliser comme référence. Si la performance chute, vous pourrez facilement repérer quand les choses tournent mal et corriger le problème.
Bien que le profilage dans l'Éditeur vous donne un moyen facile d'identifier les principaux problèmes, les résultats de profilage les plus précis proviennent toujours de l'exécution et du profilage des builds sur des appareils cibles, en utilisant des outils spécifiques à la plateforme pour examiner les caractéristiques matérielles de chaque plateforme. Cette combinaison vous fournira une vue holistique de la performance de l'application sur tous vos appareils cibles. Par exemple, vous pourriez être limité par le GPU sur certains appareils mobiles mais limité par le CPU sur d'autres, et vous ne pouvez l'apprendre qu'en mesurant sur ces appareils.

Identifier les problèmes de performance
Téléchargez la version PDF imprimable de ce tableau ici.
Le but du profilage est d'identifier les goulets d'étranglement comme cibles pour l'optimisation. Si vous vous fiez à des suppositions, vous pourriez finir par optimiser des parties du jeu qui ne sont pas des goulets d'étranglement, ce qui entraîne peu ou pas d'amélioration de la performance globale. Certaines "optimisations" pourraient même aggraver la performance globale de votre jeu tandis que d'autres peuvent être laborieuses mais donner des résultats insignifiants. La clé est d'optimiser l'impact de votre investissement de temps ciblé.
Le diagramme ci-dessus illustre le processus de profilage initial avec les sections qui le suivent fournissant des informations détaillées sur chaque étape. Ils présentent également des captures du Profiler provenant de projets Unity réels pour illustrer les types de choses à rechercher.
Pour obtenir une image holistique de toute l'activité CPU, y compris lorsqu'elle attend le GPU, utilisez le Vue chronologique dans le module CPU du Profiler. Familiarisez-vous avec les marqueurs Profiler communs pour interpréter correctement les captures. Certains des marqueurs Profiler peuvent apparaître différemment selon votre plateforme cible, alors passez du temps à explorer les captures de votre jeu sur chacune de vos plateformes cibles pour vous faire une idée de ce à quoi ressemble une capture « normale » pour votre projet.
La performance d'un projet est limitée par la puce et/ou le thread qui prend le plus de temps. C'est là que les efforts d'optimisation devraient se concentrer. Par exemple, imaginez les scénarios suivants pour un jeu avec un budget de temps de trame cible de 33,33 ms et VSync activé :
- Si le temps de trame CPU (hors VSync) est de 25 ms et le temps GPU est de 20 ms, pas de problème ! Vous êtes limité par le CPU, mais tout est dans le budget, et optimiser les choses n'améliorera pas le taux de trame (à moins que vous ne fassiez descendre à la fois le CPU et le GPU en dessous de 16,66 ms et que vous passiez à 60 fps).
- Si le temps de trame CPU est de 40 ms et le GPU est de 20 ms, vous êtes limité par le CPU et devrez optimiser la performance du CPU. Optimiser la performance du GPU n'aidera pas ; en fait, vous voudrez peut-être déplacer une partie du travail du CPU vers le GPU, par exemple, en utilisant des shaders de calcul au lieu de code C# lorsque cela est applicable, pour équilibrer les choses.
- Si le temps de trame CPU est de 20 ms et le GPU est de 40 ms, vous êtes limité par le GPU et devez optimiser le travail du GPU.
- Si le CPU et le GPU sont tous deux à 40 ms, vous êtes limité par les deux et devrez optimiser les deux en dessous de 33,33 ms pour atteindre 30 fps.
Consultez ces ressources qui explorent davantage le fait d'être limité par le CPU ou le GPU :

Êtes-vous dans le budget ?
Profiler et optimiser votre projet tôt et souvent tout au long du développement vous aidera à vous assurer que tous les threads CPU de votre application et le temps de trame GPU global sont dans le budget de trame. La question qui guidera ce processus est, êtes-vous dans le budget de trame ou non ?
Ci-dessus se trouve une image d'une capture de profil d'un jeu mobile Unity développé par une équipe qui a effectué un profilage et une optimisation continus. Le jeu cible 60 fps sur des téléphones mobiles haut de gamme, et 30 fps sur des téléphones de milieu/bas de gamme, comme celui de cette capture.
Notez comment près de la moitié du temps sur la trame sélectionnée est occupée par le marqueur Profiler jaune WaitForTargetFPS. L'application a défini Application.targetFrameRate à 30 fps, et VSync est activé. Le travail de traitement réel sur le thread principal se termine autour de la marque de 19 ms, et le reste du temps est passé à attendre que le reste des 33,33 ms s'écoule avant de commencer le prochain cadre. Bien que ce temps soit représenté par un marqueur Profiler, le thread CPU principal est essentiellement inactif pendant ce temps, permettant au CPU de refroidir et d'utiliser un minimum d'énergie de la batterie.
Le marqueur à surveiller pourrait être différent sur d'autres plateformes ou si VSync est désactivé. L'important est de vérifier si le thread principal fonctionne dans votre budget de cadre ou exactement sur votre budget de cadre avec une sorte de marqueur qui indique que l'application attend VSync et si les autres threads ont un temps d'inactivité.
Le temps d'inactivité est représenté par des marqueurs Profiler gris ou jaunes. La capture d'écran ci-dessus montre que le thread de rendu est inactif dans Gfx.WaitForGfxCommandsFromMainThread, ce qui indique les moments où il a fini d'envoyer des appels de dessin au GPU sur un cadre, et attend plus de demandes d'appels de dessin du CPU sur le suivant. De même, bien que le thread Job Worker 0 passe un certain temps dans Canvas.GeometryJob, la plupart du temps il est inactif. Ce sont tous des signes d'une application qui est confortablement dans le budget de cadre.
Si votre jeu est dans le budget de cadre
Si vous êtes dans le budget de cadre, y compris tous les ajustements apportés au budget pour tenir compte de l'utilisation de la batterie et du throttling thermique, vous avez terminé avec les tâches de profilage clés. Vous pouvez conclure en exécutant le Memory Profiler pour vous assurer que l'application est également dans son budget mémoire.
L'image ci-dessus montre un jeu fonctionnant confortablement dans le budget de cadre d'environ 22 ms requis pour 30 fps. Notez le temps d'attente pour WaitForTargetfps qui remplit le temps du thread principal jusqu'à VSync et les temps d'inactivité gris dans le thread de rendu et le thread de travail. Notez également que l'intervalle VBlank peut être observé en regardant les temps de fin de Gfx.Present cadre par cadre, et que vous pouvez dessiner une échelle de temps dans la zone Timeline ou sur la règle de temps en haut pour mesurer d'un à l'autre.

Limité par le CPU
Si votre jeu n'est pas dans le budget de cadre CPU, la prochaine étape consiste à enquêter sur quelle partie du CPU est le goulet d'étranglement - en d'autres termes, quel thread est le plus occupé.
Il est rare que la charge de travail totale du CPU soit le goulet d'étranglement. Les CPU modernes ont un certain nombre de cœurs différents, capables d'effectuer un travail de manière indépendante et simultanée. Différents threads peuvent s'exécuter sur chaque cœur de CPU. Une application Unity complète utilise une gamme de threads pour différents objectifs, mais ceux qui sont les plus courants pour trouver des problèmes de performance sont:
- Le fil principal: C'est ici que la majorité de la logique du jeu/scripts effectuent leur travail par défaut. La plupart des systèmes Unity, tels que la physique, l'animation, l'interface utilisateur et les premières étapes du rendu, s'exécutent ici.
- Le fil de rendu: Cela gère le travail de préparation (par exemple, quels objets dans la scène sont visibles par la caméra et lesquels sont exclus/invisibles parce qu'ils sont en dehors du frustum de vue, occlus ou éliminés par d'autres critères) qui doit se faire avant d'envoyer les instructions de rendu au GPU.
- Pendant le processus de rendu, le fil principal examine la scène et effectue le culling de la caméra, le tri de profondeur et le regroupement des appels de dessin, ce qui donne une liste de choses à rendre. Cette liste est transmise au fil de rendu, qui la traduit de la représentation interne agnostique de la plateforme de Unity aux appels spécifiques de l'API graphique nécessaires pour instruire le GPU sur une plateforme particulière.
- Les fils de travail de Job: Les développeurs peuvent utiliser le système de jobs pour planifier certains types de travail à exécuter sur des fils de travail, ce qui réduit la charge de travail sur le fil principal. Certains des systèmes et fonctionnalités de Unity utilisent également le système de jobs, tels que la physique, l'animation et le rendu.
Un exemple du monde réel d'optimisation du fil principal
L'image ci-dessous montre à quoi les choses pourraient ressembler dans un projet qui est limité par le fil principal. Ce projet fonctionne sur un Meta Quest 2, qui cible normalement des budgets de trame de 13,88 ms (72 fps) ou même 8,33 ms (120 fps), car des taux de trame élevés sont importants pour éviter le mal des transports dans les appareils VR. Cependant, même si ce jeu visait 30 fps, il est clair que ce projet est en difficulté.
Bien que le fil de rendu et les fils de travail ressemblent à l'exemple qui est dans le budget de trame, le fil principal est clairement occupé par le travail pendant toute la trame. Même en tenant compte de la petite quantité de surcharge du profileur à la fin de la trame, le fil principal est occupé pendant plus de 45 ms, ce qui signifie que ce projet atteint des taux de trame de moins de 22 fps. Il n'y a aucun marqueur qui montre le fil principal attendant inactif pour le VSync ; il est occupé pendant toute la trame.
La prochaine étape de l'enquête consiste à identifier les parties de la trame qui prennent le plus de temps et à comprendre pourquoi c'est ainsi. Sur cette trame, PostLateUpdate.FinishFrameRendering prend 16,23 ms, plus que le budget de trame entier. Un examen plus approfondi révèle qu'il y a cinq instances d'un marqueur appelé Inl_RenderCameraStack, indiquant que cinq caméras sont actives et rendent la scène. Puisque chaque caméra dans Unity invoque l'ensemble du pipeline de rendu, y compris le culling, le tri et le batching, la tâche de la plus haute priorité pour ce projet est de réduire le nombre de caméras actives, idéalement à une seule.
BehaviourUpdate, le marqueur Profiler qui englobe toutes les exécutions de la méthode MonoBehaviour.Update(), prend 7,27 millisecondes dans ce cadre.
Dans la vue Timeline, les sections de couleur magenta indiquent les points où les scripts allouent de la mémoire gérée sur le tas. Passer à la vue Hierarchy et filtrer en tapant GC.Alloc dans la barre de recherche montre que l'allocation de cette mémoire prend environ 0,33 ms dans ce cadre. Cependant, c'est une mesure inexacte de l'impact que les allocations de mémoire ont sur les performances de votre CPU.
Les marqueurs GC.Alloc ne sont pas chronométrés en enregistrant un point de début et de fin comme les échantillons Profiler typiques. Pour minimiser leur surcharge, Unity enregistre uniquement l'horodatage de l'allocation et la taille allouée.
Le Profiler attribue une petite durée d'échantillon artificielle aux marqueurs GC.Alloc uniquement pour s'assurer qu'ils sont visibles dans les vues Profiler. L'allocation réelle peut prendre plus de temps, surtout si une nouvelle plage de mémoire doit être demandée au système. Pour voir l'impact plus clairement, placez des marqueurs Profiler autour du code qui effectue l'allocation ; dans le profilage approfondi, les écarts entre les échantillons GC.Alloc de couleur magenta dans la vue Timeline fournissent une indication de combien de temps ils pourraient avoir pris.
De plus, l'allocation de nouvelle mémoire peut avoir des effets négatifs sur les performances qui sont plus difficiles à mesurer et à attribuer directement :
- Demander de la nouvelle mémoire au système peut affecter le budget énergétique sur un appareil mobile, ce qui peut amener le système à ralentir le CPU ou le GPU.
- La nouvelle mémoire doit probablement être chargée dans le cache L1 du CPU et pousse ainsi les lignes de cache existantes.
- La collecte de déchets incrémentielle ou synchrone peut être déclenchée directement ou avec un retard à mesure que l'espace libre existant dans la mémoire gérée est finalement dépassé.
Au début de la trame, quatre instances de Physics.FixedUpdate s'additionnent à 4,57 ms. Plus tard, LateBehaviourUpdate (appels à MonoBehaviour.LateUpdate()) prennent 4 ms, et Animators représentent environ 1 ms. Pour garantir que ce projet atteigne son budget et son taux de trame souhaités, tous ces problèmes de thread principal doivent être examinés pour trouver des optimisations appropriées.
Pièges courants pour les goulets d'étranglement du thread principal
Les plus grands gains de performance seront réalisés en optimisant les choses qui prennent le plus de temps. Les domaines suivants sont souvent des endroits fructueux pour chercher à optimiser dans des projets liés au thread principal :
- Calculs physiques
- Mise à jour du script MonoBehaviour
- Allocation et/ou collecte de mémoire
- Culling et rendu de la caméra sur le thread principal
- Batching d'appels de dessin inefficace
- Mises à jour de l'UI, mises en page et reconstructions
- Animation
Lisez nos guides d'optimisation qui offrent une longue liste de conseils pratiques pour optimiser certains des pièges les plus courants :
Selon le problème que vous souhaitez examiner, d'autres outils peuvent également être utiles :
- Pour les scripts MonoBehaviour qui prennent beaucoup de temps mais ne vous montrent pas exactement pourquoi c'est le cas, ajoutez Profils de marqueurs au code ou essayez profilage approfondi pour voir la pile d'appels complète.
- Pour les scripts qui allouent de la mémoire gérée, activez Piles d'appels d'allocation pour voir exactement d'où proviennent les allocations. Alternativement, activez le profilage approfondi ou utilisez Auditeur de projet, qui montre les problèmes de code filtrés par mémoire, afin que vous puissiez identifier toutes les lignes de code qui entraînent des allocations gérées.
- Utilisez le Débogueur de trame pour enquêter sur les causes d'un mauvais batching d'appels de dessin.
Pour des conseils complets sur l'optimisation de votre jeu, téléchargez ces guides d'experts Unity gratuitement :

Limité par le CPU : Fil de rendu
Voici un projet réel qui est limité par son thread de rendu. C'est un jeu de console avec un point de vue isométrique et un budget de trame cible de 33,33 ms.
La capture du Profiler montre qu'avant que le rendu puisse commencer sur la trame actuelle, le thread principal attend le thread de rendu, comme l'indique le marqueur Gfx.WaitForPresentOnGfxThread . Le thread de rendu soumet encore des commandes d'appels de dessin de la trame précédente et n'est pas prêt à accepter de nouveaux appels de dessin du thread principal ; il passe également du temps dans Camera.Render.
Vous pouvez faire la différence entre les marqueurs relatifs à la trame actuelle et les marqueurs d'autres trames, car ces derniers apparaissent plus sombres. Vous pouvez également voir qu'une fois que le thread principal est capable de continuer et de commencer à émettre des appels de dessin pour que le thread de rendu les traite, le thread de rendu prend plus de 100 ms pour traiter la trame actuelle, ce qui crée également un goulot d'étranglement lors de la trame suivante.
Une enquête plus approfondie a montré que ce jeu avait une configuration de rendu complexe, impliquant neuf caméras différentes et de nombreux passages supplémentaires causés par des shaders de remplacement. Le jeu rendait également plus de 130 lumières ponctuelles en utilisant un chemin de rendu avant, ce qui peut ajouter plusieurs appels de dessin transparents supplémentaires pour chaque lumière. Au total, ces problèmes combinés ont créé plus de 3000 appels de dessin par image.
Pièges communs pour les goulets d'étranglement du thread de rendu
Les causes suivantes sont courantes à examiner pour les projets qui sont liés au thread de rendu :
- Mauvaise agrégation des appels de dessin : Cela s'applique particulièrement aux anciennes API graphiques telles qu'OpenGL ou DirectX 11.
- Trop de caméras : À moins que vous ne fassiez un jeu multijoueur en écran partagé, il y a de fortes chances que vous ne deviez avoir qu'une seule caméra active.
- Mauvaise culling : Cela entraîne trop de choses dessinées. Examinez les dimensions du frustum de votre caméra et les masques de couche de culling.
Le module Profiler de rendu montre un aperçu du nombre de lots d'appels de dessin et d'appels SetPass à chaque image. Le meilleur outil pour enquêter sur les lots d'appels de dessin que votre thread de rendu envoie au GPU est le Débogueur de trame.
Outils pour résoudre les goulets d'étranglement identifiés
Bien que l'accent de ce livre électronique soit mis sur l'identification des problèmes de performance, les deux guides d'optimisation de performance complémentaires que nous avons précédemment soulignés offrent des suggestions sur la façon de résoudre les goulets d'étranglement, selon que votre plateforme cible est PC ou console ou mobile. Dans le contexte des goulets d'étranglement du thread de rendu, il convient de souligner qu'Unity offre différents systèmes et options d'agrégation en fonction des problèmes que vous avez identifiés. Voici un aperçu rapide de certaines des options que nous expliquons plus en détail dans les e-books :
- SRP Batching réduit la surcharge du CPU en stockant les données de matériau de manière persistante dans la mémoire GPU. Bien que cela ne réduise pas le nombre réel d'appels de dessin, cela rend chaque appel de dessin moins coûteux.
- Le regroupement d'instances GPU combine plusieurs instances du même maillage utilisant le même matériau en un seul appel de dessin.
- Le regroupement statique combine des maillages statiques (non mobiles) partageant le même matériau et peut donc vous donner des avantages lors de la conception d'un niveau avec de nombreux éléments statiques.
- Le tiroir résident GPU utilise automatiquement le regroupement d'instances GPU pour réduire la surcharge CPU et les appels de dessin, en regroupant des GameObjects similaires.
- Le regroupement dynamique combine de petits maillages à l'exécution, ce qui peut être un avantage sur les anciens appareils mobiles avec des coûts d'appels de dessin élevés. Cependant, l'inconvénient est que la transformation des sommets peut également être gourmande en ressources.
- Le culling d'occlusion GPU utilise des shaders de calcul pour déterminer la visibilité des objets en comparant les tampons de profondeur des images actuelles et précédentes, réduisant ainsi le rendu inutile des objets occlus sans nécessiter de données pré-cuites.
De plus, du côté CPU, des techniques telles que Camera.layerCullDistances peuvent être utilisées pour réduire le nombre d'objets envoyés au fil de rendu en éliminant les objets en fonction de leur distance par rapport à la caméra, aidant à atténuer les goulets d'étranglement CPU lors du culling de la caméra.
Ce ne sont là que quelques-unes des options disponibles. Chacune d'entre elles a des avantages et des inconvénients différents. Certaines sont limitées à certaines plateformes. Les projets doivent souvent utiliser une combinaison de plusieurs de ces systèmes et pour ce faire, il est nécessaire de comprendre comment en tirer le meilleur parti.

Limité par le CPU : Fils de travail
Les projets liés à des threads CPU autres que les threads principaux ou de rendu ne sont pas si courants. Cependant, cela peut se produire si votre projet utilise le Data-Oriented Technology Stack (DOTS), surtout si le travail est déplacé du thread principal vers des threads de travail en utilisant le système de tâches.
L'image ci-dessus est une capture du mode Play dans l'éditeur, montrant un projet DOTS exécutant une simulation de fluide de particules sur le CPU.
Cela semble être un succès à première vue. Les threads de travail sont étroitement remplis de tâches compilées avec Burst, indiquant qu'une grande quantité de travail a été déplacée du thread principal. En général, c'est une décision judicieuse.
Cependant, dans ce cas, le temps de trame de 48,14 ms et le marqueur gris WaitForJobGroupID de 35,57 ms sur le thread principal, sont des signes que tout ne va pas bien. WaitForJobGroupID indique que le thread principal a planifié des tâches à exécuter de manière asynchrone sur des threads de travail, mais il a besoin des résultats de ces tâches avant que les threads de travail aient fini de les exécuter. Les marqueurs Profiler bleus sous WaitForJobGroupID montrent le thread principal exécutant des tâches pendant qu'il attend, dans une tentative de s'assurer que les tâches se terminent plus tôt.
Bien que les travaux soient compilés avec Burst, ils effectuent toujours beaucoup de travail. Peut-être que la structure de requête spatiale utilisée par ce projet pour trouver rapidement quelles particules sont proches les unes des autres devrait être optimisée ou remplacée par une structure plus efficace. Ou, les travaux de requête spatiale peuvent être programmés pour la fin de la trame plutôt qu'au début, les résultats n'étant pas requis avant le début de la prochaine trame. Peut-être que ce projet essaie de simuler trop de particules. Une analyse plus approfondie du code des travaux est nécessaire pour trouver la solution, donc l'ajout de marqueurs de Profiler plus fins peut aider à identifier leurs parties les plus lentes.
Les travaux dans votre projet pourraient ne pas être aussi parallélisés que dans cet exemple. Peut-être que vous avez juste un long travail en cours dans un seul thread de travail. C'est bien, tant que le temps entre la programmation du travail et le moment où il doit être terminé est suffisamment long pour que le travail puisse s'exécuter. Si ce n'est pas le cas, vous verrez le thread principal se bloquer en attendant que le travail se termine, comme dans la capture d'écran ci-dessus.
Pièges courants pour les goulets d'étranglement des threads de travail
Les causes courantes des points de synchronisation et des goulets d'étranglement des threads de travail incluent :
- Travaux non compilés par le compilateur Burst
- Travaux de longue durée sur un seul thread de travail au lieu d'être parallélisés sur plusieurs threads de travail
- Temps insuffisant entre le moment dans la trame où un travail est programmé et le moment où le résultat est requis
- Plusieurs "points de synchronisation" dans une trame, qui nécessitent que tous les travaux soient terminés immédiatement
Vous pouvez utiliser la fonctionnalité Flow Events dans la vue Timeline du module Profiler d'utilisation du CPU pour enquêter sur le moment où les travaux sont programmés et quand leurs résultats sont attendus par le thread principal.
Pour plus d'informations sur l'écriture de code DOTS efficace, consultez le guide DOTS Best Practices.

Dépendance du GPU
Votre application est limitée par le GPU si le thread principal passe beaucoup de temps dans des marqueurs de Profiler comme Gfx.WaitForPresentOnGfxThread, et votre thread de rendu affiche simultanément des marqueurs comme Gfx.PresentFrame ou .WaitForLastPresent.
Le meilleur moyen d'obtenir les temps de trame GPU est d'utiliser un outil de profilage GPU spécifique à la plateforme cible, mais tous les appareils ne facilitent pas la capture de données fiables.
L'FrameTimingManager API peut être utile dans ces cas, fournissant des temps de trame de haut niveau à faible surcharge à la fois sur le CPU et le GPU.
La capture ci-dessus a été réalisée sur un téléphone mobile Android utilisant l'API graphique Vulkan. Bien que certains des temps passés dans Gfx.PresentFrame dans cet exemple puissent être liés à l'attente de VSync, la longueur extrême de ce marqueur de Profiler indique que la majorité de ce temps est passée à attendre que le GPU termine le rendu de la trame précédente.
Dans ce jeu, certains événements de gameplay ont déclenché l'utilisation d'un shader qui a triplé le nombre d'appels de dessin rendus par le GPU. Les problèmes courants à examiner lors du profilage des performances du GPU incluent :
- Des effets de post-traitement en plein écran coûteux, comme l'occlusion ambiante et le bloom
- Des shaders de fragment coûteux causés par :
- Une logique de branchement dans le code du shader
- Utiliser une précision float complète plutôt qu'une précision demi, surtout sur mobile
- Utilisation excessive de registres, ce qui affecte l'occupation des vagues des GPU
- Le sur-rendu dans la file d'attente de rendu transparent causé par :
- Un rendu d'interface utilisateur inefficace
- Utilisation de systèmes de particules qui se chevauchent ou excessifs
- Effets de post-traitement
- Des résolutions d'écran excessivement élevées, telles que :
- Des écrans 4K
- Des écrans Retina sur des appareils mobiles
- Des micro-triangles causés par :
- Une géométrie de maillage dense
- L'absence de systèmes de niveau de détail (LOD), ce qui est un problème particulier sur les GPU mobiles, mais peut également affecter les GPU PC et console.
- Des échecs de cache et une bande passante mémoire GPU gaspillée causés par :
- Textures non compressées
- Textures haute résolution sans mipmaps
- Shaders de géométrie ou de tessellation, qui peuvent être exécutés plusieurs fois par image si les ombres dynamiques sont activées
Si votre application semble être limitée par le GPU, vous pouvez utiliser le Débogueur de Trames comme un moyen rapide de comprendre les lots d'appels de dessin envoyés au GPU. Cependant, cet outil ne peut pas présenter d'informations spécifiques sur le timing du GPU, seulement comment la scène globale est construite.
La meilleure façon d'examiner la cause des goulets d'étranglement du GPU est d'examiner une capture GPU d'un profileur GPU approprié. L'outil que vous utilisez dépend du matériel cible et de l'API graphique choisie. Voir la section des outils de profilage et de débogage dans l'e-book pour plus d'informations.

Trouvez plus de meilleures pratiques et conseils dans le centre des meilleures pratiques Unity. Choisissez parmi plus de 30 guides, créés par des experts de l'industrie, ainsi que par des ingénieurs et artistes techniques de Unity, qui vous aideront à développer efficacement avec les outils et systèmes de Unity.