web-dev-qa-db-fra.com

Méthode recommandée pour forcer le ramassage des ordures en C #

D'après mon expérience, il semble que la plupart des gens vous diront qu'il est déconseillé de forcer un ramassage des ordures, mais dans certains cas, lorsque 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. ça va forcer la collecte? Existe-t-il une meilleure pratique pour le faire?

115
Echostorm

La meilleure pratique est de ne pas forcer un ramassage des ordures.

Selon MSDN:

"Il est possible de forcer la récupération de place en appelant Collect, mais la plupart du temps, cela devrait être évité car cela pourrait 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'incidence négative, 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, envisagez d'utiliser l'instruction "using" et l'interface IDisposable.

Ce lien propose des conseils pratiques pour libérer de la mémoire/le ramassage des ordures, etc.:

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

110
Mark Ingram

La meilleure pratique consiste à ne pas forcer une récupération de place dans la plupart des cas. résolu aurait éliminé la nécessité de forcer la collecte des ordures et aurait grandement accéléré le système.)

Il y a quelques cas lorsque vous en savez plus sur l'utilisation de la mémoire, puis éboueur fait. Il est peu probable que cela soit vrai dans une application multi-utilisateur ou dans un service qui répond à plusieurs demandes à la fois.

Cependant, dans certains traitements en mode batch , vous en savez plus que le CPG. Par exemple. considérez une application qui.

  • Est donné une liste de noms de fichiers sur la ligne de commande
  • Traite un fichier unique, puis écrit le résultat dans un fichier de résultats.
  • Lors du traitement du fichier, crée un grand nombre d’objets lié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 conserve pas beaucoup d'état entre les fichiers qu'il a traités .

Vous peut être en mesure de faire un cas (après avoir soigneusement) testé que vous devriez 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 tant qu’il est endormi . Forcer une collection complète juste avant d'aller dormir peut-être en vaut la peine.

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 récupération de place lorsque je pourrais lui donner des indices sur ce type de choses sans avoir à forcer un GC moi-même.

Voir aussi " Performance Tidbits de Rico Mariani "

31
Ian Ringrose

Regardez comme ça - est-il plus efficace de jeter les ordures de la cuisine quand la poubelle est à 10% ou de les laisser se remplir avant de les sortir?

En ne le laissant pas se remplir, vous perdez votre temps à aller et venir de la poubelle à l'extérieur. Ceci est analogue à ce qui se produit lorsque le thread du 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 domaines AppDomains, de sorte que la récupération de place affecte tous les éléments.

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

Cela POURRAIT être une fois que forcer un GC peut être utile - si votre programme est inactif, la mémoire en cours d'utilisation n'est pas collectée, car il n'y a pas d'allocation.

29
Maxam

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 CPG lorsqu'un document est fermé.

20
denis phillips

Il existe peu de directives générales en matière de programmation qui soient absolues. La moitié du temps, quand quelqu'un dit "tu ne te conduis pas bien", ils ne font que lancer un certain dogme. En C, c'était la peur de choses comme le code ou les threads qui se modifiaient soi-même, dans les langages du GC, il forçait le GC ou l'empêchait de s'exécuter.

Comme c'est le cas avec la plupart des directives et bonnes règles de base (et bonnes pratiques de conception), il est parfois rare de travailler autour de la norme établie. Vous devez être absolument certain de comprendre le cas, que votre cas nécessite l'abrogation de la pratique courante et que vous comprenez les risques et les effets secondaires que vous pouvez causer. Mais il y a 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 judicieux de bloquer le GC dans des langages collectés avec ordures et des endroits où il est judicieux de le déclencher plutôt que d'attendre que cela se produise naturellement. 95% du temps, l’un ou l’autre de ces cas serait un signe de ne pas avoir abordé le problème correctement. Mais une fois sur 20, il y a probablement de bonnes raisons de le justifier.

16
TomB

J'ai appris à ne pas essayer de déjouer le ramassage des ordures. Cela dit, je me limite à utiliser le mot-clé using lorsque vous utilisez des ressources non gérées, telles que des entrées/sorties de fichiers ou des connexions à une base de données.

13
Kon

Un cas que j'ai rencontré récemment et qui nécessitait des appels manuels à GC.Collect() concernait des objets C++ volumineux encapsulés dans de minuscules objets C++ gérés, 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. Appeler manuellement Dispose() sur les objets nécessiterait que je suive le moment où les objets ne sont plus nécessaires, alors qu'appeler GC.Collect() nettoiera tous les objets qui ne sont plus référés .... .

9
Morten

Pas sûr que ce soit une bonne pratique, mais lorsque je travaille avec de grandes quantités d’images en boucle (c.-à-d. Créant et disposant de nombreux objets Graphics/Image/Bitmap), je laisse régulièrement le GC.Collect.

Je pense avoir lu quelque part que le CPG ne fonctionne que lorsque le programme est (principalement) inactif, et non au milieu d'une boucle intensive, de sorte que cela pourrait ressembler à un domaine dans lequel le GC manuel pourrait avoir un sens.

9
Michael Stum

Je pense que vous avez déjà énuméré la meilleure pratique et qu’il n’est PAS recommandé de l’utiliser sauf VRAIMENT nécessaire. Je recommanderais fortement de regarder votre code plus en détail, en utilisant potentiellement des outils de profilage si nécessaire pour répondre à ces questions en premier.

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

Supposons que votre programme ne présente pas de fuites de mémoire, que les objets s’accumulent et qu’ils ne puissent pas être référencés dans la génération 0 car: 1) ils sont référencés depuis longtemps; accédez donc à la génération 1 et à la génération 2; 2) Ce sont de gros objets (> 80K) alors entrez dans LOH (Large Object Heap). Et LOH ne fait pas de compactage comme dans Gen0, Gen1 & Gen2.

Vérifiez le compteur de performance de "Mémoire .NET". Vous pouvez voir que le problème 1) n’est vraiment pas un problème. En règle générale, chaque GC 10 Gen0 déclenchera 1 GC Gen1, et chaque GC 10 Gen1 déclenchera 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 performance "Mémoire .NET" pour vérifier si LOH est gonflé. Si cela pose vraiment un problème, vous pouvez peut-être créer un pool d'objets volumineux, comme le suggère ce blog http://blogs.msdn.com/yunjin/archive/2004/01/27/63642. aspx .

5
Morgan Cheng

Les objets volumineux sont alloués sur LOH (objet volumineux), 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 pense 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 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 jeté des fenêtres de dialogue/formulaires avec de nombreux contrôles, etc. gros objets évidemment), mais en réalité, il n’a constaté aucun effet positif ou négatif à long terme.

4
liggett78

J'aimerais 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 se peut que ce ne soit pas déterministe. Prenez le code de cette msdn link et exécutez le code avec l'itération de boucle d'objet 1 ou 2. Vous trouverez ce que non déterministe signifie (définissez un point de rupture dans le destructeur de l'objet). Précisément, le destructeur n’est pas appelé s’il n’y avait que 1 (ou 2) objets en attente par Wait .. (). [Demande de citation requise]

Si votre code traite de ressources non gérées (ex: descripteurs de fichiers externes), vous devez implémenter des destructeurs (ou des 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, commencez par analyser ce que la sortie pourrait être, puis lancez-le et lisez la raison ci-dessous:

{Le destructeur n'est appelé implicitement qu'à la fin du programme. } Pour nettoyer l'objet de manière déterministe, il faut implémenter IDisposable et appeler explicitement Dispose (). C'est l'essence! :)

4
Vaibhav

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'empirer les choses.

Le .NET GC est bien conçu et adapté pour être adaptatif, ce qui signifie qu’il peut ajuster le seuil GC0/1/2 en fonction de "l’habitude" de l’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 a passé du temps à s'adapter à nouveau à "l'habitude" de votre programme.

Ma suggestion est toujours la confiance. NET GC. Tout problème de mémoire fait surface, vérifiez le compteur de performance "Mémoire .NET" et diagnostiquez moi-même le code.

2
Morgan Cheng

Pas sûr que ce soit une bonne pratique ...

Suggestion: n'implémentez pas 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.

1
user957965

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

IMHO, cela revient à dire "Si vous pouvez prouver que votre programme n'aura jamais de bugs dans le futur, alors allez-y ..."

Très sérieusement, forcer le CPG est utile à des fins de débogage/test. Si vous sentez que vous devez le faire à un autre moment, vous vous trompez ou votre programme a été mal construit. Quoi qu'il en soit, la solution ne force pas le GC ...

0
Orion Edwards