Réduire l’utilisation de la mémoire des applications .NET?

Quels sont les conseils pour réduire l’utilisation de la mémoire des applications .NET? Considérons le programme simple C # suivant.

class Program { static void Main(ssortingng[] args) { Console.ReadLine(); } } 

Compilé en mode de publication pour x64 et exécuté en dehors de Visual Studio, le gestionnaire de tâches affiche les informations suivantes:

 Working Set: 9364k Private Working Set: 2500k Commit Size: 17480k 

C’est un peu mieux s’il est compilé uniquement pour x86 :

 Working Set: 5888k Private Working Set: 1280k Commit Size: 7012k 

J’ai ensuite essayé le programme suivant, qui fait la même chose mais essaie de réduire la taille du processus après l’initialisation de l’exécution:

 class Program { static void Main(ssortingng[] args) { minimizeMemory(); Console.ReadLine(); } private static void minimizeMemory() { GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, (UIntPtr) 0xFFFFFFFF, (UIntPtr)0xFFFFFFFF); } [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetProcessWorkingSetSize(IntPtr process, UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize); } 

Les résultats sur la version x86 en dehors de Visual Studio:

 Working Set: 2300k Private Working Set: 964k Commit Size: 8408k 

Ce qui est un peu mieux, mais cela semble toujours excessif pour un programme aussi simple. Y a-t-il des astuces pour rendre un processus C # un peu plus léger? J’écris un programme conçu pour fonctionner en arrière-plan la plupart du temps. Je fais déjà des trucs d’interface utilisateur dans un domaine d’application distinct, ce qui signifie que les éléments de l’interface utilisateur peuvent être déchargés en toute sécurité, mais prendre 10 Mo en arrière-plan semble excessif.

PS: Pourquoi les utilisateurs de (Power) se préoccupent-ils de ces choses? Même si cela n’a quasiment aucun effet sur les performances, les utilisateurs semi-technophiles (mon public cible) ont tendance à se mettre en quatre sur l’utilisation de la mémoire des applications en arrière-plan. Même quand je vois Adobe Updater prendre 11 Mo de mémoire et me sentir apaisé par le toucher apaisant de Foobar2000, qui peut prendre moins de 6 Mo même en jouant. Je sais que dans les systèmes d’exploitation modernes, ce genre de choses n’a pas vraiment d’importance techniquement, mais cela ne veut pas dire que cela n’a aucune incidence sur la perception.

  1. Vous voudrez peut-être vérifier Stack Overflow question Empreinte de mémoire .NET EXE .
  2. Article de blog MSDN Jeu de travail! = L’empreinte de la mémoire réelle consiste à démystifier le jeu de travail, la mémoire de processus et à effectuer des calculs précis sur la consommation totale de mémoire vive.

Je ne dirai pas que vous devez ignorer l’empreinte mémoire de votre application – de toute évidence, une taille plus petite et plus efficace est souhaitable. Cependant, vous devriez considérer quels sont vos besoins réels.

Si vous écrivez des applications client Windows Forms et WPF standard destinées à être exécutées sur le PC d’un individu, et qu’il s’agira probablement de l’application principale dans laquelle l’utilisateur opère, vous pouvez vous permettre d’être plus discret sur l’allocation de mémoire. (Tant que tout est désalloué.)

Cependant, si vous écrivez une application Windows Forms qui s’exécutera dans un environnement de services de terminal, sur un serveur partagé éventuellement utilisé par 10, 20 utilisateurs ou plus, alors oui. , vous devez absolument considérer l’utilisation de la mémoire. Et vous devrez être vigilant. La meilleure façon de résoudre ce problème est de concevoir de bonnes structures de données et de suivre les meilleures pratiques concernant le moment et la manière dont vous allouez.

Les applications .NET auront une plus grande empreinte par rapport aux applications natives car elles doivent toutes deux charger le runtime et l’application dans le processus. Si vous voulez quelque chose de vraiment ordonné, .NET peut ne pas être la meilleure option.

Cependant, gardez à l’esprit que si votre application est en grande partie en veille, les pages mémoire nécessaires seront supprimées de la mémoire et ne constitueront pas un lourd fardeau pour le système dans son ensemble.

Si vous voulez garder une empreinte réduite, vous devrez penser à l’utilisation de la mémoire. Voici quelques idées:

  • Réduisez le nombre d’objects et veillez à ne pas conserver une instance plus longue que nécessaire.
  • Soyez conscient de la List et des types similaires qui doublent la capacité lorsque cela est nécessaire, car ils peuvent entraîner une perte de 50%.
  • Vous pouvez envisager d’utiliser des types de valeur sur les types de référence pour forcer davantage de mémoire sur la stack, mais gardez à l’esprit que l’espace de stack par défaut n’est que de 1 Mo.
  • Évitez les objects de plus de 85 000 octets, car ils vont à LOH qui n’est pas compacté et peut donc facilement être fragmenté.

Ce n’est probablement pas une liste exhaustive, mais quelques idées.

Une chose que vous devez prendre en compte dans ce cas est le coût de la mémoire du CLR. Le CLR est chargé pour chaque processus .Net et, par conséquent, prend en compte les facteurs de mémoire. Pour un programme aussi simple / petit, le coût du CLR va dominer votre empreinte mémoire.

Il serait beaucoup plus instructif de construire une application réelle et d’en voir le coût par rapport au coût de ce programme de base.

Aucune suggestion spécifique en soi, mais vous pouvez consulter le profileur CLR (téléchargement gratuit de Microsoft).
Une fois que vous l’avez installé, consultez cette page .

De la façon de:

Ce How To vous montre comment utiliser l’outil CLR Profiler pour étudier le profil d’allocation de mémoire de votre application. Vous pouvez utiliser CLR Profiler pour identifier le code qui provoque des problèmes de mémoire, tels que les memory leaks et le nettoyage de la mémoire excessif ou inefficace.

Peut-être voudrez-vous regarder l’utilisation de la mémoire d’une “vraie” application.

Semblable à Java, il existe une quantité fixe de surcharge pour le moteur d’exécution, quelle que soit la taille du programme, mais la consommation de mémoire sera beaucoup plus raisonnable après cela.

Il existe encore des moyens de réduire l’ensemble de travail privé de ce programme simple:

  1. NGEN votre demande. Cela supprime le coût de compilation JIT de votre processus.

  2. Entraînez votre application à l’aide de MPGO en réduisant l’utilisation de la mémoire et ensuite le NGEN.

Il existe plusieurs façons de réduire votre empreinte.

Une chose que vous devrez toujours vivre avec .NET est que la taille de l’image native de votre code IL est énorme

Et ce code ne peut pas être complètement partagé entre les instances d’application. Même les assemblages NGEN ne sont pas complètement statiques, ils ont encore quelques petites pièces qui nécessitent JITting.

Les gens ont également tendance à écrire du code qui bloque la mémoire beaucoup plus longtemps que nécessaire.

Un exemple souvent vu: Prendre un lecteur de données, charger le contenu dans un DataTable juste pour l’écrire dans un fichier XML. Vous pouvez facilement exécuter une exception OutOfMemoryException. OTOH, vous pouvez utiliser un XmlTextWriter et faire défiler le lecteur de données, en émettant des XmlNodes lorsque vous faites défiler le curseur de la firebase database. De cette façon, vous ne disposez que de l’enregistrement de firebase database actuel et de sa sortie XML en mémoire. Ce qui ne sera jamais (ou peu probable) d’obtenir une génération de récupération de place supérieure et peut donc être réutilisé.

Il en va de même pour obtenir une liste de certaines instances, faire des choses (qui génèrent des milliers de nouvelles instances, qui peuvent restr référencées quelque part), et même si vous n’en avez pas besoin après, vous faites toujours référence à foreach. Nommer explicitement votre liste d’entrées et vos sous-produits temporaires signifie que cette mémoire peut être réutilisée avant même que vous ne quittiez votre boucle.

C # a une excellente fonctionnalité appelée iterators. Ils vous permettent de diffuser des objects en faisant défiler votre entrée et de ne garder l’instance actuelle que jusqu’à la suivante. Même en utilisant LINQ, vous n’avez toujours pas besoin de tout conserver, simplement parce que vous souhaitiez le filtrer.

Aborder la question générale dans le titre et non la question spécifique:

Si vous utilisez un composant COM qui renvoie beaucoup de données (par exemple de grands tableaux 2xN de doubles) et que seule une petite fraction est nécessaire, vous pouvez écrire un composant COM wrapper qui masque la mémoire de .NET et ne renvoie que les données nécessaire.

C’est ce que j’ai fait dans mon application principale et cela a considérablement amélioré la consommation de mémoire.

J’ai constaté que l’utilisation des API SetProcessWorkingSetSize ou EmptyWorkingSet pour forcer périodiquement les pages mémoire sur le disque au cours d’un processus de longue durée peut entraîner la disparition effective de toute la mémoire physique disponible sur une machine jusqu’au redémarrage de la machine. Nous avons eu une DLL .NET chargée dans un processus natif qui utiliserait l’API EmptyWorkingSet (une alternative à l’utilisation de SetProcessWorkingSetSize) pour réduire l’ensemble de travail après avoir effectué une tâche gourmande en mémoire. J’ai constaté qu’après un jour ou une semaine, une machine affichait une utilisation de mémoire physique de 99% dans le Gestionnaire des tâches, alors qu’aucun processus n’utilisait de mémoire importante. Peu de temps après, la machine ne répondait plus, nécessitant un redémarrage brutal. Ces machines étaient plus de deux douzaines de serveurs Windows Server 2008 R2 et 2012 R2 fonctionnant sur du matériel physique et virtuel.

Peut-être que le code .NET chargé dans un processus natif a quelque chose à voir avec cela, mais utilisez EmptyWorkingSet (ou SetProcessWorkingSetSize) à vos risques et périls. Peut-être ne l’utilisez qu’une fois après le lancement initial de votre application. J’ai décidé de désactiver le code et de laisser le Garbage Collector gérer seul l’utilisation de la mémoire.