Inspection de la mémoire avec le nouveau paquet Memory Profiler

ANDY BARNARD / UNITYSoftware Engineer, Performance Tooling
Feb 28, 2023|15 Min
Inspection de la mémoire avec le nouveau paquet Memory Profiler
Cette page a été traduite automatiquement pour faciliter votre expérience. Nous ne pouvons pas garantir l'exactitude ou la fiabilité du contenu traduit. Si vous avez des doutes quant à la qualité de cette traduction, reportez-vous à la version anglaise de la page web.

Dans ce blog, nous aborderons cinq flux de travail clés dans le nouveau package Memory Profiler que vous pouvez utiliser pour diagnostiquer et examiner les problèmes liés à la mémoire dans votre jeu. Il s'agit de

  • Contrôler la pression de la mémoire de votre application
  • Voir la distribution des objets Unity
  • Détecter les actifs mal configurés
  • Localisation des objets dupliqués involontaires
  • Comparaison des captures de mémoire pour valider les optimisations

Pour une introduction au Memory Profiler, veuillez consulter le récent blog, Tout ce que vous devez savoir sur le Memory Profiler 1.0.0.

Contrôler la pression de la mémoire de votre application

Ce premier flux de travail permet de contrôler dans quelle mesure votre application sollicite les ressources de la mémoire de l'appareil. Ce processus est essentiel pour déterminer si votre application risque ou non de connaître des problèmes de performance, voire d'être expulsée et arrêtée par le système d'exploitation, en raison d'une trop grande consommation de mémoire.

Pour commencer, nous disposons d'une version d'un jeu d'exemple fonctionnant sur l'appareil cible. Naturellement, il est essentiel que nous fassions une capture de la mémoire du jeu, fonctionnant sur le matériel réel, pour voir comment il utilise les ressources de mémoire disponibles sur les appareils. En outre, la mémoire ne se comporte pas de la même manière dans l'éditeur Unity et dans le moteur d'exécution Unity, de sorte que la capture de la mémoire de l'éditeur en mode lecture n'est pas une bonne représentation de l'aspect de la mémoire d'un jeu sur un appareil. (La capture de la mémoire de l'éditeur est utile lors du développement d'outils pour l'éditeur, tels que les fenêtres personnalisées de l'éditeur).

Après avoir navigué jusqu'à l'étape de notre jeu où nous voulons analyser l'utilisation de la mémoire, nous attachons le Memory Profiler à notre appareil en utilisant le menu déroulant du Memory Profiler. Nous pouvons ensuite réaliser une capture de mémoire, comme indiqué ci-dessous.

Se connecter à une version de notre jeu et prendre une capture de mémoire.

Après avoir ouvert cette capture, le Memory Profiler affiche l'empreinte mémoire de notre application en haut de la page de résumé sous la forme "Memory Usage On Device".

L'empreinte mémoire de notre application est affichée par l'indicateur Utilisation de la mémoire sur l'appareil.
L'empreinte mémoire de notre application est affichée par l'indicateur Utilisation de la mémoire sur l'appareil.

Nous voyons ici que l'empreinte mémoire de notre application est de 492,5 Mo, sur les 3,50 Go disponibles. Nous devons ensuite faire preuve de discernement pour déterminer s'il s'agit d'une proportion raisonnable de la mémoire physique (RAM) de l'appareil au moment de la capture. N'oubliez pas que la mémoire physique d'un appareil est partagée par tous les processus en cours.

Qu'entend-on exactement par "empreinte mémoire" ?

Vous remarquerez que cet indicateur visuel vous montre la mémoire résidente totale. La mémoire résidente totale correspond à la quantité de mémoire de votre application qui réside dans la mémoire physique de l'appareil (RAM). Il s'agit de l'indicateur le plus clair de l'utilisation actuelle de la mémoire de votre application sur l'appareil cible, et ce pour deux raisons. Tout d'abord, cela s'explique par le fait que plus l'utilisation de la mémoire résidente totale de votre application augmente, plus vous risquez de subir des erreurs de page fréquentes, où le système d'exploitation doit paginer la mémoire virtuelle dans et hors de la mémoire physique de l'appareil. Des erreurs de page fréquentes entraîneront une dégradation significative des performances de votre application. D'autre part, de nombreux systèmes d'exploitation utilisent l'utilisation de la mémoire résidente de votre application pour déterminer son empreinte mémoire actuelle. Si l'empreinte mémoire de votre application devient trop importante, le système d'exploitation l'expulsera et y mettra fin, ce qui provoquera un plantage de vos joueurs.

Par conséquent, vous pouvez utiliser l'indicateur visuel Memory Usage On Device dans le Memory Profiler pour déduire si une application risque d'avoir des problèmes de performance ou d'être interrompue par le système d'exploitation, en raison d'une surutilisation de la mémoire au moment de la capture.

En revanche, la mémoire allouée, parfois appelée mémoire engagée, est affichée dans divers graphiques sous cet indicateur et constitue actuellement l'option par défaut de toutes les autres vues, telles que Unity Objects. La mémoire allouée fait référence à toute la mémoire actuellement allouée à votre application, qu'elle ait été rendue résidente dans la mémoire physique ou non, et correspond donc plus étroitement à la vision de la mémoire qu'a votre application. En tant que telle, elle peut être utile pour explorer toute la mémoire actuellement allouée à votre application, tandis que l'utilisation de la mémoire résidente est essentielle pour comprendre la pression de mémoire que votre application exerce sur le matériel à tout moment.

Voir la distribution des objets Unity

L'onglet Unity Objects du Memory Profiler vous donne un aperçu de la mémoire de votre application du point de vue des Unity Objects, c'est-à-dire des textures, des shaders, des maillages, des matériaux, etc. de votre application. C'est un excellent point de départ pour explorer le Memory Profiler car les objets Unity sont familiers à de nombreux utilisateurs d'Unity, car c'est ce avec quoi la majorité d'entre nous travaille directement dans l'éditeur Unity. Non seulement cela fournit un point d'entrée familier pour comprendre la mémoire de notre application, mais cela peut également aider à diagnostiquer et à résoudre une série de problèmes potentiels en fournissant ce contexte spécifique à Unity.

La vue Objets Unity.
La vue Objets Unity.

Pour afficher la vue Unity Objects, il suffit de sélectionner l'onglet Unity Objects en haut du Memory Profiler après avoir ouvert une capture de mémoire, comme illustré ci-dessus.

Vous pouvez voir comment la vue Unity Objects nous permet de comprendre rapidement la distribution des types d' objets Unity dans notre application. Cela nous permet à la fois d'obtenir une compréhension de haut niveau des types qui consommaient le plus de mémoire au moment de la capture, et de raisonner à ce sujet, comme de savoir si l'on s'attend à ce qu'une scène particulière soit lourdement chargée en objets AudioClip, par exemple. L'expansion de chaque type nous permet également de visualiser chaque objet Unity qui est actuellement alloué, individuellement, comme indiqué ci-dessous.

Nous pouvons voir tous les objets de maillage actuellement alloués par notre application en développant le type de maillage.
Nous pouvons voir tous les objets de maillage actuellement alloués par notre application en développant le type de maillage.

Il est important de se rappeler que les Unity Objects représentent une partie de la mémoire totale allouée à notre application. Le montant exact est indiqué dans l'indicateur situé au-dessus du tableau, mis en évidence ci-dessous.

L'indicateur au-dessus du tableau nous montre la proportion de notre mémoire totale allouée que nous voyons dans le tableau.
L'indicateur au-dessus du tableau nous montre la proportion de notre mémoire totale allouée que nous voyons dans le tableau.

Ici, vous pouvez voir que la taille totale de la mémoire allouée, "Total Memory In Snapshot", est de 4,64 Go et que nos Unity Objects représentent 2,37 Go de cette taille. En outre, si nous filtrons le tableau - par exemple, en utilisant la fonction de recherche - vous remarquerez que cette barre se met à jour pour refléter les résultats de notre recherche. En d'autres termes, il affiche la taille de toute la mémoire figurant actuellement dans le tableau. Cela vous permet de garder une perspective sur la quantité exacte de mémoire que vous inspectez par rapport à l'ensemble de la capture et peut vous aider à savoir où investir vos efforts d'optimisation.

L'indicateur au-dessus du tableau se met à jour au fur et à mesure que nous filtrons pour refléter la taille de la mémoire actuellement affichée dans le tableau, en proportion de la mémoire totale allouée.
L'indicateur au-dessus du tableau se met à jour au fur et à mesure que nous filtrons pour refléter la taille de la mémoire actuellement affichée dans le tableau, en proportion de la mémoire totale allouée.
Dans la version 1.0 de Memory Profiler, le tableau Unity Objects indique la mémoire allouée ou, en d'autres termes, tous les Unity Objects actifs dans votre application. Nous envisageons d'ajouter la visibilité de la mémoire résidente à ces vues dans une prochaine version, ce qui vous permettrait de voir exactement quels objets Unity résident actuellement dans la mémoire physique, et donc de voir exactement quels objets contribuent directement à l'empreinte mémoire actuelle de votre application.



Vous pouvez utiliser l'onglet Toute la mémoire pour inspecter le reste de la mémoire de votre application au moment de la capture, ce qui inclut la mémoire en dehors des objets Unity, tels que divers sous-systèmes Unity, la mémoire gérée uniquement (C#), ainsi que les DLL et les exécutables.
Détecter les actifs mal configurés

La vue Unity Objects peut nous aider à diagnostiquer une série de problèmes potentiels. L'un de ces problèmes consiste à détecter les actifs qui ont été mal configurés et qui consomment donc plus de mémoire que nécessaire.

Dans la capture ci-dessous, vous pouvez voir qu'une grande partie de nos objets Unity sont des textures. La capture provient d'un projet à haute fidélité graphique qui utilise le pipeline de rendu haute définition et fait un usage intensif d'effets visuels. Dans ce contexte, nous nous attendons donc à une forte utilisation des textures, ce qui est le cas.

La quantité de mémoire associée aux objets Unity dans cette capture est dominée par les types RenderTexture et Texture2D.
La quantité de mémoire associée aux objets Unity dans cette capture est dominée par les types RenderTexture et Texture2D.

Cependant, en élargissant notre deuxième grande catégorie, Texture2D, nous pouvons remarquer que deux textures apparaissent beaucoup plus grandes que les autres. Compte tenu de notre compréhension du projet, nous sommes surpris que ces textures soient plus grandes que des textures comparables, telles que HoloTable_Normal ou HoloTable_Mask, car nous nous attendions à ce qu'elles soient de taille similaire.

Deux textures de notre capture semblent être deux fois plus grandes que des textures comparables.
Deux textures de notre capture semblent être deux fois plus grandes que des textures comparables.

Nous sélectionnons donc l'une de ces textures dans le tableau afin d'en savoir plus et de commencer à en rechercher la cause. Ici, dans la vue Détails, nous trouvons notre explication : notre texture est inscriptible, ou "Read/Write Enabled" (lecture/écriture activée).

La vue Détails affiche des informations supplémentaires sur l'objet sélectionné et révèle que notre texture est "activée en lecture/écriture".
La vue Détails affiche des informations supplémentaires sur l'objet sélectionné et révèle que notre texture est "activée en lecture/écriture".

Il s'agit d'un problème courant que nous rencontrons dans de nombreux projets d'utilisateurs : rendre accidentellement une texture accessible en écriture alors qu'elle n'est pas nécessaire en cochant le paramètre "Read/Write" dans les paramètres d'importation de la texture. Lorsque ce drapeau est activé, la taille de la texture est doublée en mémoire. En effet, une deuxième copie des données de texture est nécessaire pour que l'unité centrale puisse y accéder. Un signe révélateur est que la taille totale d'une texture est deux fois plus importante que celle à laquelle vous vous attendiez, ou deux fois plus importante que des textures similaires.

Après avoir désactivé le drapeau "lecture/écriture" sur ces deux textures et effectué une seconde capture, nous pouvons constater que la taille de ces deux textures a été divisée par deux.

Les deux textures incriminées dans notre capture ont désormais la même taille que les textures comparables après la modification.
Les deux textures incriminées dans notre capture ont désormais la même taille que les textures comparables après la modification.
Nous envisageons d'ajouter une colonne pour la mémoire graphique (GPU) à la table des objets Unity dans une prochaine version afin de faciliter la localisation des cas où un objet Unity a alloué de la mémoire graphique, comme dans cet exemple.
Localisation des objets dupliqués involontaires

Une erreur fréquente dans les projets Unity est de créer involontairement des objets Unity en double. Par exemple, il est très facile de créer accidentellement un matériau en double en accédant à la propriété de matériau d'un MeshRenderer. Non seulement cela s'accumule rapidement dans ce cas - si, par exemple, on le fait pour chaque instance d'un MeshRenderer particulier - mais, en outre, ces matériaux créés dynamiquement doivent être explicitement détruits.

Pour vous aider à localiser ce type de problème, le tableau des objets Unity fournit un filtre rapide pour vous montrer uniquement les objets Unity dupliqués potentiels. Cette vue filtrera le tableau pour n'afficher que les objets Unity qui ont plusieurs instances avec un nom et une taille identiques. Il est important de noter que de nombreux doublons potentiels sont prévisibles et ne constituent en aucun cas une source d'inquiétude. Par exemple, plusieurs instances d'un préfabriqué peuvent avoir des composants Transform de même nom et de même taille, et il s'agirait alors de doublons. Seule la découverte de doublons involontaires nous intéresse, ce que nous illustrerons dans l'exemple suivant du flux de travail.

La capture ci-dessous a été réalisée dans une scène simple avec deux instances d'un préfabriqué de porte, et nous avons activé le filtre Show Potential Duplicates Only (Afficher uniquement les doublons potentiels) situé sous le tableau Unity Objects (Objets Unity). Le tableau est ainsi filtré pour n'afficher que les objets Unity qui ont plusieurs instances avec le même nom et la même taille.

Le filtre rapide "Afficher uniquement les doublons potentiels" n'affiche que les objets Unity qui ont plusieurs instances avec le même nom et la même taille.
Le filtre rapide "Afficher uniquement les doublons potentiels" n'affiche que les objets Unity qui ont plusieurs instances avec le même nom et la même taille.

Comme nous avons deux instances d'un préfabriqué Porte dans notre scène, nous avons également, comme prévu, deux instances de tous les objets pertinents : MeshRenderer, Transform, GameObject, etc. Cependant, nous avons également deux exemples de matériau "Porte" dans notre capture ci-dessus. Ces instances de porte ont la même apparence dans notre scène, il est donc normal qu'elles partagent un matériau. Il s'agit donc d'un doublon involontaire qui, dans cet exemple particulier, a été causé par l'accès à la propriété matérielle du MeshRenderer dans le préfabriqué. En supprimant l'accès à cette propriété et en effectuant une seconde capture, on constate que le matériau dupliqué n'est plus présent dans la table Unity Objects.

Le matériau dupliqué involontairement n'est plus présent dans la table des objets Unity.
Le matériau dupliqué involontairement n'est plus présent dans la table des objets Unity.

Il est important de se rappeler que ce filtre vous montre simplement tous les Unity Objects qui ont plusieurs instances avec le même nom et la même taille. Il faut que vous connaissiez votre projet pour savoir si les doublons potentiels que vous voyez sont attendus ou s'ils sont en fait involontaires et justifient une enquête. Nous vous recommandons de prêter attention à la barre " Total Memory In Table" (Mémoire totale dans le tableau) située en haut, qui vous donne une indication visuelle de la proportion de la mémoire allouée à votre application que vous voyez dans le tableau. Cela peut vous aider à garder une perspective sur la façon dont vous devez investir vos efforts d'optimisation.

Comparaison des captures de mémoire pour valider les optimisations

Le Memory Profiler permet également de comparer deux captures de mémoire. Cela nous permet d'apporter des modifications à notre projet, par exemple pour résoudre un problème que nous aurions découvert, et de tester ensuite si nos modifications ont bien eu l'effet escompté. Il est important de toujours vérifier que votre hypothèse est correcte et que vos modifications ont eu l'effet escompté sur le matériel réel. Voici un exemple de ce processus de comparaison.

Vous trouverez ci-dessous une capture de notre jeu mobile prise pendant le premier niveau. Nous pouvons voir que la plus grande catégorie d'objets Unity est Texture2D. Après avoir ouvert cette catégorie pour vérifier quelles sont nos plus grosses textures, nous pouvons voir qu'il y a quelques textures d'interface utilisateur qui sont assez grandes par rapport au reste de notre jeu - des mégaoctets chacune. Cela nous amène à nous poser des questions : Pourquoi ces textures sont-elles beaucoup plus grandes que les autres et ont-elles besoin de l'être ? Pour découvrir pourquoi, nous pouvons d'abord localiser la texture source dans notre projet en sélectionnant la texture dans le Memory Profiler et en utilisant le bouton "Select In Editor", qui mettra en évidence la texture source dans notre fenêtre de projet.

Utilisez le bouton "Sélectionner dans l'éditeur" dans la vue Détails pour sélectionner la ressource source dans la fenêtre Projet.
Utilisez le bouton "Sélectionner dans l'éditeur" dans la vue Détails pour sélectionner la ressource source dans la fenêtre Projet.

En utilisant la fenêtre de l'inspecteur, nous pouvons voir que toutes nos grandes textures d'interface utilisateur ne sont pas compressées parce que leurs dimensions ne sont pas une puissance de deux, comme le montre le texte "NPOT" (non-power-of-two).

Les six grandes textures ne sont pas compressées car leurs dimensions ne correspondent pas à une puissance de deux.
Les six grandes textures ne sont pas compressées car leurs dimensions ne correspondent pas à une puissance de deux.

C'est ce qui explique la taille importante des textures. Nous pouvons maintenant utiliser notre connaissance de notre projet pour réduire cette utilisation de la mémoire. Nous savons que trois de ces textures (les commandes d'aide) sont toujours affichées ensemble dans l'interface utilisateur, ainsi que les trois autres textures (les créatures). Par conséquent, nous pouvons émettre l'hypothèse que la création de deux Sprite Atlases pour chaque ensemble de trois textures réduira l'utilisation de la mémoire allouée, car elle permettra de les compresser sans augmenter le nombre de textures en mémoire.

Création de deux atlas de sprites pour chaque ensemble de trois textures.
Création de deux atlas de sprites pour chaque ensemble de trois textures.

Pour comparer deux instantanés, commencez par ouvrir le premier instantané. Il s'agit de la "base" à laquelle nous voulons nous comparer. Au-dessus de l'instantané ouvert, sélectionnez l'onglet "Comparer les instantanés" et choisissez le deuxième instantané. Le Memory Profiler présente alors un résumé comparant les deux instantanés, comme indiqué ci-dessous.

La page Résumé du profileur de mémoire présente un résumé des différences entre deux captures de mémoire lorsque deux instantanés sont comparés.
La page Résumé du profileur de mémoire présente un résumé des différences entre deux captures de mémoire lorsque deux instantanés sont comparés.

Pour voir l'effet de notre changement et vérifier qu'il a bien réduit la taille de la mémoire allouée à notre application pour la catégorie Texture2D, nous pouvons sélectionner l'onglet Unity Objects. Ici, nous avons un tableau comparatif qui montre les types d'objets Unity qui ont changé, ainsi que la façon dont ils ont changé entre les captures (voir ci-dessous).

Les types d'objets Unity qui ont changé entre les deux captures de mémoire.
Les types d'objets Unity qui ont changé entre les deux captures de mémoire.

Nous pouvons constater que notre type Texture2D dans son ensemble a réduit sa taille de 3,6 Mo et qu'il contient quatre textures de moins qu'auparavant. En élargissant cette catégorie, nous pouvons voir la suppression de nos textures Sprite individuelles non compressées et l'ajout de nos deux textures Sprite Atlas, ce qui se traduit par une réduction nette de 3,6 Mo et de 4 objets Texture2D.

Les objets Unity de Texture2D qui ont changé entre les deux captures.
Les objets Unity de Texture2D qui ont changé entre les deux captures.

C'est donc un succès : nous avons confirmé que notre hypothèse était correcte en utilisant la fonctionnalité de comparaison, et nous avons réduit la taille de ces textures dans la mémoire allouée.

Conclusion

En lisant ce blog, vous devriez maintenant avoir une meilleure compréhension de cinq flux de travail clés dans le nouveau package Memory Profiler. Ces flux de travail sont conçus pour diagnostiquer et examiner les problèmes liés à la mémoire dans votre jeu. Nous espérons que le module Memory Profiler publié dans Unity 2022.2 vous aidera à mieux surveiller, examiner et comprendre l'empreinte mémoire de votre jeu. N'hésitez pas à contacter l'équipe pour nous faire part de vos commentaires sur la manière dont nous pouvons améliorer les outils de profilage des performances via notre page de forum -, ou à nous faire part de vos suggestions via notre page de feuille de route, où vous pouvez également voir certaines des fonctionnalités sur lesquelles nous travaillons.

Si vous souhaitez obtenir plus de détails sur ce sujet, nous publierons dans les semaines à venir un autre blog qui approfondira la manière dont l'empreinte mémoire d'une application est calculée, en abordant plus en détail des sujets tels que la mémoire résidente et la mémoire allouée.