Profilage de mémoire dans Unity
Profilant et affinant les performances de votre jeu pour une large gamme de plates-formes et d'appareils, vous pouvez élargir votre base de joueurs et augmenter vos chances de succès.
Cette page fournit des informations sur deux outils pour analyser l'utilisation de la mémoire dans votre application dans Unity : le module Memory Profiler intégré et le package Memory Profiler, un package Unity que vous pouvez ajouter à votre projet.
Les informations ici sont extraites du livre électronique, Ultimate guide to profiling Unity games, disponible gratuitement en téléchargement. Le livre électronique a été créé par des experts externes et internes d'Unity en développement, profilage et optimisation de jeux.
Lisez la suite pour en savoir plus sur le profilage mémoire dans Unity.
Le profilage de la mémoire est utile pour tester les limitations de mémoire de la plate-forme matérielle, réduire le temps de chargement et les plantages, et rendre votre projet compatible avec les anciens appareils. Il peut également être pertinent si vous souhaitez améliorer les performances du processeur/GPU en apportant des modifications qui augmentent réellement l'utilisation de la mémoire. Il est largement étranger aux performances d'exécution.
Il existe deux façons d'analyser l'utilisation de la mémoire dans votre application dans Unity.
Le module Profiler de mémoire : Il s'agit d'un module profiler intégré qui vous donne des informations de base sur l'endroit où votre application utilise de la mémoire.
Package du Profileur de mémoire Il s'agit d'un package Unity que vous pouvez ajouter à votre projet. Il ajoute une fenêtre Memory Profiler supplémentaire à l'éditeur Unity, que vous pouvez ensuite utiliser pour analyser encore plus en détail l'utilisation de la mémoire dans votre application. Vous pouvez stocker et comparer des captures d'écran, afin de localiser les fuites de mémoire ou de visualiser sa configuration pour déterminer d'éventuels problèmes de fragmentation.
Grâce à ces outils intégrés, vous pouvez surveiller l'utilisation de la mémoire, localiser les zones d'une application où l'utilisation de la mémoire est plus élevée que prévu et trouver et améliorer la fragmentation de la mémoire.
La compréhension et la budgétisation des limites de mémoire de vos périphériques cibles sont essentielles pour le développement multiplateforme. Lorsque vous concevez des scènes et des niveaux, respectez le budget mémoire défini pour chaque périphérique cible. En fixant des limites et des directives, vous pouvez vous assurer que votre application fonctionne bien dans les limites des spécifications matérielles de chaque plateforme.
Vous trouverez les spécifications de la mémoire du périphérique dans la documentation du développeur. Par exemple, selon la documentation, la console Xbox One est limitée à 5 Go de mémoire disponible maximale pour les jeux exécutés au premier plan.
Il peut également être utile de définir des budgets de contenu autour de la complexité du maillage et du shader, ainsi que pour la compression de texture. Tout cela joue sur la quantité de mémoire allouée. Ces chiffres budgétaires peuvent être consultés au cours du cycle de développement du projet.
Déterminer les limites physiques de RAM
Chaque plateforme cible a une limite de mémoire, et une fois que vous la connaissez, vous pouvez définir un budget mémoire pour votre application. Utilisez le Memory Profiler pour regarder un instantané de capture. Les ressources matérielles (voir image ci-dessus) indiquent les tailles de mémoire vive physique (RAM) et de mémoire vive vidéo (VRAM). Ce chiffre ne tient pas compte du fait que tout cet espace pourrait ne pas être disponible. Cependant, il fournit une figure de ballpark utile pour commencer à travailler avec.
C’est une bonne idée de croiser les spécifications matérielles de référence pour les plates-formes cibles, car les chiffres affichés ici peuvent ne pas toujours donner une image complète. Le matériel du kit développeur a parfois plus de mémoire, ou vous travaillez peut-être avec du matériel ayant une architecture mémoire unifiée.
Identifiez le matériel avec les spécifications les plus basses en termes de mémoire vive pour chaque plate-forme que vous prenez en charge, et utilisez-le pour guider votre décision de budget mémoire. Rappelez-vous que toute cette mémoire physique n'est peut-être pas disponible. Par exemple, une console pourrait avoir un hyperviseur fonctionnant pour supporter les anciens jeux qui pourraient utiliser une partie de la mémoire totale. Pensez à un pourcentage (p. ex. 80 % du total) à utiliser. Pour les plates-formes mobiles, vous pouvez également envisager de diviser en plusieurs niveaux de spécifications pour prendre en charge une meilleure qualité et des fonctionnalités pour ceux qui ont des appareils plus haut de gamme.
Une fois un budget mémoire défini, pensez à définir des budgets mémoire par équipe. Par exemple, les artistes de votre environnement obtiennent une certaine quantité de mémoire à utiliser pour chaque niveau ou scène qui est chargé, l'équipe audio obtient l'allocation de mémoire pour la musique et les effets sonores, et ainsi de suite.
Il est important d’être flexible avec les budgets à mesure que le projet avance. Si une équipe ne respecte pas son budget, attribuez le surplus à une autre équipe si cela peut améliorer les domaines de jeu qu’elle développe.
Une fois que vous avez décidé et défini des budgets mémoire pour vos plateformes cibles, l'étape suivante consiste à utiliser des outils de profilage pour vous aider à surveiller et suivre l'utilisation de la mémoire dans votre jeu.
Le module Memory Profiler fournit deux vues: Simple et détaillé. Utilisez la vue Simple pour obtenir une vue de haut niveau de l'utilisation de la mémoire pour votre application. Si nécessaire, passez à la vue détaillée pour creuser davantage.
Simple
Le chiffre Total Reserved Memory est le « Total Tracked by Unity Memory ». Il inclut de la mémoire qu'Unity a réservée mais n'utilise pas en ce moment (ce chiffre est le Total Used Memory).
La figure System Used Memory est ce que l'OS considère comme étant utilisé par votre application. Si jamais cette figure affiche 0, sachez que cela indique que le compteur Profiler n'est pas implémenté sur la plateforme que vous profilez. Dans ce cas, le meilleur indicateur à utiliser est la mémoire réservée totale. Il est également recommandé de passer à un outil de profilage de plateforme natif pour obtenir des informations détaillées sur la mémoire dans ces cas.
Pour savoir quelle quantité de mémoire est utilisée par votre exécutable, les DLL et la machine virtuelle Mono, les chiffres de mémoire image par image ne la couperont pas. Utilisez une capture instantanée détaillée pour creuser ce genre de panne de mémoire.
remarque L'arbre de référence de la vue détaillée du module Memory Profiler affiche uniquement les références natives. Les références des objets de types héritant de UnityEngine.Object peuvent apparaître avec le nom de leurs shells gérés. Cependant, ils peuvent apparaître uniquement parce qu'ils ont des objets autochtones en dessous d'eux. Vous ne verrez pas nécessairement de type géré. Prenons comme exemple un objet avec un Texture2Din un de ses champs comme référence. En utilisant cette vue, vous ne verrez pas non plus quel champ contient cette référence. Pour ce genre de détail, utilisez le Memory Profiler Package.
Pour déterminer à un niveau élevé le moment où l’utilisation de la mémoire commence à approcher les budgets de la plate-forme, utilisez le calcul « dos de la serviette » suivant :
Système utilisé Mémoire (ou mémoire totale réservée si le système utilisé affiche 0) + tampon ballpark de mémoire non suivie / mémoire totale de la plate-forme
Lorsque ce chiffre commence à approcher 100 % du budget mémoire de votre plateforme, utilisez le package Memory Profiler pour comprendre pourquoi.
De nombreuses fonctionnalités du module Memory Profiler ont été remplacées par le package Memory Profiler, mais vous pouvez toujours utiliser le module pour compléter vos efforts d'analyse de la mémoire.
Par exemple :
- Pour repérer les allocations du GC : Bien que ceux-ci apparaissent dans le module, ils sont plus faciles à retrouver en utilisant Project Auditor ou Deep Profiling.
- Pour regarder rapidement la taille Utilisé/Réservé du tas
- Analyse de la mémoire Shader
Rappelez-vous de profiler sur l'appareil qui a les spécifications les plus basses pour votre plate-forme cible globale lorsque vous définissez un budget mémoire. Surveillez étroitement l'utilisation de la mémoire, en gardant à l'esprit vos limites cibles.
Vous voudrez généralement profiler en utilisant un système de développement puissant avec beaucoup de mémoire disponible (l'espace pour stocker de gros instantanés de mémoire ou charger et enregistrer ces instantanés rapidement est important).
Le profilage mémoire est une bête différente par rapport au profilage CPU et GPU en ce sens qu'il peut entraîner lui-même des frais supplémentaires de mémoire. Vous devrez peut-être profiler la mémoire sur des périphériques haut de gamme (avec plus de mémoire), mais attention en particulier à la limite de budget mémoire pour la spécification cible bas de gamme.
Points à considérer lors du profilage pour l'utilisation de la mémoire:
- Les paramètres tels que les niveaux de qualité, les niveaux graphiques et les variantes AssetBundle peuvent avoir une utilisation différente de la mémoire sur des appareils plus puissants. Par exemple :
- Les paramètres Niveau de qualité et Graphiques pourraient affecter la taille des Textures de rendu utilisées pour les cartes ombres.
- La mise à l'échelle de la résolution pourrait affecter la taille des tampons d'écran, des textures de rendu et des effets de post-traitement.
- Le réglage de la qualité des textures pourrait affecter la taille de toutes les textures.
- La LOD maximale pourrait affecter les modèles et plus encore.
- Si vous avez des variantes AssetBundle comme une version HD (Haute Définition) et une version SD (Définition Standard) et choisissez laquelle utiliser en fonction des spécifications de l'appareil, vous pouvez également obtenir différentes tailles d'actifs en fonction de l'appareil sur lequel vous faites le profilage.
- La Résolution d'écran de votre appareil cible affectera la taille des RenderTextures utilisées pour les effets de post-traitement.
- L'API graphique prise en charge d'un périphérique peut affecter la taille des shaders en fonction des variantes prises en charge ou non par l'API.
- Avoir un système à plusieurs niveaux qui utilise différents paramètres de qualité, paramètres de niveau graphique et variations de groupe d'actifs est un excellent moyen de cibler un plus large éventail d'appareils, par exemple en chargeant une version haute définition d'un groupe d'actifs sur un appareil mobile de 4 Go et une version en définition standard sur un appareil de 2 Go. Cependant, prenez en compte les variations ci-dessus dans l'utilisation de la mémoire et assurez-vous de tester les deux types de périphériques, ainsi que les périphériques avec des résolutions d'écran différentes ou des API graphiques prises en charge.
remarque L'éditeur Unity affichera généralement toujours une plus grande empreinte mémoire en raison d'objets supplémentaires chargés à partir de l'éditeur et du profileur. Il peut même afficher la mémoire des actifs qui ne serait pas chargée en mémoire dans une compilation, par exemple à partir de groupes d'actifs (selon le mode de simulation Adressables) ou de Sprites et Atlas, ou pour les actifs affichés dans l'inspecteur. Certaines chaînes de référence peuvent également être plus confuses dans l'éditeur.
Le Memory Profiler est actuellement en avant-première pour Unity 2019 LTS ou plus récent, mais devrait être vérifié dans Unity 2022 LTS.
Un grand avantage du package Memory Profiler est qu'en plus de capturer des objets natifs (comme le fait le module Memory Profiler), il vous permet également d'afficher la mémoire gérée, d'enregistrer et de comparer des instantanés et d'explorer le contenu de la mémoire encore plus en détail, avec des ventilations visuelles de votre utilisation de la mémoire.
Un instantané montre les allocations de mémoire dans le moteur, vous permettant d'identifier rapidement les causes d'une utilisation excessive ou inutile de la mémoire, de traquer les fuites de mémoire ou de voir la fragmentation du tas.
Après avoir installé le package Memory Profiler, ouvrez-le en cliquant sur Fenêtre > Analyse > Memory Profiler.
La barre de menu supérieure du Memory Profiler vous permet de modifier la cible de sélection du lecteur et de capturer ou importer des instantanés.
remarque Mémoire de profil sur le matériel cible en connectant le profileur de mémoire au périphérique distant avec la liste déroulante de sélection de cible. Profilage dans l'éditeur Unity vous donnera des chiffres inexacts en raison des frais généraux ajoutés par l'éditeur et d'autres outils.
Sur la gauche de la fenêtre Memory Profiler se trouve le Workbencharea. Utilisez ceci pour gérer et ouvrir ou fermer les instantanés de mémoire enregistrés. Vous pouvez également utiliser cette zone pour basculer entre les vues Single et Compare Snapshots.
Similaire à Profile Analyzer, le Memory Profiler vous permet de charger deux jeux de données (instantanés mémoire) pour les comparer. Ceci est particulièrement utile lorsque vous regardez comment l'utilisation de la mémoire a augmenté au fil du temps ou entre les scènes et lorsque vous recherchez des fuites de mémoire.
Memory Profiler dispose d'un certain nombre d'onglets dans la fenêtre principale qui vous permettent de fouiller dans les instantanés de mémoire, y compris Résumé, Objets et allocations et Fragmentation. Examinons chacune de ces options en détail.
Choisissez cette vue lorsque vous souhaitez obtenir un aperçu rapide de l'utilisation de la mémoire d'un projet. Il contient également des chiffres utiles et importants liés à la mémoire pour l'instantané de mémoire capturé en question. Il est parfait pour un coup d’œil rapide sur ce qui se passe au moment où un instantané a été pris.
La vue Carte Arborescente affiche une ventilation de la mémoire utilisée par les Objets comme une Carte Arborescente graphique que vous pouvez creuser pour découvrir le type d'Objets qui consomment le plus de mémoire.
Sous la vue Carte Arborescente se trouve un tableau filtré qui se met à jour pour afficher la liste des objets dans les cellules de grille sélectionnées.
La Carte Arborescente montre la mémoire attribuée aux Objets, native ou gérée. La mémoire Managed Object tend à être éclipsée par la mémoire Native Object, ce qui la rend plus difficile à repérer dans la vue carte. Vous pouvez zoomer sur la carte de l'arbre pour les regarder, mais pour l'inspection des objets plus petits, les tableaux fournissent généralement une meilleure vue d'ensemble. En cliquant sur les cellules de la carte arborescente, vous filtrerez le tableau situé sous celui-ci en fonction du type de section et/ou sélectionnerez l'objet d'intérêt spécifique dans le tableau.
Vous pouvez rechercher quels éléments référencent les objets de cette liste et éventuellement dans quels champs de classe gérée ces références résident en sélectionnant la ligne de tableau ou la cellule de grille Arborescence Map qui la représente, puis en cochant la section Références dans le panneau latéral Détails. Si le côté est caché, vous pouvez le rendre visible via un bouton à bascule dans la partie supérieure droite de la barre d’outils.
remarque La Carte Arborescente ne montre que la mémoire Objectsin. Ce n’est pas une représentation complète de la mémoire suivie. Ceci est important à comprendre dans le cas où vous remarquez que les chiffres d'Aperçu de l'utilisation de la mémoire ne sont pas les mêmes que le total de la mémoire suivie.
Cela résulte du fait que toute la mémoire native n'est pas liée aux Objets. Il peut également se composer d'allocations natives non associées à un objet telles que des exécutables et des DLL, des NativeArrays, etc. Des concepts encore plus abstraits comme « Espace mémoire réservé mais non utilisé » peuvent jouer dans le total des allocations natives.
La vue Objets et allocations montre une table qui peut être commutée en filtre en fonction de sélections prêtes à l'emploi, telles que Tous les objets, Tous les objets natifs, Tous les objets gérés, Toutes les allocations natives, etc.
Vous pouvez basculer le tableau du bas pour afficher les Objets, Allocations ou Régions mémoire dans la plage sélectionnée. Comme indiqué pour la vue Tree Map, toute la mémoire n'est pas associée aux objets, de sorte que les pages All Memory Regions et All Native Allocations peuvent fournir une image plus complète de votre utilisation de la mémoire, où les régions mémoire incluent également de la mémoire réservée mais actuellement inutilisée.
Utilisez ceci à votre avantage lorsque vous optimisez l'utilisation de la mémoire et visez à emballer la mémoire plus efficacement pour les plates-formes matérielles où les budgets mémoire sont limités.
Chargez un instantané Memory Profiler et passez par la vue Tree Map pour inspecter les catégories, ordonnées du plus grand au plus petit en taille d'empreinte mémoire.
Les actifs du projet sont souvent les plus gros consommateurs de mémoire. À l'aide de la vue Tableau, localisez les objets Texture, Mesh, AudioClips, RenderTextures, Shaders et tampons préalloués. Ce sont tous de bons candidats pour l'optimisation de la mémoire.
Une fuite de mémoire se produit généralement lorsque:
- Un objet n'est pas libéré manuellement de la mémoire grâce au code
- Un objet reste en mémoire à cause d'une référence involontaire
Le mode Memory Profiler Compare peut aider à trouver des fuites de mémoire en comparant deux instantanés sur une période spécifique.
Un scénario courant de fuite de mémoire dans les jeux Unity peut se produire après le déchargement d'une scène.
Le package Memory Profiler dispose d'un flux de travail qui vous guide dans le processus de découverte de ces types de fuites à l'aide du mode Comparer.
Grâce à la comparaison différentielle de plusieurs instantanés mémoire, vous pouvez identifier la source d'allocations mémoire continues pendant la durée de vie de l'application.
Les sections suivantes énumèrent quelques conseils pour aider à identifier les allocations gérées de tas dans vos projets.
Le module Memory Profiler du Unity Profiler représente les allocations gérées par image avec une ligne rouge. Cela devrait être 0 la plupart du temps, donc tous les pics dans cette ligne indiquent les cadres que vous devriez rechercher pour les allocations gérées.
La vue Timeline du module CPU Usage Profiler affiche les allocations, y compris gérées, en rose, ce qui les rend faciles à voir et à affiner.
Les piles d'appels d'allocation fournissent un moyen rapide de découvrir les allocations de mémoire gérée dans votre code. Ceux-ci fourniront le détail de la pile d'appels dont vous avez besoin à moindre frais généraux par rapport à ce que le profilage profond ajouterait normalement, et ils peuvent être activés à la volée à l'aide du Profiler standard.
Les piles d'appels d'allocation sont désactivées par défaut dans le Profiler. Pour les activer, cliquez sur le bouton Call Stacks dans la barre d'outils principale de la fenêtre Profiler. Modifiez la vue Détails en Données connexes.
remarque Si vous utilisez une ancienne version d'Unity (avant la prise en charge de la pile d'appels Allocation), le profilage profond est un bon moyen d'obtenir des piles d'appels complètes pour aider à trouver des allocations gérées.
Les échantillons GC.Alloc sélectionnés dans la Hiérarchie ou la Hiérarchie brute contiendront désormais leurs piles d'appels. Vous pouvez également voir les piles d'appels des échantillons GC.Alloc dans l'info-bulle de sélection dans Timeline.
La vue Hiérarchie dans le profileur d'utilisation du processeur vous permet de cliquer sur les en-têtes de colonne pour les utiliser comme critères de tri. Le tri par GC Alloc est un excellent moyen de se concentrer sur ceux-ci.
Project Auditor est un outil expérimental d'analyse statique. Il fait beaucoup de choses utiles, dont plusieurs sortent du cadre de ce guide, mais il peut produire une liste de chaque ligne de code dans un projet qui provoque une allocation gérée, sans jamais avoir à exécuter le projet. C’est un moyen très efficace de trouver et d’enquêter sur ce genre de questions.
Unity utilise l'éboueur Boehm-Demers-Weiser, qui cesse d'exécuter le code de votre programme et ne reprend son exécution normale qu'une fois son travail terminé.
Soyez conscient des allocations inutiles de tas qui peuvent causer des pics de GC.
- Strings: En C#, les chaînes sont des types de référence, pas des types de valeur. Cela signifie que chaque nouvelle chaîne sera allouée sur le tas géré, même si elle n’est utilisée que temporairement. Réduisez la création ou la manipulation inutile de chaînes. Évitez d'analyser des fichiers de données basés sur des chaînes de caractères tels que JSON et XML, et stockez les données dans des objets scriptables ou des formats tels que MessagePack ou Protobuf à la place. Utilisez la classe StringBuilder si vous devez construire des chaînes de caractères à l'exécution.
- La fonction Unity appelle: Certaines fonctions de l'API Unity créent des allocations de tas, en particulier celles qui renvoient un tableau d'objets gérés. Mettre en cache les références aux tableaux plutôt que de les allouer au milieu d'une boucle. Profitez également de certaines fonctions qui évitent de générer des déchets. Par exemple, utilisez GameObject.CompareTag au lieu de comparer manuellement une chaîne avec GameObject.tag (car renvoyer une nouvelle chaîne crée des ordures).
- Boxe: Évitez de passer une variable de type valeur à la place d'une variable de type référence. Cela crée un objet temporaire, et la corbeille potentielle qui vient avec convertit implicitement le type de valeur en objet de type (par exemple, int i = 123; objet o = i). Essayez plutôt de fournir des remplacements concrets avec le type de valeur que vous souhaitez passer. Les génériques peuvent également être utilisés pour ces dérogations.
- Coroutines : Bien que yield ne produise pas d'ordures, la création d'un nouvel objet WaitForSeconds le fait. Mettez en cache et réutilisez l'objet WaitForSeconds plutôt que de le créer dans la ligne de rendement ou utilisez yield return null.
- LINQ et expressions régulières : Les deux génèrent des déchets de boxe en coulisses. Évitez LINQ et les expressions régulières si les performances posent problème. Écrivez des boucles for et utilisez des listes comme alternative à la création de nouveaux tableaux.
- Collections génériques et autres types gérés : Ne déclarez pas et ne remplissez pas une Liste ou une collection à chaque image dans Update (par exemple, une liste d'ennemis dans un certain rayon autour du joueur). Faites plutôt de la Liste un membre du MonoBehaviour et initialisez-la dans Start. Videz simplement la collection avec Effacer chaque cadre avant de l'utiliser.
Temps de collecte des ordures chaque fois que possible
Si vous êtes certain qu’un gel de collecte d’ordures n’affectera pas un point précis de votre jeu, vous pouvez déclencher la collecte d’ordures avec System.GC.Collect.
Voir Comprendre la gestion automatique de la mémoire pour des exemples d'utilisation à votre avantage.
Utilisez le collecteur d'ordures incrémentiel pour diviser la charge de travail du GC
Plutôt que de créer une seule et longue interruption pendant l'exécution de votre programme, la collecte incrémentale des ordures utilise plusieurs interruptions plus courtes qui répartissent la charge de travail sur de nombreuses trames. Si la collecte des ordures entraîne une fréquence d'images irrégulière, essayez cette option pour voir si elle peut réduire le problème des pics de GC. Utilisez l'analyseur de profil pour vérifier ses avantages pour votre application.
Notez que l'utilisation du GC en mode incrémental ajoute des barrières de lecture-écriture à certains appels C#, ce qui est fourni avec certains frais généraux qui peuvent ajouter jusqu'à ~1 ms par image d'appel de script. Pour des performances optimales, il est idéal de ne pas avoir de GC Allocs dans les boucles de gameplay principales afin de ne pas avoir besoin du GC incrémental pour une fréquence d’images fluide et de pouvoir masquer le GC.Collect là où un utilisateur ne le remarquera pas, par exemple lors de l’ouverture du menu ou du chargement d’un nouveau niveau.
Pour en savoir plus sur Memory Profiler, consultez les ressources suivantes :
- Documentation Memory Profiler
- Améliorer l'utilisation de la mémoire avec le Memory Profiler dans Unity
- Le profiler de mémoire L'outil de résolution des problèmes liés à la mémoire
- Travailler avec la session Memory Profiler Unity Learn
Téléchargez gratuitement le livre électronique Ultimate guide to profileing Unity games pour obtenir tous les conseils et bonnes pratiques.