Quels sont quelques conseils pour réduire l'utilisation de la mémoire des applications .NET? Considérez le programme simple C # suivant.
class Program
{
static void Main(string[] args)
{
Console.ReadLine();
}
}
Compilé en mode release pour x64 et exécuté en dehors de Visual Studio, la tâche gestionnaire rapporte ce qui suit:
Working Set: 9364k
Private Working Set: 2500k
Commit Size: 17480k
C'est un peu mieux si c'est compilé juste 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 tente de réduire la taille du processus après l'initialisation de l'exécution:
class Program
{
static void Main(string[] 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 x86 publiés en dehors de Visual Studio:
Working Set: 2300k
Private Working Set: 964k
Commit Size: 8408k
Ce qui est un peu mieux, mais cela semble encore excessif pour un programme aussi simple. Existe-t-il des astuces pour alléger un peu le processus C #? J'écris un programme conçu pour fonctionner en arrière-plan la plupart du temps. Je suis déjà en train de créer des éléments d'interface utilisateur dans un domaine domaine d'application distinct, ce qui signifie que les éléments d'interface utilisateur peuvent être déchargés en toute sécurité, mais l'utilisation de 10 Mo en mode veille semble excessive.
P.S. Quant à savoir pourquoi je m'en soucie, les utilisateurs de (pouvoir) ont tendance à s'inquiéter de ces choses. Même si cela n’a pratiquement aucun effet sur les performances, les utilisateurs semi-férus de technologie (mon public cible) ont tendance à se laisser aller à de sombres discussions sur l’utilisation de la mémoire en arrière-plan des applications. Même quand je vois paniquer Adobe Updater prendre 11 Mo de mémoire, je me sens 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 matériel n’a vraiment pas beaucoup d’importance technique, mais cela ne signifie pas pour autant que cela n’affecte pas la perception.
Je ne dirai pas que vous devriez ignorer l'encombrement de la mémoire de votre application - de toute évidence, une taille plus petite et plus efficace a tendance à être souhaitable. Cependant, vous devriez considérer quels sont vos besoins réels.
Si vous écrivez des applications clientes Windows Forms et WPF standard destinées à être exécutées sur un ordinateur individuel et susceptibles de constituer la principale application utilisée par l'utilisateur, vous pouvez vous en aller à l'allocation de mémoire plus sereine. (Tant que tout est désalloué.)
Cependant, pour adresser certaines personnes ici qui disent ne pas s'en inquiéter: 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 tenir compte de l’utilisation de la mémoire. Et vous devrez être vigilant. La meilleure façon de résoudre ce problème consiste à concevoir correctement la structure des données et à suivre les meilleures pratiques concernant le moment et les ressources alloués.
Les applications .NET auront une empreinte plus grande par rapport aux applications natives car elles doivent toutes les 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 principalement en veille, les pages de mémoire nécessaires seront remplacées par de la mémoire et ne constitueront donc pas un fardeau aussi lourd pour le système la plupart du temps.
Si vous souhaitez conserver une empreinte réduite, vous devrez réfléchir à l'utilisation de la mémoire. Voici quelques idées:
List<T>
et des types similaires dont la capacité double si nécessaire car ils peuvent générer jusqu'à 50% de déchets.Ce n'est probablement pas une liste exhaustive, mais quelques idées.
Dans ce cas, vous devez prendre en compte le coût de la mémoire du CLR. Le CLR est chargé pour chaque processus .Net et est donc pris en compte dans les considérations de mémoire. Pour un programme aussi simple/petit, le coût du CLR dominera votre empreinte mémoire.
Il serait beaucoup plus instructif de construire une application réelle et de comparer son coût à celui de ce programme de base.
Aucune suggestion spécifique en soi, mais vous pouvez jeter un oeil à CLR Profiler (téléchargement gratuit de Microsoft).
Une fois que vous l’avez installé, jetez un coup d’œil à cette page d’information .
Du comment faire:
Ce guide explique comment utiliser l'outil Générateur de profils CLR pour étudier le profil d'allocation de mémoire de votre application. Vous pouvez utiliser CLR Profiler pour identifier le code causant des problèmes de mémoire, tels que des fuites de mémoire et une récupération de place excessive ou inefficace.
Vous voudrez peut-être examiner l'utilisation de la mémoire d'une application "réelle".
Semblable à Java, le temps d’exécution est fixe, quelle que soit la taille du programme, mais la consommation de mémoire sera beaucoup plus raisonnable à partir de ce point.
Il existe toujours des moyens de réduire le nombre de tâches privées de ce programme simple:
NGEN votre application. Cela supprime les coûts de compilation JIT de votre processus.
Entraînez votre application à l’aide de MPGO réduction de l’utilisation de la mémoire , puis NGEN.
Il existe de nombreuses façons de réduire votre empreinte.
Une chose avec laquelle vous devrez toujours vivre . 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 Datareader, charger le contenu dans un DataTable juste pour l'écrire dans un fichier XML. Vous pouvez facilement rencontrer une exception OutOfMemoryException. OTOH, vous pouvez utiliser un XmlTextWriter et faire défiler le Datareader, en émettant XmlNodes lorsque vous faites défiler le curseur de la base de données. De cette façon, vous avez uniquement l'enregistrement de base de données actuel et sa sortie XML en mémoire. Ce qui n'obtiendra jamais (ou est peu probable) 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, effectuer des tâches (qui génèrent des milliers de nouvelles instances, qui peuvent rester référencées quelque part), et même si vous n'en avez pas besoin par la suite, vous faites toujours référence à tout jusqu'à l'après. Si vous annulez explicitement votre liste d’entrée et vos sous-produits temporaires, cette mémoire peut être réutilisée avant même de quitter votre boucle.
C # a une excellente fonctionnalité appelée itérateurs. Ils vous permettent de diffuser des objets en faisant défiler votre entrée et de ne conserver que l’instance actuelle jusqu’à ce que vous obteniez la suivante. Même en utilisant LINQ, vous n’avez toujours pas besoin de tout conserver parce que vous vouliez qu’il soit filtré.
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) et qu’une petite fraction est nécessaire, vous pouvez écrire un composant COM wrapper qui masque la mémoire de .NET et renvoie uniquement les données stocké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 de mémoire sur le disque au cours d'un processus long peut entraîner la disparition de toute la mémoire physique disponible sur un ordinateur jusqu'au redémarrage de celui-ci. Nous avions un fichier .NET DLL chargé 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. entre 1 jour et une semaine, une machine indiquait une utilisation de mémoire physique de 99% dans le Gestionnaire des tâches, alors qu'aucun processus n'utilisait une utilisation importante de la mémoire. Peu de temps après, la machine devenait inactive, nécessitant un redémarrage forcé. Les serveurs Windows Server 2008 R2 et 2012 R2 s'exécutant sur du matériel physique et virtuel.
Peut-être que le code .NET chargé dans un processus natif y est pour quelque chose, 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 récupérateur de place gérer lui-même l'utilisation de la mémoire.