Meilleures pratiques pour forcer le nettoyage de la mémoire en C #

118

D'après mon expérience, il semble que la plupart des gens vous diront qu'il n'est pas judicieux de forcer un ramassage des ordures, mais dans certains cas, où vous travaillez avec des objets volumineux qui ne sont pas toujours collectés dans la génération 0 mais où la mémoire est un problème, c'est -il ok pour forcer la collecte? Existe-t-il une meilleure pratique pour le faire?

Echostorm
la source

Réponses:

112

La meilleure pratique consiste à ne pas forcer un garbage collection.

Selon MSDN:

"Il est possible de forcer le garbage collection en appelant Collect, mais la plupart du temps, cela doit être évité car cela peut créer des problèmes de performances."

Cependant, si vous pouvez tester votre code de manière fiable pour confirmer que l'appel de Collect () n'aura pas d'impact négatif, alors continuez ...

Essayez simplement de vous assurer que les objets sont nettoyés lorsque vous n'en avez plus besoin. Si vous avez des objets personnalisés, utilisez l'instruction "using" et l'interface IDisposable.

Ce lien contient de bons conseils pratiques concernant la libération de mémoire / garbage collection, etc.:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

Mark Ingram
la source
3
En outre, vous pouvez définir différents msdn.microsoft.com/en-us/library/bb384202.aspx de LatencyMode
David d C e Freitas
4
Si vos objets pointent vers de la mémoire non gérée, vous pouvez informer le garbage collector via l'API GC.AddMemoryPressure ( msdn.microsoft.com/en-us/library/… ). Cela donne au garbage collector plus d'informations sur votre système sans interférer avec les algorithmes de collecte.
Govert
+1: * regardez à l'aide de la "déclaration d'utilisation" et de l'interface IDisposable. * Je n'envisagerais même pas de forcer sauf en dernier recours - bon conseil (lire comme "avertissement"). Cependant, je force la collecte dans un test unitaire pour simuler la perte d'une référence active dans une opération back-end - en lançant finalement un fichier TargetOfInvocationNullException.
IAbstract
33

Regardez les choses de cette façon - est-il plus efficace de jeter les ordures de la cuisine lorsque la poubelle est à 10% ou de la laisser se remplir avant de la sortir?

En ne le laissant pas se remplir, vous perdez votre temps à aller et à revenir de la poubelle à l'extérieur. Ceci est analogue à ce qui se passe lorsque le thread GC s'exécute - tous les threads gérés sont suspendus pendant son exécution. Et si je ne me trompe pas, le thread GC peut être partagé entre plusieurs AppDomains, de sorte que le garbage collection les affecte tous.

Bien sûr, vous pourriez rencontrer une situation où vous n'ajouterez rien à la poubelle de sitôt - par exemple, si vous prévoyez de prendre des vacances. Ensuite, ce serait une bonne idée de jeter les ordures avant de sortir.

Cela PEUT être une fois que forcer un GC peut aider - si votre programme est inactif, la mémoire utilisée n'est pas récupérée car il n'y a pas d'allocations.

Maxam
la source
5
Si vous aviez un bébé qui mourrait si vous le laissiez plus d'une minute et que vous n'aviez qu'une minute pour manipuler les ordures, alors vous voudriez en faire un peu à chaque fois au lieu de tout à la fois. Malheureusement, la méthode GC :: Collect () n'est pas plus rapide plus vous l'appelez. Donc, pour un moteur en temps réel, si vous ne pouvez pas simplement utiliser le mécanisme de suppression et laisser le GC regrouper vos données, vous ne devriez pas utiliser un système géré - selon ma réponse (probablement en dessous de celui-ci, lol).
Jin
1
Dans mon cas, j'exécute un algorithme A * (chemin le plus court) RÉPÉTEMENT pour le régler sur les performances ... qui, en production, ne sera exécuté qu'une seule fois (sur chaque "carte"). Je veux donc que le GC soit fait avant chaque itération, en dehors de mon "bloc de mesure des performances", car je pense que cela modélise de plus près la situation en production, dans laquelle le GC pourrait / devrait être forcé après avoir parcouru chaque "carte".
corlettk
L'appel de GC avant le bloc mesuré ne modélise pas en fait la situation en production, car en production, le GC se fera à des moments imprévisibles. Pour atténuer cela, vous devez prendre une mesure longue qui comprendra plusieurs exécutions GC et prendre en compte les pics pendant le GC dans votre analyse et vos statistiques.
Ran
32

La meilleure pratique consiste à ne pas forcer un garbage collection dans la plupart des cas. (Tous les systèmes sur lesquels j'ai travaillé et qui avaient forcé le ramassage des ordures, avaient des problèmes de soulignement qui, s'ils étaient résolus, auraient éliminé le besoin de forcer le ramassage des ordures et accéléré considérablement le système.)

Il existe quelques casvous en savez plus sur l'utilisation de la mémoire que le ramasse-miettes. Il est peu probable que cela soit vrai dans une application multi-utilisateur ou un service qui répond à plusieurs demandes à la fois.

Cependant, dans certains traitements par lots, vous en savez plus que le GC. Par exemple, considérez une application qui.

  • Reçoit une liste de noms de fichiers sur la ligne de commande
  • Traite un seul fichier, puis écrit le résultat dans un fichier de résultats.
  • Lors du traitement du fichier, crée de nombreux objets interconnectés qui ne peuvent pas être collectés tant que le traitement du fichier n'est pas terminé (par exemple, un arbre d'analyse)
  • Ne garde pas beaucoup d’état entre les fichiers qu’il a traités .

Vous pourrez peut- être faire un cas (après avoir testé minutieusement) que vous devez forcer un garbage collection complet après avoir traité chaque fichier.

Un autre cas est un service qui se réveille toutes les quelques minutes pour traiter certains éléments et ne conserve aucun état pendant son sommeil . Alors forcer une collection complète juste avant d'aller dormir peut être utile.

La seule fois où j'envisagerais de forcer une collection, c'est quand je sais que beaucoup d'objets ont été créés récemment et que très peu d'objets sont actuellement référencés.

Je préférerais avoir une API de ramasse-miettes quand je pourrais lui donner des conseils sur ce type de chose sans avoir à forcer moi-même un GC.

Voir aussi " Les morceaux de performance de Rico Mariani "

Ian Ringrose
la source
2
Analogie: Playschool (système) conserve des crayons (ressources). En fonction du nombre d'enfants (tâches) et de la rareté des couleurs, l'enseignant (.Net) décide comment répartir et partager entre les enfants. Lorsqu'une couleur rare est demandée, l'enseignant peut allouer à partir du pool ou en rechercher une qui n'est pas utilisée. L'enseignant a la discrétion de ramasser périodiquement des crayons inutilisés (ramasse-miettes) pour garder les choses en ordre (optimiser l'utilisation des ressources). En général, un parent (programmeur) ne peut pas prédéterminer la meilleure politique de rangement des crayons en classe. Il est peu probable que la sieste programmée d'un enfant soit un bon moment pour interférer avec la coloration des autres enfants.
AlanK
1
@AlanK, j'aime ça, car quand les enfants rentrent à la maison pour la journée, c'est un très bon moment pour que les enseignants aident à faire un bon rangement sans que les enfants ne se gênent. (Les systèmes récents sur lesquels j'ai travaillé viennent de redémarrer le processus de service à de tels moments au lieu de forcer GC.)
Ian Ringrose
21

Je pense que l'exemple donné par Rico Mariani était bon: il peut être approprié de déclencher un GC s'il y a un changement significatif dans l'état de l'application. Par exemple, dans un éditeur de document, il peut être correct de déclencher un GC lorsqu'un document est fermé.

Denis Phillips
la source
2
Ou juste avant d'ouvrir un grand objet contigu qui a montré un historique de défaillance et ne présente aucune résolution efficace pour augmenter sa granularité.
crokusek
17

Il existe peu de directives générales en matière de programmation qui soient absolues. La moitié du temps, quand quelqu'un dit «vous faites mal», il ne fait que jaillir une certaine quantité de dogme. En C, c'était la peur de choses comme le code ou les threads qui s'auto-modifiaient, dans les langages GC, cela forçait le GC ou empêchait le GC de fonctionner.

Comme c'est le cas avec la plupart des directives et de bonnes règles empiriques (et de bonnes pratiques de conception), il existe de rares occasions où il est logique de contourner la norme établie. Vous devez être très sûr de bien comprendre le cas, que votre cas nécessite vraiment l'abrogation de la pratique courante et que vous comprenez les risques et les effets secondaires que vous pouvez causer. Mais il existe de tels cas.

Les problèmes de programmation sont très variés et nécessitent une approche flexible. J'ai vu des cas où il est logique de bloquer GC dans des langages récupérés et des endroits où il est logique de le déclencher plutôt que d'attendre qu'il se produise naturellement. 95% du temps, l'un ou l'autre signifierait que le problème n'a pas été abordé correctement. Mais 1 fois sur 20, il y a probablement un argument valable à faire pour cela.


la source
12

J'ai appris à ne pas essayer de déjouer le ramassage des ordures. Cela dit, je m'en tiens simplement à l'utilisation de usingmots-clés lorsque je traite des ressources non gérées telles que les E / S de fichiers ou les connexions à la base de données.

Kon
la source
27
compilateur? qu'est-ce que le compilateur a à voir avec GC? :)
KristoferA
1
Rien, il ne compile et optimise que le code. Et cela n'a certainement rien à voir avec le CLR ... ou même .NET.
Kon
1
Les objets occupant beaucoup de mémoire, comme les très grandes images, peuvent finir par ne pas être récupérés, à moins que vous ne les récupériez explicitement. Je pense que (gros objets) était le problème du PO, plus ou moins.
code4life
1
L'emballer dans une utilisation garantira qu'il est programmé pour GC une fois qu'il est hors de portée. À moins que l'ordinateur n'explose, cette mémoire sera probablement nettoyée.
Kon
9

Je ne sais pas si c'est une bonne pratique, mais lorsque je travaille avec de grandes quantités d'images en boucle (c'est-à-dire en créant et en supprimant beaucoup d'objets Graphics / Image / Bitmap), je laisse régulièrement GC.Collect.

Je pense avoir lu quelque part que le GC ne fonctionne que lorsque le programme est (principalement) inactif, et non au milieu d'une boucle intensive, ce qui pourrait ressembler à un domaine où le GC manuel pourrait avoir un sens.

Michael Stum
la source
Etes-vous sûr que vous en avez besoin? Le GC va recueillir si elle a besoin de mémoire, même si votre code est pas inactif.
Konrad Rudolph
Je ne sais pas comment c'est dans .net 3.5 SP1 maintenant, mais auparavant (1.1 et je crois que j'ai testé contre 2.0) cela a fait une différence dans l'utilisation de la mémoire. Bien sûr, le GC collectera toujours en cas de besoin, mais vous risquez de perdre 100 Mo de RAM alors que vous n'en avez besoin que de 20. Aurait besoin de quelques tests supplémentaires
Michael Stum
2
GC est déclenché sur l'allocation de mémoire lorsque la génération 0 atteint un certain seuil (par exemple 1 Mo), pas lorsque "quelque chose est inactif". Sinon, vous pourriez vous retrouver avec OutOfMemoryException dans une boucle en allouant simplement et en supprimant immédiatement des objets.
liggett78
5
les 100 Mo de RAM ne sont pas gaspillés si aucun autre processus n'en a besoin. Cela vous donne une belle amélioration des performances :-P
Orion Edwards
9

Un cas que j'ai récemment rencontré et qui nécessitait des appels manuels GC.Collect()était lorsque je travaillais avec de grands objets C ++ qui étaient enveloppés dans de petits objets C ++ gérés, qui à leur tour étaient accessibles à partir de C #.

Le garbage collector n'a jamais été appelé car la quantité de mémoire gérée utilisée était négligeable, mais la quantité de mémoire non gérée utilisée était énorme. L'appel manuel Dispose()des objets nécessiterait que je surveille moi-même quand les objets ne sont plus nécessaires, tandis que l'appel GC.Collect()nettoiera tous les objets qui ne sont plus référencés .....

Morten
la source
6
Une meilleure façon de résoudre ce problème est d'appeler GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)le constructeur et plus tard GC.RemoveMemoryPressure(addedSize)le finaliseur. De cette façon, le garbage collector s'exécutera automatiquement, en tenant compte de la taille des structures non gérées qui pourraient être collectées. stackoverflow.com/questions/1149181/…
HugoRune
Et un moyen encore meilleur de résoudre le problème est d'appeler Dispose (), ce que vous êtes censé faire de toute façon.
fabspro
2
Le meilleur moyen est d'utiliser la structure Using. Essayez / Enfin .Disposer est un problème
TamusJRoyce
7

Je pense que vous avez déjà énuméré la meilleure pratique et c'est de NE PAS l'utiliser sauf VRAIMENT nécessaire. Je recommanderais fortement d'examiner votre code plus en détail, en utilisant éventuellement des outils de profilage si nécessaire pour répondre d'abord à ces questions.

  1. Avez-vous quelque chose dans votre code qui déclare des éléments à une portée plus large que nécessaire
  2. L'utilisation de la mémoire est-elle vraiment trop élevée
  3. Comparez les performances avant et après l'utilisation de GC.Collect () pour voir si cela aide vraiment.
Vendeurs Mitchel
la source
5

Supposons que votre programme n'ait pas de fuite de mémoire, que les objets s'accumulent et ne puissent pas être GC-ed dans Gen 0 parce que: 1) Ils sont référencés pendant longtemps alors entrez dans Gen1 & Gen2; 2) Ce sont des objets volumineux (> 80 Ko), alors entrez dans LOH (Large Object Heap). Et LOH ne fait pas de compactage comme dans Gen0, Gen1 et Gen2.

Vérifiez le compteur de performances de ".NET Memory" vous pouvez voir que le problème 1) n'est vraiment pas un problème. En règle générale, tous les 10 GC Gen0 déclenchent 1 GC Gen1 et tous les 10 GC Gen1 déclenchent 1 GC Gen2. Théoriquement, GC1 et GC2 ne peuvent jamais être GC-ed s'il n'y a pas de pression sur GC0 (si l'utilisation de la mémoire du programme est vraiment câblée). Cela ne m'arrive jamais.

Pour le problème 2), vous pouvez vérifier le compteur de performances «.NET Memory» pour vérifier si LOH est gonflé. Si c'est vraiment un problème avec votre problème, vous pouvez peut-être créer un grand pool d'objets comme le suggère ce blog http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .

Morgan Cheng
la source
4

Les grands objets sont alloués sur LOH (grand tas d'objets), pas sur la génération 0. Si vous dites qu'ils ne sont pas récupérés avec la génération 0, vous avez raison. Je crois qu'ils ne sont collectés que lorsque le cycle complet du GC (générations 0, 1 et 2) se produit.

Cela étant dit, je crois que, de l'autre côté, GC ajustera et collectera la mémoire de manière plus agressive lorsque vous travaillez avec de gros objets et que la pression de la mémoire augmente.

Il est difficile de dire s'il faut collecter ou non et dans quelles circonstances. J'avais l'habitude de faire GC.Collect () après avoir supprimé les fenêtres / formulaires de dialogue avec de nombreux contrôles, etc. grands objets évidemment), mais n'a en fait pas remarqué d'effets positifs ou négatifs sur le long terme.

liggett78
la source
4

Je voudrais ajouter que: L'appel de GC.Collect () (+ WaitForPendingFinalizers ()) est une partie de l'histoire. Comme mentionné à juste titre par d'autres, GC.COllect () est une collection non déterministe et est laissée à la discrétion du GC lui-même (CLR). Même si vous ajoutez un appel à WaitForPendingFinalizers, il peut ne pas être déterministe. Prenez le code de ce lien msdn et exécutez le code avec l'itération de la boucle d'objet comme 1 ou 2. Vous trouverez ce que signifie non déterministe (définir un point de rupture dans le destructeur de l'objet). Précisément, le destructeur n'est pas appelé quand il y avait juste 1 (ou 2) objets persistants par Wait .. (). [Citation reqd.]

Si votre code traite des ressources non gérées (ex: descripteurs de fichiers externes), vous devez implémenter des destructeurs (ou finaliseurs).

Voici un exemple intéressant:

Remarque : Si vous avez déjà essayé l'exemple ci-dessus à partir de MSDN, le code suivant va effacer l'air.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Je suggère d'analyser d'abord ce que pourrait être la sortie, puis de l'exécuter, puis de lire la raison ci-dessous:

{Le destructeur n'est appelé implicitement qu'une fois le programme terminé. } Afin de nettoyer l'objet de manière déterministe, il faut implémenter IDisposable et faire un appel explicite à Dispose (). Voilà l'essence! :)

Vaibhav
la source
2

Une dernière chose, déclencher explicitement GC Collect ne peut PAS améliorer les performances de votre programme. Il est tout à fait possible d'aggraver les choses.

Le .NET GC est bien conçu et réglé pour être adaptatif, ce qui signifie qu'il peut ajuster le seuil GC0 / 1/2 en fonction de «l'habitude» d'utilisation de la mémoire de votre programme. Ainsi, il sera adapté à votre programme après un certain temps d'exécution. Une fois que vous appelez explicitement GC.Collect, les seuils seront réinitialisés! Et le .NET doit prendre du temps pour s'adapter à nouveau à «l'habitude» de votre programme.

Ma suggestion est toujours de faire confiance à .NET GC. Tout problème de mémoire fait surface, vérifiez le compteur de performances ".NET Memory" et diagnostiquez mon propre code.

Morgan Cheng
la source
6
Je pense qu'il vaut mieux que vous fusionniez cette réponse avec votre réponse précédente.
Salamander2007
1

Je ne sais pas si c'est une bonne pratique ...

Suggestion: ne mettez pas en œuvre ceci ou quoi que ce soit en cas de doute. Réévaluez lorsque les faits sont connus, puis effectuez des tests de performance avant / après pour vérifier.

utilisateur957965
la source
0

Cependant, si vous pouvez tester votre code de manière fiable pour confirmer que l'appel de Collect () n'aura pas d'impact négatif, alors continuez ...

À mon humble avis, cela revient à dire "Si vous pouvez prouver que votre programme n'aura jamais de bogues à l'avenir, alors allez-y ..."

En toute sincérité, forcer le GC est utile à des fins de débogage / test. Si vous sentez que vous devez le faire à un autre moment, alors soit vous vous trompez, soit votre programme a été mal construit. Quoi qu'il en soit, la solution n'est pas de forcer le GC ...

Orion Edwards
la source
"alors soit vous vous trompez, soit votre programme a été mal construit. Quoi qu'il en soit, la solution n'est pas de forcer le GC ..." Les absolus ne sont presque toujours pas vrais. Il y a des circonstances exceptionnelles, cela a du sens.
lance le