Best Practice zum Erzwingen von Garbage Collection in C #

Nach meiner Erfahrung scheinen die meisten Leute Ihnen zu sagen, dass es unklug ist, eine Speicherbereinigung zu erzwingen, aber in einigen Fällen, in denen Sie mit großen Objekten arbeiten, die nicht immer in der 0-Generation gesammelt werden, aber Speicher ein Problem ist ist es in Ordnung, das Sammeln zu erzwingen? Gibt es dafür eine Best Practice?

Die beste Vorgehensweise besteht darin, keine Speicherbereinigung zu erzwingen.

Laut MSDN:

“Es ist möglich, die Garbage Collection durch Aufrufen von Collect zu erzwingen. Dies sollte jedoch in den meisten Fällen vermieden werden, da dies zu performancesproblemen führen kann.”

Wenn Sie jedoch Ihren Code zuverlässig testen können, um zu bestätigen, dass der Aufruf von Collect () keine negativen Auswirkungen hat, dann fahren Sie fort …

Versuchen Sie einfach, sicherzustellen, dass Objekte bereinigt werden, wenn Sie sie nicht mehr benötigen. Wenn Sie benutzerdefinierte Objekte haben, schauen Sie sich die “using-statement” und die IDisposable-Schnittstelle an.

Dieser Link bietet einige gute praktische Ratschläge bezüglich der Speicher- / Speicherbereinigung usw.:

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

Die beste Vorgehensweise besteht darin, in den meisten Fällen keine Speicherbereinigung zu erzwingen. (Jedes System, an dem ich gearbeitet habe, hatte Garbage Collections erzwingen müssen, hatte Probleme hervorhebt, die, wenn es getriggers würde, die Notwendigkeit, die Garbage Collection zu erzwingen, beseitigt und das System stark beschleunigt hätte.)

Es gibt ein paar Fälle, in denen Sie mehr über die Speichernutzung wissen als der Garbage Collector. Dies trifft wahrscheinlich nicht auf eine Mehrbenutzeranwendung oder einen Dienst zu, der mehr als eine Anfrage gleichzeitig beantwortet.

Bei einigen Batch-Verarbeitungsprozessen kennen Sie jedoch mehr als den GC. Betrachten Sie zum Beispiel eine Anwendung, die.

  • Ist eine Liste von Dateinamen in der Befehlszeile angegeben
  • Verarbeitet eine einzelne Datei und schreibt das Ergebnis in eine Ergebnisdatei.
  • Erstellt während der Verarbeitung der Datei viele miteinander verknüpfte Objekte, die erst gesammelt werden können, wenn die Verarbeitung der Datei abgeschlossen ist (z. B. ein Syntaxbaum)
  • Der Übereinstimmungsstatus zwischen den verarbeiteten Dateien wird nicht beibehalten .

Sie können möglicherweise (nach sorgfältiger Prüfung) feststellen, dass Sie eine vollständige Speicherbereinigung erzwingen müssen, nachdem Sie jede Datei verarbeitet haben.

Ein weiterer Fall ist ein Dienst, der alle paar Minuten zur Verarbeitung einiger Elemente aufwacht und keinen Status behält, während er schläft . Dann kann es sich lohnen, eine vollständige Sammlung kurz vor dem Schlafengehen zu erzwingen.

Die einzige Zeit, die ich erwägen würde, eine Sammlung zu erzwingen, ist, wenn ich weiß, dass in letzter Zeit viele Objekte erstellt wurden und nur sehr wenige Objekte referenziert werden.

Ich hätte lieber eine Garbage-Collection-API, wenn ich Hinweise zu dieser Art von Dingen geben könnte, ohne einen GC selbst erzwingen zu müssen.

Siehe auch ” Rico Marianis Performance Leckerbissen ”

Schauen Sie es sich so an – ist es effizienter, den Küchenmüll wegzucasting, wenn der Mülleimer 10% beträgt, oder ihn vor dem Herausnehmen zu füllen?

Wenn du es nicht vollstopfen lässt, verschwendest du deine Zeit damit, zu dem Mülleimer draußen zu laufen. Dies entspricht der Vorgehensweise beim Ausführen des GC-Threads – alle verwalteten Threads werden während der Ausführung ausgesetzt. Und wenn ich mich nicht irre, kann der GC-Thread unter mehreren AppDomains geteilt werden, so dass die Garbage-Collection alle davon betrifft.

Natürlich könnte es passieren, dass Sie dem Müll bald nichts mehr hinzufügen – sagen wir, wenn Sie Urlaub machen. Dann wäre es eine gute Idee, den Müll raus zu casting, bevor Sie ausgehen.

Dies könnte einmal sein, dass das Erzwingen eines GC helfen kann – wenn Ihr Programm im Leerlauf ist, wird der verwendete Speicher nicht als Müll gesammelt, weil es keine Zuordnungen gibt.

Ich denke, das Beispiel von Rico Mariani war gut: Es könnte angemessen sein, einen GC auszulösen, wenn sich der Status der Anwendung signifikant ändert. Beispielsweise kann es in einem Dokumenteditor OK sein, einen GC zu starten, wenn ein Dokument geschlossen wird.

Es gibt einige allgemeine Richtlinien in der Programmierung, die absolut sind. Die Hälfte der Zeit, wenn jemand sagt “Du machst es falsch”, spucken sie nur ein gewisses Maß an Dogma. In C war es früher die Angst vor Dingen wie selbst modifizierendem Code oder Threads, in GC-Sprachen erzwingt es den GC oder verhindert alternativ, dass der GC läuft.

Wie bei den meisten Leitlinien und guten Faustregeln (und guten Designpraktiken) gibt es seltene Fälle, in denen es sinnvoll ist, die festgelegte Norm zu umgehen. Sie müssen sehr sicher sein, dass Sie den Fall verstehen, dass Ihr Fall wirklich die Abschaffung der üblichen Praxis erfordert und dass Sie die Risiken und Nebenwirkungen verstehen, die Sie verursachen können. Aber es gibt solche Fälle.

Programmierprobleme sind vielfältig und erfordern einen flexiblen Ansatz. Ich habe Fälle gesehen, in denen es sinnvoll ist, GC in Garbage Collection-Sprachen und an Stellen zu blockieren, an denen es sinnvoll ist, sie auszulösen, anstatt darauf zu warten, dass sie auf natürliche Weise auftreten. In 95% der Fälle wäre dies ein Wegweiser, das Problem nicht richtig angegangen zu haben. Aber in 20 Fällen gibt es wahrscheinlich einen gültigen Fall dafür.

Ich habe gelernt, nicht zu versuchen, die Müllsammlung zu überlisten. Mit dem gesagt, ich bleibe nur bei Verwendung using Schlüsselwort im Umgang mit nicht verwalteten Ressourcen wie Datei-I / O oder databaseverbindungen.

Ein Fall, den ich kürzlich GC.Collect() , erforderte manuelle Aufrufe von GC.Collect() wenn mit großen C ++ – Objekten gearbeitet wurde, die in winzige verwaltete C ++ – Objekte verpackt waren, auf die wiederum von C # zugegriffen wurde.

Der Garbage Collector wurde nie aufgerufen, da die Größe des verwal- teten Arbeitsspeichers vernachlässigbar war, die Menge an nicht verwaltetem Speicher jedoch sehr groß war. Der manuelle Aufruf von Dispose() für die Objekte würde erfordern, dass ich GC.Collect() wenn Objekte selbst nicht mehr benötigt werden, während das Aufrufen von GC.Collect() alle Objekte bereinigen wird, auf die nicht mehr verwiesen wird.

Nicht sicher, ob es sich um eine bewährte Methode handelt, aber wenn Sie mit großen Mengen von Bildern in einer Schleife arbeiten (dh viele Grafik- / Bild- / Bitmap-Objekte erstellen und entsorgen), lasse ich regelmäßig GC.Collect laufen.

Ich denke, ich habe irgendwo gelesen, dass der GC nur dann läuft, wenn das Programm (meistens) leer ist und nicht in der Mitte einer intensiven Schleife, so dass das wie ein Bereich aussehen könnte, in dem manuelle GC sinnvoll wäre.

Ich denke, Sie haben bereits die beste Praxis aufgeführt und das ist NICHT zu verwenden, es sei denn, es ist wirklich notwendig. Ich empfehle Ihnen dringend, Ihren Code genauer zu betrachten und Profiling-Tools zu verwenden, um diese Fragen zu beantworten.

  1. Haben Sie etwas in Ihrem Code, das Elemente in einem größeren scope deklariert als benötigt?
  2. Ist die Speichernutzung wirklich zu hoch?
  3. Vergleichen Sie die performance vor und nach der Verwendung von GC.Collect (), um festzustellen, ob es wirklich hilft.

Angenommen, Ihr Programm hat keinen Speicherverlust, Objekte sammeln sich an und können in Gen 0 nicht GC-ediert werden, weil: 1) sie lange Zeit referenziert werden, also in Gen1 & Gen2; 2) Sie sind große Objekte (> 80K), also geh in LOH (Large Object Heap). Und LOH kompaktiert nicht wie in Gen0, Gen1 & Gen2.

Überprüfen Sie den performancesindikator von “.NET Memory” können Sie sehen, dass das 1) Problem wirklich kein Problem ist. Im Allgemeinen triggers jeder 10 Gen0 GC 1 Gen1 GC aus und alle 10 Gen1 GC 1 Gen2 GC. Theoretisch können GC1 und GC2 niemals GC-ediert werden, wenn kein Druck auf GC0 vorliegt (wenn die Programmspeicherbelegung tatsächlich verdrahtet ist). Es passiert mir nie.

Bei Problem 2) können Sie den performancesindikator “.NET-Speicher” überprüfen, um zu überprüfen, ob LOH aufgebläht ist. Wenn es sich wirklich um ein Problem für Ihr Problem handelt, können Sie möglicherweise einen Pool für große Objekte erstellen, wie in diesem Blog http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx vorgeschlagen .

Große Objekte werden auf LOH (großer Object-Heap) zugewiesen, nicht auf Gen 0. Wenn Sie sagen, dass sie nicht mit Gain 0 müllsammeln, haben Sie recht. Ich glaube, sie werden nur gesammelt, wenn der vollständige GC-Zyklus (Generationen 0, 1 und 2) passiert.

Davon abgesehen glaube ich, dass GC bei der Arbeit mit großen Objekten den Speicher aggressiver anpassen und sammeln wird und der Speicherdruck steigt.

Es ist schwer zu sagen, ob und unter welchen Umständen gesammelt werden soll. Ich habe GC.Collect () nach dem Löschen von Dialogfenstern / Formularen mit zahlreichen Steuerelementen usw. verwendet (weil zu dem Zeitpunkt, zu dem das Formular und seine Steuerelemente in Gen 2 enden, weil viele Instanzen von Geschäftsobjekten erstellt wurden / viele Daten geladen wurden) große Objekte offensichtlich), aber auf diese Weise tatsächlich keine positiven oder negativen Effekte feststellten.

Ich möchte hinzufügen, dass: GC.Collect () aufrufen (+ WaitForPendingFinalizers ()) ist ein Teil der Geschichte. Wie von anderen zu Recht erwähnt, ist GC.COpect () eine nicht-deterministische Sammlung und liegt im Ermessen des GC selbst (CLR). Selbst wenn Sie WaitForPendingFinalizers einen Aufruf hinzufügen, ist dieser möglicherweise nicht deterministisch. Nimm den Code von dieser msdn- Verknüpfung und führe den Code mit der Objektschleifeniteration als 1 oder 2 durch. Du wirst finden, was nicht-deterministisch ist (setze einen Unterbrechungspunkt im Destruktor des Objekts). Genauer gesagt, der Destruktor wird nicht aufgerufen, wenn nur 1 (oder 2) veraltete Objekte von Wait .. () vorhanden waren. [Zitat erforderlich]

Wenn Ihr Code nicht verwaltete Ressourcen verwendet (z. B. externe Dateihandles), müssen Destruktoren (oder Finalizer) implementiert werden.

Hier ist ein interessantes Beispiel:

Hinweis : Wenn Sie das obige Beispiel bereits aus MSDN versucht haben, wird der folgende Code die Luft löschen.

 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 = ""; } } 

Ich schlage vor, zuerst zu analysieren, was die Ausgabe sein könnte und dann laufen und dann den Grund lesen:

{Der Destruktor wird nur implizit aufgerufen, wenn das Programm beendet ist. } Um das Objekt deterministisch zu bereinigen, muss IDisposable implementiert und explizit auf Dispose () zugegriffen werden. Das ist die Essenz! 🙂

Eine weitere Sache, die GC Collect explizit austriggers, kann NICHT die performance Ihres Programms verbessern. Es ist durchaus möglich, es noch schlimmer zu machen.

Der .NET GC ist gut entworfen und angepasst, um adaptiv zu sein, was bedeutet, dass er den GC0 / 1/2-Schwellenwert entsprechend der “Gewohnheit” der Programmspeicherbelegung einstellen kann. So wird es nach einiger Zeit an Ihr Programm angepasst. Sobald Sie GC.Collect explizit aufrufen, werden die Schwellenwerte zurückgesetzt! Und das .NET muss sich Zeit nehmen, um sich wieder an die “Gewohnheit” Ihres Programms anzupassen.

Mein Vorschlag ist immer Vertrauen .NET GC. Jedes Speicherproblem Oberflächen, überprüfen Sie “.NET Memory” performancesindikator und diagnostizieren meinen eigenen Code.

Nicht sicher, ob es eine Best Practice ist …

Vorschlag: Implementieren Sie dies oder etwas nicht, wenn Sie sich nicht sicher sind. Neu bewerten, wenn Fakten bekannt sind, dann vor / nach performancestests durchführen, um zu verifizieren.

Wenn Sie jedoch Ihren Code zuverlässig testen können, um zu bestätigen, dass der Aufruf von Collect () keine negativen Auswirkungen hat, dann fahren Sie fort …

IMHO, das ist ähnlich zu sagen “Wenn du beweisen kannst, dass dein Programm in der Zukunft keine Fehler haben wird, dann mach weiter …”

In aller Ernsthaftigkeit ist das Erzwingen des GC für Debugging / Testzwecke nützlich. Wenn Sie das Gefühl haben, dass Sie es zu anderen Zeiten tun müssen, dann irren Sie sich entweder, oder Ihr Programm wurde falsch aufgebaut. So oder so, die Lösung zwingt den GC nicht …