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

108

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

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

Compilé en mode version pour x64 et s'exécutant en dehors de Visual Studio, le gestionnaire de tâches signale ce qui suit:

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 de même mais essaie 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 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 tout de même excessif pour un programme aussi simple. Existe-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 séparé, ce qui signifie que les trucs d'interface utilisateur peuvent être déchargés en toute sécurité, mais occuper 10 Mo quand il est juste assis en arrière-plan semble excessif.

PS Quant à savoir pourquoi je m'en soucierais --- (Power) les utilisateurs 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 à s'embarrasser de l'utilisation de la mémoire des applications en arrière-plan. Même je panique quand je vois Adobe Updater prendre 11 Mo de mémoire et me sentir apaisé par la touche apaisante de Foobar2000, qui peut prendre moins de 6 Mo même en jouant. Je sais que dans les systèmes d'exploitation modernes, ce truc n'a vraiment pas beaucoup d'importance techniquement, mais cela ne veut pas dire que cela n'a pas d'incidence sur la perception.

Robert Fraser
la source
14
Pourquoi vous en soucieriez-vous? L'ensemble de travail privé est assez bas. Les systèmes d'exploitation modernes seront envoyés sur le disque si la mémoire est inutile. Nous sommes en 2009. Sauf si vous construisez des éléments sur des systèmes embarqués, vous ne devriez pas vous soucier de 10 Mo.
Mehrdad Afshari
8
Arrêtez d'utiliser .NET et vous pouvez avoir de petits programmes. Pour charger le framework .NET, beaucoup de grandes DLL doivent être chargées en mémoire.
Adam Sills
2
Les prix de la mémoire chutent de façon exponentielle (oui, vous pouvez maintenant commander un système informatique domestique Dell avec 24 Go de RAM). À moins que votre application n'utilise une optimisation> 500 Mo, elle n'est pas nécessaire.
Alex
28
@LeakyCode Je déteste vraiment que les programmeurs modernes pensent de cette façon, vous devriez vous soucier de l'utilisation de la mémoire de votre application. Je peux dire que la plupart des applications modernes, écrites principalement en java ou c #, sont assez inefficaces en matière de gestion des ressources, et grâce à cela en 2014, nous pouvons exécuter autant d'applications que nous pouvions en 1998 sur win95 et 64 Mo de RAM ... une seule instance de navigateur consomme désormais 2 Go de RAM et un IDE simple d'environ 1 Go. Ram est bon marché, mais cela ne signifie pas que vous devriez le gaspiller.
Petr
6
@ Petr Vous devriez vous soucier de la gestion des ressources. Le temps du programmeur est également une ressource. Veuillez ne pas trop généraliser le gaspillage de 10 Mo à 2 Go.
Mehrdad Afshari

Réponses:

33
  1. Vous voudrez peut-être consulter l' empreinte mémoire .NET EXE de la question de débordement de pile .
  2. Le billet de blog MSDN Working set! = Empreinte mémoire réelle consiste à démystifier le jeu de travail, la mémoire de processus et comment effectuer des calculs précis sur votre consommation totale en RAM.

Je ne dirai pas que vous devriez ignorer l'empreinte mémoire de votre application - évidemment, plus petite et plus efficace a tendance à être souhaitable. Cependant, vous devez tenir compte de vos besoins réels.

Si vous écrivez une application client Windows Forms et WPF standard destinée à s'exécuter sur le PC d'un individu et susceptible d'être la principale application dans laquelle l'utilisateur opère, vous pouvez vous en sortir en étant plus indifférent à l'allocation de mémoire. (Tant que tout est désalloué.)

Cependant, pour s'adresser à certaines personnes ici qui disent de ne pas s'en inquiéter: si vous écrivez une application Windows Forms qui s'exécutera dans un environnement de services de terminaux, 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 une bonne structure de données et à suivre les meilleures pratiques concernant le moment et ce que vous allouez.

John Rudy
la source
45

Les applications .NET auront une plus grande empreinte par rapport aux applications natives en raison du fait qu'elles doivent toutes deux charger le runtime et l'application dans le processus. Si vous voulez quelque chose de vraiment bien rangé, .NET n'est peut-être pas 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 la mémoire et ne seront donc pas vraiment un fardeau pour le système dans son ensemble la plupart du temps.

Si vous souhaitez conserver un faible encombrement, vous devrez penser à l'utilisation de la mémoire. Voici quelques idées:

  • Réduisez le nombre d'objets et assurez-vous de ne pas conserver une instance plus longtemps que nécessaire.
  • Soyez conscient des List<T>types similaires qui doublent la capacité en cas de besoin, car ils peuvent entraîner jusqu'à 50% de déchets.
  • Vous pouvez envisager d'utiliser des types valeur par rapport aux types référence pour forcer plus de mémoire sur la pile, mais gardez à l'esprit que l'espace de pile par défaut n'est que de 1 Mo.
  • Évitez les objets de plus de 85000 octets, car ils iront à LOH qui n'est pas compacté et peuvent donc facilement se fragmenter.

Ce n'est probablement pas une liste exhaustive, en aucun cas, mais juste quelques idées.

Brian Rasmussen
la source
IOW, les mêmes types de techniques qui permettent de réduire la taille du code natif fonctionnent dans .NET?
Robert Fraser
Je suppose qu'il y a un certain chevauchement, mais avec le code natif, vous avez plus de choix en ce qui concerne l'utilisation de la mémoire.
Brian Rasmussen
17

Une chose que vous devez considérer dans ce cas est le coût de la mémoire du CLR. Le CLR est chargé pour chaque processus .Net et prend donc en compte les considérations relatives à la 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 de voir le coût de celle-ci par rapport au coût de ce programme de base.

JaredPar
la source
7

Aucune suggestion spécifique en soi, mais vous pouvez jeter un œil au profileur CLR (téléchargement gratuit de Microsoft).
Une fois que vous l'avez installé, jetez un œil à cette page de procédures .

Du mode d'emploi:

Cette procédure 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 des fuites de mémoire et un garbage collection excessif ou inefficace.

Donut
la source
7

Pourrait vouloir regarder l'utilisation de la mémoire d'une "vraie" application.

Semblable à Java, il y a une certaine quantité fixe de surcharge pour l'exécution quelle que soit la taille du programme, mais la consommation de mémoire sera beaucoup plus raisonnable après ce point.

Eric Petroelje
la source
4

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

  1. NGEN votre application. 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, puis en la NGEN.

Feng Yuan
la source
2

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

Une chose avec laquelle vous devrez toujours vivre dans .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 ont besoin de 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 exécuter une OutOfMemoryException. OTOH, vous pouvez utiliser un XmlTextWriter et faire défiler le Datareader, émettant des XmlNodes lorsque vous faites défiler le curseur de la base de données. De cette façon, vous ne disposez que de l'enregistrement de base de données actuel et de 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 plus élevée et peut donc être réutilisé.

Il en va de même pour obtenir une liste de certaines instances, faire des choses (qui engendrent 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'après le foreach. Annulation explicite de votre liste d'entrée et de 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 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 simplement parce que vous vouliez qu'il soit filtré.

Robert Giesecke
la source
1

Répondre à la question générale du titre et non à la question spécifique:

Si vous utilisez un composant COM qui renvoie beaucoup de données (disons de grands tableaux 2xN de doubles) et qu'une petite fraction seulement est nécessaire, vous pouvez écrire un composant COM wrapper qui cache la mémoire de .NET et ne renvoie que les données qui sont 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.

Peter Mortensen
la source
0

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

Le chargement du code .NET dans un processus natif a peut-être quelque chose à voir avec cela, mais utilisez EmptyWorkingSet (ou SetProcessWorkingSetSize) à vos propres risques. Peut-être ne l'utiliser qu'une seule 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.

Stuart Welch
la source