webentwicklung-frage-antwort-db.com.de

Ist richtig, GC.Collect () zu verwenden; GC.WaitForPendingFinalizers () ;?

Ich habe angefangen, etwas Code in einem Projekt zu überprüfen und Folgendes gefunden:

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

Diese Linien erscheinen normalerweise bei Methoden, die das Objekt unter dem Gesichtspunkt der Effizienzsteigerung zerstören sollen. Ich habe diese Bemerkungen gemacht:

  1. Das explizite Aufrufen der Garbage Collection bei der Zerstörung jedes Objekts beeinträchtigt die Leistung, da dies nicht berücksichtigt wird, wenn dies für die CLR-Leistung unbedingt erforderlich ist.
  2. Durch das Aufrufen dieser Anweisungen in dieser Reihenfolge wird jedes Objekt nur dann zerstört, wenn andere Objekte finalisiert werden. Daher muss ein Objekt, das unabhängig zerstört werden könnte, ohne wirkliche Notwendigkeit auf die Zerstörung eines anderen Objekts warten.
  3. Es kann ein Deadlock erzeugt werden (siehe: diese Frage )

Sind 1, 2 und 3 wahr? Können Sie einige Hinweise geben, die Ihre Antworten unterstützen? 

Obwohl ich fast sicher bin, was meine Anmerkungen angeht, muss ich meine Argumente klarstellen, um meinem Team erklären zu können, warum dies ein Problem ist. Aus diesem Grund bitte ich um Bestätigung und Referenz.

28
JPCF

Die kurze Antwort lautet: Nehmen Sie es heraus. Dieser Code wird fast niemals die Leistung verbessern oder die Verwendung des Langzeitgedächtnisses.

Alle deine Punkte sind wahr. (Es kann einen Deadlock erzeugen; das heißt nicht, dass es immer wird.) Das Aufrufen von GC.Collect() sammelt den Speicher aller GC-Generationen. Das macht zwei Dinge.

  • Es wird jedes Mal über alle Generationen hinweg gesammelt - anstelle dessen, was der GC standardmäßig tut, dh, eine Generation nur dann zu sammeln, wenn sie voll ist. Bei typischer Verwendung sammelt Gen0 zehnmal so oft wie Gen1, das wiederum zehnmal so oft sammelt wie Gen2. Dieser Code erfasst jedes Mal alle Generationen. Die Gen0-Sammlung liegt in der Regel unter 100 ms. Gen2 kann viel länger sein.
  • Es befördert nicht sammelbare Objekte zur nächsten Generation. Das heißt, jedes Mal, wenn Sie eine Sammlung erzwingen und immer noch einen Verweis auf ein Objekt haben, wird dieses Objekt in die nachfolgende Generation befördert. Normalerweise passiert dies relativ selten, aber Code wie der folgende wird dies weitaus häufiger erzwingen:

    void SomeMethod()
    { 
     object o1 = new Object();
     object o2 = new Object();
    
     o1.ToString();
     GC.Collect(); // this forces o2 into Gen1, because it's still referenced
     o2.ToString();
    }
    

Ohne GC.Collect() werden diese beiden Gegenstände bei der nächsten Gelegenheit gesammelt. Mit der Collection als writte wird o2 in Gen1 landen - was bedeutet, dass eine automatisierte Gen0 Collection nicht gib diese Erinnerung frei.

Erwähnenswert ist auch ein noch größerer Horror: Im DEBUG-Modus funktioniert der GC anders und fordert keine Variable zurück, die sich noch im Gültigkeitsbereich befindet (auch wenn sie später in der aktuellen Methode nicht verwendet wird). Im DEBUG-Modus würde der obige Code nicht einmal o1 sammeln, wenn GC.Collect aufgerufen wird, und daher werden sowohl o1 als auch o2 hochgestuft. Dies kann zu einer sehr unregelmäßigen und unerwarteten Speichernutzung beim Debuggen von Code führen. (Artikel wie this heben dieses Verhalten hervor.)

EDIT: Nachdem Sie dieses Verhalten gerade getestet haben, einige echte Ironie: Wenn Sie eine Methode wie diese haben:

void CleanUp(Thing someObject)
{
    someObject.TidyUp();
    someObject = null;
    GC.Collect();
    GC.WaitForPendingFinalizers(); 
}

... dann wird der Speicher von someObject selbst im RELEASE-Modus explizit NICHT freigegeben, sondern in die nächste GC-Generation hochgestuft.

28
Dan Puzey

Es gibt einen Punkt, den man leicht verstehen kann: Wenn GC ausgeführt wird, werden automatisch viele Objekte pro Ausführung bereinigt (z. B. 10000). Das Aufrufen nach jeder Zerstörung räumt ungefähr ein Objekt pro Lauf auf.

Da GC einen hohen Overhead hat (Threads müssen gestoppt und gestartet werden, alle Objekte müssen lebendig gescannt werden), sollten Stapelverarbeitungsaufrufe unbedingt durchgeführt werden.

Auch was gut könnte beim Aufräumen nach jedem Objekt herauskommen? Wie könnte dies effizienter sein als das Dosieren?

8
usr

Ihr Punkt Nummer 3 ist technisch korrekt, kann aber nur passieren, wenn jemand während eines Finalisers gesperrt ist.

Selbst ohne diese Art von Aufruf ist das Sperren eines Finalisers noch schlimmer als das, was Sie hier haben.

Es gibt eine Handvoll Zeiten, in denen der Aufruf von GC.Collect() wirklich zur Performance beiträgt.

Bisher habe ich das 2, 3 Mal in meiner Karriere gemacht. (Oder vielleicht 5 oder 6 Mal, wenn Sie diejenigen hinzufügen, an denen ich es gemacht habe, die Ergebnisse gemessen und dann wieder herausgenommen habe - und dies ist etwas, das Sie immer danach messen sollten).

Wenn Sie in kurzer Zeit hunderte oder tausende von Megabytes Speicher durchspielen und dann für eine längere Zeit auf eine weniger intensive Nutzung des Speichers umschalten, kann dies eine massive oder sogar entscheidende Verbesserung darstellen explizit sammeln. Geschieht das hier?

Überall sonst werden sie im besten Fall langsamer und benötigen mehr Speicher.

6
Jon Hanna

Sehen Sie meine andere Antwort hier: 

GC.Collect oder nicht?

zwei Dinge können passieren, wenn Sie GC.Collect () selbst anrufen: Am Ende verbringen Sie more _ Zeit, um Sammlungen durchzuführen (da die normalen Hintergrundsammlungen zusätzlich zu Ihrem Handbuch GC.Collect ()) und Ihnen noch auftreten Bleib in der Erinnerung länger (weil du einige Dinge zu einer Generation höherer Ordnung gezwungen hast, die nicht dorthin gehen musste). Mit anderen Worten, die Verwendung von GC.Collect () selbst ist fast immer eine schlechte Idee.

Der einzige Zeitpunkt, zu dem Sie GC.Collect () selbst aufrufen möchten, ist, wenn Sie bestimmte Informationen zu Ihrem Programm haben, die der Garbage Collector nur schwer kennt. Das kanonische Beispiel ist ein lang andauerndes Programm mit unterschiedlichen Last- und Lastzyklen. Möglicherweise möchten Sie vor einem ausgelasteten Zyklus eine Sammlung gegen Ende einer Periode leichter Last erzwingen, um sicherzustellen, dass die Ressourcen für den belebten Zyklus so frei wie möglich sind. Aber auch hier könnten Sie feststellen, dass Sie besser arbeiten, wenn Sie die Art und Weise des Aufbaus Ihrer App besser überdenken (dh würde eine geplante Aufgabe besser funktionieren?).

4
Joel Coehoorn

Ich habe das nur einmal verwendet: um den serverseitigen Cache von Crystal Report-Dokumenten zu bereinigen. Siehe meine Antwort in Crystal Reports-Ausnahme: Das von Ihrem Systemadministrator konfigurierte maximale Limit für die Verarbeitung von Berichtsjobs wurde erreicht

Die WaitForPendingFinalizers waren für mich besonders hilfreich, da die Objekte manchmal nicht ordnungsgemäß bereinigt wurden. In Anbetracht der relativ schlechten Leistung des Berichts auf einer Webseite war jede geringfügige GC-Verzögerung vernachlässigbar und die Verbesserung der Speicherverwaltung machte den Server insgesamt zufriedener.

1
gojimmypi

Wir haben ähnliche Probleme wie @Grzenio, aber wir arbeiten mit viel größeren zweidimensionalen Arrays in der Größenordnung von 1000x1000 bis 3000x3000. Dies ist ein Webservice. 

Das Hinzufügen von mehr Arbeitsspeicher ist nicht immer die richtige Antwort, Sie müssen Ihren Code und den Anwendungsfall verstehen. Ohne GC-Erfassung benötigen wir 16-32 GB Speicher (abhängig von der Kundengröße). Andernfalls würden wir 32-64 GB Speicher benötigen und selbst dann gibt es keine Garantie dafür, dass das System nicht darunter leidet. Der .NET-Garbage-Collector ist nicht perfekt.

Unser Webservice verfügt über einen In-Memory-Cache in der Größenordnung von 5-50 Millionen Zeichenfolgen (je nach Konfiguration ~ 80-140 Zeichen pro Schlüssel/Wert-Paar). Zusätzlich würden wir bei jeder Clientanforderung 2 Matrizen erstellen, eine davon double, eine von boolean, die dann an einen anderen Dienst übergeben wurden, um die Arbeit zu erledigen. Für eine 1000x1000 "Matrix" (2-dimensionales Array) sind dies ~ 25mb, pro Anforderung . Der Boolean würde sagen, welche Elemente wir benötigen (basierend auf unserem Cache). Jeder Cache-Eintrag repräsentiert eine "Zelle" in der "Matrix".

Die Cache-Leistung verschlechtert sich drastisch, wenn der Server aufgrund von Paging zu mehr als 80% Speicherauslastung verfügt. 

Was wir gefunden haben, ist, dass der .net-Garbage-Collector die transitorischen Variablen "bereinigen" würde, wenn wir nicht explizit GC sind, bis wir uns im Bereich von 90-95% befanden, bis zu dem Zeitpunkt, zu dem sich die Cache-Leistung drastisch verschlechtert hatte.

Da der Downstream-Prozess oft lange dauerte (3-900 Sekunden), war der Leistungstreffer einer GC-Sammlung vernachlässigbar (3-10 Sekunden pro Erfassung). Wir haben dieses Sammeln initiiert nachdem wir hatten die Antwort bereits an den Kunden zurückgesandt.

Letztendlich haben wir die GC-Parameter konfigurierbar gemacht, auch mit .net 4.6 gibt es weitere Optionen. Hier ist der .net 4.5-Code, den wir verwendet haben.

if (sinceLastGC.Minutes > Service.g_GCMinutes)
{
     Service.g_LastGCTime = DateTime.Now;
     var sw = Stopwatch.StartNew();
     long memBefore = System.GC.GetTotalMemory(false);
     context.Response.Flush();
     context.ApplicationInstance.CompleteRequest();
     System.GC.Collect( Service.g_GCGeneration, Service.g_GCForced ? System.GCCollectionMode.Forced : System.GCCollectionMode.Optimized);
     System.GC.WaitForPendingFinalizers();
     long memAfter = System.GC.GetTotalMemory(true);
     var elapsed = sw.ElapsedMilliseconds;
     Log.Info(string.Format("GC starts with {0} bytes, ends with {1} bytes, GC time {2} (ms)", memBefore, memAfter, elapsed));
}

Nach dem Umschreiben für die Verwendung mit .net 4.6 teilen wir die Müllsammlung in 2 Schritte auf - ein einfaches Sammeln und ein Verdichten.

    public static RunGC(GCParameters param = null)
    {
        lock (GCLock)
        {
            var theParams = param ?? GCParams;
            var sw = Stopwatch.StartNew();
            var timestamp = DateTime.Now;
            long memBefore = GC.GetTotalMemory(false);
            GC.Collect(theParams.Generation, theParams.Mode, theParams.Blocking, theParams.Compacting);
            GC.WaitForPendingFinalizers();
            //GC.Collect(); // may need to collect dead objects created by the finalizers
            var elapsed = sw.ElapsedMilliseconds;
            long memAfter = GC.GetTotalMemory(true);
            Log.Info($"GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");

        }
    }

    // https://msdn.Microsoft.com/en-us/library/system.runtime.gcsettings.largeobjectheapcompactionmode.aspx
    public static RunCompactingGC()
    {
        lock (CompactingGCLock)
        {
            var sw = Stopwatch.StartNew();
            var timestamp = DateTime.Now;
            long memBefore = GC.GetTotalMemory(false);

            GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
            GC.Collect();
            var elapsed = sw.ElapsedMilliseconds;
            long memAfter = GC.GetTotalMemory(true);
            Log.Info($"Compacting GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");
        }
    }

Ich hoffe, das hilft jemand anderem, da wir viel Zeit damit verbracht haben, dies zu erforschen.

1
Justin

Stimmen Sie den Antworten hier weitgehend zu - es gibt sehr selten Fälle, in denen Sie Ihre eigene Speicherbereinigung durchführen sollten, da die native Python-Implementierung relativ effektiv und vorhersehbar ist. Ich sehe zwei Situationen, in denen es sinnvoll ist, die Dinge selbst in die Hand zu nehmen:

  1. Sie haben eine Echtzeitanwendung, und die Standardspeicherbereinigung wird zu einem ungünstigen Zeitpunkt ausgelöst. Sie sollten auch die Frage stellen, warum Sie in diesem Moment auch echtzeitempfindlichen Code in Python schreiben. und
  2. Sie debuggen Speicherlecks - die Speicherbereinigung unmittelbar vor dem Speichern des Speicherplatzes und die Suche nach interessanten Objekten verringert das Durcheinander; Abgesehen von den grundlegendsten Funktionen ist es wahrscheinlich besser, vorhandene Bibliotheken zu verwenden.
0
F1Rumors