
Conseils en matière de profilage des performances 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 l'optimisation de votre jeu, un flux de production de profilage cohérent et complet est indispensable pour un développement de jeu efficace. Il commence par une procédure simple en trois points :
- Profilez avant d'effectuer des modifications majeures : Établissez une base de référence.
- Profilage pendant le développement : Suivez et assurez-vous que les changements ne nuisent pas aux performances ou aux budgets.
- Profil après : Prouvez que les modifications ont eu l'effet souhaité.
Cette page décrit un flux de production de profilage général pour les développeurs de jeux. Il est extrait de l'ebook, Ultimate guide to profileing Unity games, disponible en téléchargement gratuit (la version Unity 6 du guide sera bientôt disponible). L'ebook a été créé par des experts Unity externes et internes en développement, profilage et optimisation de jeux.
Dans cet article, vous pouvez en apprendre plus sur les objectifs utiles à fixer avec le profilage, les goulots d'étranglement de performances courants, comme le fait d'être dépendant du processeur ou du GPU, et comment identifier et étudier plus en détail ces situations.
Définir un budget cadre
Les joueurs mesurent souvent les performances à l'aide de la fréquence d'image, ou images par seconde (FPS), mais en tant que développeur, il est généralement recommandé d'utiliser le frame time en millisecondes à la place. Envisagez le scénario simplifié suivant :
Pendant l'exécution, votre jeu affiche 59 images en 0,75 seconde. Cependant, le rendu de l'image suivante prend 0,25 seconde. La fréquence d'image moyenne délivrée de 60 FPS sonne bien, mais en réalité les joueurs remarqueront un effet de bégaiement puisque le rendu de la dernière image prend un quart de seconde.
C'est l'une des raisons pour lesquelles il est important de viser un budget temps spécifique par image. Cela vous fournit un objectif solide à atteindre lors du profilage et de l'optimisation de votre jeu et, au final, crée une expérience plus fluide et plus cohérente pour vos joueurs.
Chaque image aura un budget temps en fonction de votre FPS cible. Une application ciblant 30 FPS doit toujours prendre moins de 33,33 ms par image (1000 ms / 30 FPS). De même, une cible de 60 FPS laisse 16.66 ms par image (1000 ms / 60 FPS).
Vous pouvez dépasser ce budget lors de séquences non interactives, par exemple, lors de l'affichage de menus de l'IU ou du chargement de scènes, mais pas pendant l'expérience de jeu. Même une seule image qui dépasse le budget cible causera des problèmes.
Note : Une fréquence d'images systématiquement élevée dans les jeux VR est essentielle pour éviter de causer des nausées ou de l'inconfort aux joueurs, et est souvent nécessaire pour que votre jeu obtienne la certification du détenteur de la plateforme.
Images par seconde : Une mesure trompeuse
Une façon courante pour les joueurs de mesurer les performances est avec la fréquence d'image, ou images par seconde. Cependant, il est recommandé d'utiliser plutôt le frame time en millisecondes. Pour comprendre pourquoi, regardez le graphique ci-dessus de FPS versus frame time.
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 tourne à 900 FPS, cela se traduit par un temps d'image de 1,111 millisecondes par image. A 450 FPS, c'est 2,222 millisecondes par image. Cela représente une différence de seulement 1,111 milliseconde par image, même si la fréquence d'image semble baisser 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 milliseconde supplémentaire par image, mais ici, la baisse de fréquence d'image semble beaucoup moins spectaculaire en pourcentage.
C'est pour cette raison que les développeurs utilisent le frame time moyen pour comparer la vitesse de jeu plutôt que FPS.
Ne vous souciez pas des FPS sauf si vous descendez en dessous de votre fréquence d'image cible. Concentrez-vous sur le frame time pour mesurer la vitesse d'exécution de votre jeu, puis respectez votre budget.
Pour en savoir plus, lisez l’article original intitulé « Robert Dunlop’s FPS versus frame time ».

Défis mobiles
Le contrôle thermique est l'un des domaines les plus importants à optimiser pour le développement d'applications pour appareils mobiles. Si le processeur ou le GPU passe trop de temps à travailler à plein régime en raison d'un code inefficace, ces puces seront chaudes. Pour éviter la surchauffe et les dommages potentiels aux puces, le système d'exploitation réduit la vitesse d'horloge de l'appareil pour lui permettre de refroidir, ce qui provoque un bégaiement des images et une mauvaise expérience utilisateur. Cette réduction des performances est connue sous le nom de thermal throttling.
Des fréquences d'image plus élevées et une exécution de code accrue (ou opérations d'accès DRAM) conduisent à une vidange accrue de la batterie et à une génération de chaleur accrue. De mauvaises performances peuvent également rendre votre jeu injouable pour des segments entiers d'appareils mobiles bas de gamme, ce qui peut faire rater des opportunités de marché.
Lorsque vous vous attaquez au problème thermique, considérez le budget avec lequel vous devez travailler comme un budget pour l'ensemble du système.
Lutter contre la limitation thermique et la vidange de la batterie en effectuant un profilage précoce pour optimiser votre jeu dès le début. Composez les paramètres de votre projet pour le matériel de votre plateforme cible afin de lutter contre les problèmes thermiques et de vidange de la batterie.
Ajuster les budgets de frame sur mobile
Un conseil général pour lutter contre les problèmes thermiques de l'appareil sur des durées de jeu prolongées est de laisser une durée d'inactivité d'environ 35 %. Cela donne aux puces mobiles le temps de refroidir et permet d'éviter que la batterie ne se vide trop. En utilisant un délai cible de 33,33 ms par image (pour 30 FPS), le budget d'image pour les appareils mobiles sera d'environ 22 ms par image.
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 délai d'image cible de (1000 ms / 60) * 0,65 = 10,83 ms. Un objectif difficile à atteindre sur de nombreux appareils mobiles et qui viderait 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 référez-vous à la section « Définir un budget d'image » dans l'ebook de profilage pour plus de détails sur le frame time.
La mise à l'échelle de la fréquence sur les puces mobiles peut rendre difficile l'identification de vos allocations budgétaires de frame idle lors du profilage. Vos améliorations et optimisations peuvent avoir un effet positif net, mais l'appareil mobile peut réduire la fréquence et, par conséquent, fonctionner plus froidement. Utilisez des outils personnalisés tels que FTrace ou Perfetto pour surveiller les fréquences des puces mobiles, les temps d'inactivité et la mise à l'échelle avant et après les optimisations.
Tant que vous respectez votre budget total de frame time pour votre FPS cible (disons 33,33 ms pour 30 FPS) et que vous voyez votre appareil fonctionner moins ou enregistrer des températures plus basses pour maintenir cette fréquence d'image, alors vous êtes sur la bonne voie.
Une autre raison d'ajouter de l'espace pour cadrer le budget sur les appareils mobiles est de tenir compte des fluctuations de température du monde réel. Par une journée chaude, un appareil mobile chauffe et a du mal à dissiper la chaleur, ce qui peut conduire à une limitation thermique et à de mauvaises performances du jeu. Réservez un pourcent du budget de base pour é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 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éduire la fréquence d'image
- Réduire la résolution d'affichage si possible
- Utiliser des maillages plus simples avec un nombre de sommets réduit et une précision des attributs
- Utiliser la compression de texture et le mipmapping
Lorsque vous devez vous concentrer sur des appareils tirant parti du processeur ARM ou du matériel du GPU, les outils ARM Performance Studio (plus précisément Streamline Performance Analyzer) incluent d'excellents compteurs de performances pour identifier les problèmes de bande passante mémoire. Les compteurs disponibles sont répertoriés et expliqués pour chaque génération de GPU ARM dans un guide de l'utilisateur correspondant, par exemple le Mali-G710 Performance Counter Reference Guide (Guide de référence des compteurs de performances Mali-G710). Notez que le profilage du GPU ARM Performance Studio nécessite un GPU ARM Immortalis ou Mali.
Établir des niveaux d'équipement pour l'analyse comparative
En plus d'utiliser des outils de profilage spécifiques à chaque plateforme, établissez des niveaux ou un appareil de qualité minimale 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 pouvez décider de prendre en charge trois niveaux avec des contrôles de qualité qui activent ou désactivent les fonctionnalités en fonction du matériel cible. Vous optimisez ensuite pour les spécifications d'appareil les plus basses de chaque niveau. Autre exemple : si vous développez un jeu pour consoles, assurez-vous d'utiliser des versions plus anciennes et plus récentes.
Notre dernier guide d'optimisation mobile contient de nombreux conseils et astuces qui vous aideront à réduire la limitation thermique et à augmenter la durée de vie de la batterie pour les 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 une présentation de haut niveau des catégories telles que le rendu, les scripts, la physique et les allocations de Garbage Collection (GC). Une fois que vous aurez identifié les sujets de préoccupation, vous pourrez approfondir vos connaissances. Utilisez ce pass de haut niveau pour collecter des données et prendre des notes sur les problèmes de performances les plus critiques, y compris les scénarios qui provoquent des allocations gérées non désirées ou une utilisation excessive du processeur dans votre boucle de jeu principale.
Vous devrez d'abord rassembler les piles d'appels pour les marqueurs GCAlloc. Si vous ne connaissez pas ce processus, vous trouverez quelques conseils et astuces dans la section intitulée « Localisation des allocations de mémoire récurrentes au cours du cycle de vie de l'application » dans l'ebook.
Si les piles d'appels signalées ne sont pas assez détaillées pour trouver la source des allocations ou d'autres ralentissements, vous pouvez effectuer une deuxième session de profilage avec Deep Profiling activé afin de trouver la source des allocations. Nous abordons le profilage approfondi plus en détail dans l'ebook, mais en résumé, il s'agit d'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 une surcharge nettement plus élevée par rapport au profilage standard.
Lorsque vous recueillez des notes sur les « délinquants » du frame time, assurez-vous de noter comment ils se comparent par rapport au reste du frame. Cet impact relatif peut être faussé lorsque le profilage en profondeur est activé, car le profilage en profondeur ajoute une surcharge importante en instrumentant chaque appel de méthode.
Profilez en avant-première
Bien que vous deviez toujours effectuer votre profilage tout au long du cycle de développement de votre projet, les gains les plus significatifs sont obtenus lorsque vous commencez dans les premières phases.
Créez votre profil 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 pour comparer. Si les performances piquent du nez, vous serez en mesure de repérer facilement quand les choses tournent mal et de résoudre le problème.
Bien que le profilage dans l'Éditeur vous permette d'identifier facilement les principaux problèmes, les résultats de profilage les plus précis proviennent toujours de l'exécution et des compilations de profilage sur les appareils cibles, ainsi que de l'utilisation d'outils spécifiques aux plateformes pour étudier les caractéristiques matérielles de chaque plateforme. Cette combinaison vous offrira une vue globale des performances de l'application sur tous vos appareils cibles. Par exemple, vous pouvez être dépendant du GPU sur certains appareils mobiles, mais dépendant du CPU sur d'autres, et vous ne pouvez l'apprendre qu'en mesurant sur ces appareils.
Identification des problèmes de performance
Ici, téléchargez la version PDF imprimable de ce graphique.
Le but du profilage est d'identifier les goulots d'étranglement comme cibles à optimiser. Si vous vous fiez à des suppositions, vous pouvez finir par optimiser des parties du jeu qui ne sont pas des goulots d'étranglement, ce qui n'améliore que peu ou pas du tout les performances globales. Certaines « optimisations » peuvent même dégrader les performances globales de votre jeu, tandis que d'autres peuvent nécessiter beaucoup de travail, mais produire des résultats négligeables. L'essentiel est d'optimiser l'impact de votre investissement de temps ciblé.
L'organigramme ci-dessus illustre le processus de profilage initial et les sections qui le suivent fournissent des informations détaillées sur chaque étape. Ils présentent également des captures Profiler provenant de projets Unity réels pour illustrer les types de choses à rechercher.
Pour obtenir une image globale de toute l'activité du processeur, y compris lorsqu'il attend le GPU, utilisez la vue Timeline dans le module CPU du Profiler. Familiarisez-vous avec les marqueurs Profiler courants pour interpréter correctement les captures. Certains des marqueurs du Profiler peuvent apparaître différemment en fonction de votre plateforme cible. Passez donc du temps à explorer les captures de votre jeu sur chacune de vos plateformes cibles pour avoir une idée de ce à quoi ressemble une capture « normale » pour votre projet.
Les performances d'un projet sont liées à la puce et/ou au thread qui prend le plus de temps. C'est là que les efforts d'optimisation doivent se concentrer. Par exemple, imaginez les scénarios suivants pour un jeu avec un budget cible de 33,33 ms et VSync activé :
- Si la durée d'image du processeur (hors VSync) est de 25 ms et la durée du processeur de 20 ms, pas de problème ! Vous êtes lié au processeur, mais tout respecte le budget, et l'optimisation des choses n'améliorera pas la fréquence d'image (à moins de passer à la fois le processeur et le GPU en dessous de 16,66 ms et de sauter jusqu"à 60 FPS).
- Si la durée du processeur est de 40 ms et le GPU de 20 ms, vous êtes lié au processeur et devrez optimiser les performances du processeur. L'optimisation des performances du GPU n'aidera pas. En fait, vous pouvez déplacer une partie du travail du processeur vers le GPU, par exemple, en utilisant des shaders de calcul au lieu du code C#, le cas échéant, pour équilibrer les choses.
- Si la durée du processeur est de 20 ms et le GPU de 40 ms, vous êtes dépendant du GPU et devez optimiser le travail du GPU.
- Si le processeur et le GPU sont tous deux à 40 ms, vous êtes lié par les deux et devrez les optimiser en dessous de 33,33 ms pour atteindre 30 FPS.
Découvrez ces ressources qui explorent plus en détail le fait d'être dépendant du processeur ou du GPU :

Êtes-vous dans le budget ?
Le profilage et l'optimisation de votre projet en amont et souvent tout au long du développement vous aideront à vous assurer que tous les threads du processeur de votre application et le délai global du processeur respectent le budget de base. La question qui guidera ce processus est de savoir si vous respectez le budget-cadre ou non.
Ci-dessus, 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 vise 60 FPS sur les téléphones mobiles à hautes spécifications, et 30 FPS sur les téléphones à moyennes/basse spécifications, comme celui de cette capture.
Notez que près de la moitié du temps sur l'image sélectionnée est occupée par le marqueur jaune WaitForTargetFPS Profiler. L'application a défini Application.targetFrameRate à 30 FPS, et VSync est activé. Le travail de traitement proprement dit sur le thread principal se termine aux alentours de 19 ms, et le reste du temps est consacré à attendre que le reste des 33,33 ms s'écoule avant de commencer l'image suivante. Bien que ce temps soit représenté avec un marqueur Profiler, le thread principal du processeur est essentiellement inactif pendant ce temps, ce qui permet au processeur de refroidir et d'utiliser un minimum d'énergie de la batterie.
Le marqueur à surveiller peut être différent sur d'autres plateformes ou si VSync est désactivé. L'important est de vérifier si le thread principal s'exécute dans votre budget d'image ou exactement sur votre budget d'image 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 tourne au ralenti dans Gfx.WaitForGfxCommandsFromMainThread, ce qui indique les moments où il a terminé d'envoyer des requêtes de requête au GPU sur une image, et attend d'autres requêtes de requête de requête du CPU sur la suivante. De même, bien que le thread Job Worker 0 passe un certain temps dans Canvas.GeometryJob, la plupart du temps il est inactif. Autant de signes d'une application qui respecte confortablement le budget.
Si votre jeu est au budget cadre
Si vous respectez le budget de base, y compris les ajustements apportés au budget pour tenir compte de l'utilisation de la batterie et de la limitation thermique, vous avez terminé les principales tâches de profilage. Vous pouvez conclure en exécutant le profileur de mémoire pour vous assurer que l'application respecte également son budget mémoire.
L'image ci-dessus montre un jeu qui fonctionne correctement avec le budget d'image de ~22 ms nécessaire à 30 FPS sans surchauffe. Notez que WaitForTargetfps padding le temps du thread principal jusqu'à VSync et les temps d'inactivité gris dans le thread de rendu et le thread worker. Notez également que l'intervalle VBlank peut être observé en regardant les heures de fin de Gfx.Present image par image, et que vous pouvez établir une échelle de temps dans la zone Timeline ou sur la règle Time en haut pour mesurer de l'une à la suivante.

Dépendance du GPU
Si votre jeu n'est pas dans le budget de frame du processeur, l'étape suivante est d'examiner quelle partie du processeur est le goulot d'étranglement, en d'autres termes, quel thread est le plus occupé.
Il est rare que l'ensemble de la charge de travail du processeur soit le goulot d'étranglement. Les processeurs modernes ont un certain nombre de cœurs différents, capables d'exécuter le travail indépendamment et simultanément. Différents threads peuvent s'exécuter sur chaque cœur de processeur. Une application Unity complète utilise une gamme de threads à des fins différentes, mais ceux qui sont les plus courants pour trouver des problèmes de performances sont :
- The main thread: C'est là que la plupart des logiques/scripts de jeu effectuent leur travail par défaut. La plupart des systèmes Unity, tels que la physique, l'animation, l'IU et les étapes initiales du rendu, s'exécutent ici.
- Le thread render : Cela gère le travail de préparation (par exemple, quels objets de la scène sont visibles par la caméra et qui sont exclus/invisibles parce qu'ils sont en dehors du tronc de vue, occlus ou gommés par d'autres critères) qui doit se produire avant d'envoyer des instructions de rendu au GPU.
- Pendant le processus de rendu, le thread principal examine la scène et effectue le gommage de la caméra, le tri de profondeur et le traitement par lots des requêtes, ce qui donne une liste de choses à rendre. Cette liste est transmise au thread de rendu, qui la traduit de la représentation interne Unity agnostique de la plateforme aux appels spécifiques de l'API graphique nécessaires pour instruire le GPU sur une plateforme particulière.
- Les fils Job worker : Les développeurs peuvent utiliser le système de tâches pour programmer certains types de travaux à exécuter sur les threads de travail, ce qui réduit la charge de travail sur le thread principal. Certains des systèmes et fonctionnalités de Unity utilisent également le système de tâches, comme la physique, l'animation et le rendu.
Exemple concret d'optimisation de thread principal
L'image ci-dessous montre à quoi cela peut ressembler dans un projet lié par le fil principal. Ce projet s'exécute sur un Meta Quest 2, qui vise normalement des budgets d'images de 13,88 ms (72 FPS) ou même 8,33 ms (120 FPS), car les fréquences d'images élevées sont importantes pour éviter le mal des mouvements dans les appareils VR. Cependant, même si ce jeu visait 30 FPS, force est de constater que ce projet est en difficulté.
Bien que le thread de rendu et les threads worker ressemblent à l'exemple qui est dans le budget de frame, le thread principal est clairement occupé à travailler pendant toute la frame. Même en tenant compte de la faible surcharge du profiler en fin d'image, le thread principal est occupé pendant plus de 45 ms, ce qui signifie que ce projet atteint des fréquences d'image inférieures à 22 FPS. Aucun marqueur n'affiche le thread principal en attente de VSync, il est occupé pour toute l'image.
La prochaine étape de l'enquête consiste à identifier les parties de la monture qui prennent le plus de temps et à comprendre pourquoi c'est le cas. Sur cette image, le rendu PostLateUpdate.FinishFrameRendering prend 16.23 ms, soit plus que le budget total de l'image. Une inspection plus approfondie révèle qu'il existe cinq cas d'un marqueur appelé Inl_RenderCameraStack, indiquant que cinq caméras sont actives et rendant la scène. Chaque caméra de Unity invoquant l'ensemble du pipeline de rendu, y compris le gommage, le tri et le traitement par lots, la tâche prioritaire de 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 cette image.
Dans l'aperçu Timeline, les sections colorées magenta indiquent les points où les scripts allouent de la mémoire de pile gérée. Passer dans 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 cette image. Cependant, c'est une mesure imprécise de l'impact des allocations de mémoire sur les performances de votre processeur.
Les marqueurs GC ne sont pas chronométrés en enregistrant un point de début et de fin comme les exemples typiques du Profiler. 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 artificiel aux marqueurs GC uniquement pour s'assurer qu'ils sont visibles dans les vues du 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 plus clairement l'impact, placez des marqueurs Profiler autour du code qui effectue l'allocation. Dans le profilage profond, les espaces entre les échantillons GC colorés en magenta dans la vue Timeline donnent une indication du temps qu'ils ont pu prendre.
De plus, allouer une nouvelle mémoire peut avoir des effets négatifs sur les performances qui sont plus difficiles à mesurer et à leur attribuer directement :
- Demander une nouvelle mémoire au système peut affecter le budget d'énergie sur un appareil mobile, ce qui peut conduire au ralentissement du processeur ou du GPU.
- La nouvelle mémoire a probablement besoin d'être chargée dans le cache L1 du processeur et, par conséquent, de sortir les lignes de cache existantes.
- La récupération de mémoire incrémentielle ou synchrone peut être déclenchée directement ou avec retard car l'espace libre existant dans la mémoire gérée est éventuellement dépassé.
Au début de la frame, quatre instances de Physics.FixedUpdate s'ajoutent jusqu'à 4,57 ms. Plus tard, LateBehaviourUpdate (appels à MonoBehaviour.LateUpdate()) prend 4 ms, et les animateurs environ 1 ms. Pour que ce projet atteigne le budget d'image et la fréquence souhaités, tous ces principaux problèmes de thread doivent être étudiés pour trouver les optimisations appropriées.
Écueils courants pour le goulot d'étranglement du thread principal
Les gains de performances les plus importants seront réalisés en optimisant les éléments qui prennent le plus de temps. Les domaines suivants sont souvent des domaines fructueux à rechercher pour optimiser dans les projets qui sont liés à des threads principaux :
- Calculs physiques
- Mises à jour des scripts MonoBehaviour
- Attribution et/ou collecte des ordures
- Gommage et rendu de caméra sur le thread principal
- Traitement inefficace des requêtes
- Mises à jour, mises en page et reconstructions de l'IU
- Animation
Lisez nos guides d'optimisation qui offrent une longue liste de conseils pratiques pour optimiser certains des pièges les plus courants :
Selon la question sur laquelle vous souhaitez enquêter, d'autres outils peuvent également vous être utiles :
- Pour les scripts MonoBehaviour qui prennent beaucoup de temps mais ne vous montrent pas exactement pourquoi c'est le cas, ajoutez des marqueurs de profilage au code ou essayez le profilage profond pour voir la pile d'appels complète.
- Pour les scripts qui allouent de la mémoire gérée, activez Allocation Call Stacks pour voir exactement d'où proviennent les allocations. Vous pouvez également activer le profilage approfondi ou utiliser Project Auditor, qui montre les problèmes de code filtrés par la mémoire, afin que vous puissiez identifier toutes les lignes de code qui aboutissent à des allocations gérées.
- Utilisez le débogueur d'images pour étudier les causes d'un mauvais traitement par lots des requêtes.
Pour des conseils complets sur l'optimisation de votre jeu, téléchargez gratuitement ces guides d'experts Unity :

Dépendance du GPU : Fil de rendu
Voici un projet réel qui est lié par son thread de rendu. Il s'agit d'un jeu pour console avec un point de vue isométrique et un budget cible de 33,33 ms.
La capture du Profiler montre qu'avant que le rendu puisse commencer sur l'image courante, le thread principal attend le thread de rendu, comme indiqué par le marqueur Gfx.WaitForPresentOnGfxThread. Le thread de rendu soumet toujours des commandes d'appel de requêtes de l'image précédente et n'est pas prêt à accepter de nouveaux appels de requêtes du thread principal ; il passe également du temps dans Camera.Render.
Vous pouvez faire la différence entre les marqueurs relatifs à l'image actuelle et les marqueurs des autres images, car ces derniers semblent plus foncés. Vous pouvez également voir qu'une fois que le thread principal est capable de continuer et de commencer à émettre des requêtes pour le thread de rendu à traiter, le thread de rendu prend plus de 100 ms pour traiter l'image actuelle, ce qui crée également un goulot d'étranglement lors de l'image suivante.
Des recherches plus poussées ont montré que ce jeu avait une configuration de rendu complexe, impliquant neuf caméras différentes et de nombreuses passes supplémentaires causées par des shaders de remplacement. Le jeu rendait également plus de 130 éclairages de points en utilisant un chemin de rendu direct, qui peut ajouter plusieurs requêtes transparentes supplémentaires pour chaque éclairage. Au total, ces problèmes se sont combinés pour créer plus de 3000 requêtes par image.
Écueils courants pour les goulots d'étranglement des threads de rendu
Voici les causes les plus communes à étudier pour les projets qui sont liés par des threads de rendu :
- Mauvais traitement par lots : Cela vaut particulièrement pour les anciennes API graphiques telles que OpenGL ou DirectX 11.
- Trop de caméras : À moins de créer un jeu Multiplayer sur écran scindé, il est fort probable que vous ne devriez jamais avoir qu'une seule caméra active.
- Mauvais gommage : Il en résulte trop de choses dessinées. Examinez les dimensions des troncs de votre caméra et les masques de couche de gommage.
Le module Rendering Profiler affiche un aperçu du nombre de lots de requêtes et de requêtes SetPass à chaque image. Le Frame Debugger est le meilleur outil pour déterminer quels lots de requêtes votre thread de rendu émet vers le GPU.
Outils pour résoudre les goulots d'étranglement identifiés
Bien que cet ebook s'attache à identifier les problèmes de performance, les deux guides complémentaires d'optimisation des performances que nous avons précédemment mis en évidence proposent des suggestions sur la façon de résoudre les goulots d"étranglement, selon que votre plateforme cible est PC ou console ou mobile. Dans le contexte des goulots d'étranglement des threads de rendu, il convient de souligner que Unity propose différents systèmes de traitement par lots et options 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 ebooks :
- SRP Batching réduit la surcharge du processeur en stockant les données matérielles de manière persistante dans la mémoire du GPU. Bien que cela ne réduise pas le nombre réel de requêtes, cela rend chaque requête moins coûteuse.
- L'instanciation GPU combine plusieurs instances d'un même maillage utilisant le même matériau en un seul appel de tirage.
- Le batching statique combine des maillages statiques (immobiles) partageant le même matériau et peut donc vous donner des gains lorsque vous travaillez avec une conception de niveaux avec de nombreux éléments statiques.
- Le tiroir résident GPU utilise automatiquement l'instanciation GPU pour réduire la surcharge du processeur et les requêtes, en regroupant des GameObjects similaires.
- Le batching dynamique combine de petits maillages lors de l'exécution, ce qui peut constituer un avantage sur les appareils mobiles plus anciens avec des coûts d'appel élevés. Cependant, l'inconvénient est que la transformation de sommet peut également être gourmande en ressources.
- Le GPU occlusion culling utilise des compute shaders pour déterminer la visibilité des objets en comparant les tampons de profondeur des images actuelles et précédentes, ce qui réduit le rendu inutile des objets occlus sans nécessiter de données précalculées.
En outre, en ce qui concerne le processeur, des techniques telles que Camera.layerCullDistances peuvent être utilisées pour réduire le nombre d'objets envoyés vers le thread de rendu en gommant les objets en fonction de leur distance par rapport à la caméra, ce qui permet de réduire les goulots d'étranglement du processeur lors du gommage de la caméra.
Ce ne sont que quelques-unes des options disponibles. Chacun d'entre eux a ses propres avantages et inconvénients. Certains sont limités à certaines plateformes. Les projets doivent souvent utiliser une combinaison de plusieurs de ces systèmes et, pour ce faire, comprendre comment en tirer le meilleur parti.

Dépendance du GPU : Fils de discussion
Les projets liés par des threads CPU autres que les threads main ou render ne sont pas si courants. Cependant, elle peut survenir si votre projet utilise la pile technologique orientée vers les données (DOTS), en particulier si le travail est déplacé du thread principal vers des threads de travail à l'aide du système de tâches.
L'image ci-dessus est une capture du mode lecture dans l'éditeur, montrant un projet DOTS exécutant une simulation de fluide de particules sur le processeur.
Cela ressemble à une réussite à première vue. Les threads de travail sont serrés avec les tâches compilées Burst, ce qui indique que beaucoup de travail a été déplacé hors du thread principal. Généralement, c'est une bonne décision.
Cependant, dans ce cas, le frame time 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 des tâches planifiées pour s'exécuter de manière asynchrone sur les worker threads, mais qu'il a besoin des résultats de ces tâches avant que les worker threads ne les aient terminées. Les marqueurs bleus du Profiler sous WaitForJobGroupID affichent le thread principal exécutant les tâches pendant qu'il attend, pour essayer de s'assurer que les tâches se terminent plus rapidement.
Bien que les emplois soient compilés par Burst, ils font encore 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 échangée pour une structure plus efficace. Vous pouvez également programmer les tâches de requête spatiale pour la fin de l'image plutôt que pour le début, les résultats ne devant pas être obtenus avant le début de l'image suivante. Ce projet tente peut-être de simuler un trop grand nombre de particules. Une analyse plus approfondie du code des tâches est nécessaire pour trouver la solution, de sorte que l'ajout de marqueurs Profiler à grain plus fin peut aider à identifier leurs parties les plus lentes.
Les tâches de votre projet peuvent ne pas être aussi parallélisées que dans cet exemple. Peut-être avez-vous juste un long travail en cours dans un seul thread de travailleur. Cela ne pose pas de problème, tant que le temps entre le travail planifié et le temps nécessaire à sa réalisation est suffisamment long pour qu'il puisse s'exécuter. Si ce n'est pas le cas, vous verrez le thread principal s'arrêter en attendant la fin de la tâche, comme dans la capture d'écran ci-dessus.
Pièges courants pour les goulots d'étranglement des threads de travailleurs
Les causes courantes des points de synchronisation et des goulots d'étranglement des threads de travail sont les suivantes :
- Tâches non compilées par le compilateur Burst
- Tâches de longue durée sur un thread unique au lieu d'être parallélisées sur plusieurs threads
- Insuffisance de temps entre le point de la trame où une tâche est planifiée et le point où le résultat est requis
- Plusieurs « points de synchronisation » dans une image, qui exigent que toutes les tâches soient terminées immédiatement
Vous pouvez utiliser la fonctionnalité Flow Events dans la fenêtre Timeline du module du profileur d'utilisation du processeur pour déterminer quand les tâches sont planifiées et quand leurs résultats sont attendus par le thread principal.
Pour en savoir plus sur la rédaction d'un code DOTS efficace, consultez le guide des bonnes pratiques DOTS.

Dépendance du GPU
Votre application est liée au GPU si le thread principal passe beaucoup de temps dans les marqueurs du profiler comme Gfx.WaitForPresentOnGfxThread et que votre thread de rendu affiche simultanément des marqueurs comme Gfx.PresentFrame ou .WaitForLastPresent.
La meilleure façon d'obtenir des frame times 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'API FrameTimingManager peut être utile dans ces cas, car elle permet de réduire les surcharges et d'obtenir des frame times de haut niveau sur le processeur 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 qu'une partie du temps passé dans Gfx.PresentFrame dans cet exemple puisse être liée à l'attente de VSync, la longueur extrême de ce marqueur Profiler indique que la majorité de ce temps est passé à attendre que le GPU termine le rendu de l'image précédente.
Dans ce jeu, certains événements déclenchaient l'utilisation d'un shader qui triplait le nombre de requêtes rendues par le GPU. Les problèmes courants à étudier lors du profilage des performances du GPU sont les suivants :
- Effets de post-traitement plein écran coûteux, comme l'occlusion ambiante et Bloom
- Les shaders de fragments coûteux causés par :
- Logique de ramification dans le code du shader
- Utiliser la précision full float plutôt que la demi-précision, surtout sur mobile
- Utilisation excessive des registres, affectant l'occupation du front d'onde des GPU
- Overdraw dans la file d'attente de rendu transparent causé par :
- Rendu IU inefficace
- Superposition ou utilisation excessive des systèmes de particules
- Effets de post-traitement
- Résolutions d'écran excessivement élevées, telles que :
- Écrans 4K
- Écrans Retina sur appareils mobiles
- Microtriangles causés par :
- Géométrie de maillage dense
- Le manque de systèmes de niveau de détail (LOD), qui est un problème particulier sur les GPU mobiles, mais qui peut affecter aussi les GPU PC et consoles
- Le cache passe à côté et gaspille de la bande passante de la mémoire du GPU à cause de :
- 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 liée au GPU, vous pouvez utiliser Frame Debugger pour comprendre rapidement les lots de requêtes envoyés au GPU. Cependant, cet outil ne peut pas présenter d'informations spécifiques sur le timing du GPU, seulement la façon dont la scène globale est construite.
La meilleure façon de rechercher la cause des goulots d'étranglement du GPU est d'examiner une capture du GPU à partir d'un profiler GPU approprié. L'outil que vous utilisez dépend du matériel cible et de l'API graphique choisie. Consultez la section Outils de profilage et de débogage dans l'ebook pour en savoir plus.


Découvrez d'autres bonnes pratiques et conseils sur le hub Unity. Choisissez parmi plus de 30 guides, créés par des experts du secteur, des ingénieurs et des infographistes techniques Unity, qui vous aideront à développer efficacement avec les ensembles d'outils et les systèmes Unity.