Ich habe gerade eine Frage gelesen , in der es darum geht, den C # -Müllsammler zum Ausführen zu zwingen, bei der fast jede einzelne Antwort gleich ist: Sie können es tun, aber Sie sollten es nicht - mit Ausnahme einiger sehr seltener Fälle . Leider geht dort niemand auf solche Fälle ein.
Können Sie mir sagen, in welchem Szenario es eigentlich eine gute oder vernünftige Idee ist, die Garbage Collection zu erzwingen?
Ich frage nicht nach C # -Fällen, sondern nach allen Programmiersprachen mit Garbage Collector. Ich weiß, dass Sie GC nicht für alle Sprachen wie Java erzwingen können, aber nehmen wir an, Sie können es.
Antworten:
Sie können wirklich keine pauschalen Aussagen über die angemessene Verwendung aller GC-Implementierungen machen. Sie variieren stark. Also spreche ich mit dem .NET, auf das Sie sich ursprünglich bezogen haben.
Sie müssen das Verhalten des GC ziemlich genau kennen, um dies mit einer Logik oder einem Grund zu tun.
Der einzige Rat, den ich zum Sammeln geben kann, ist: Tu es niemals.
Wenn Sie die komplizierten Details des GC wirklich kennen, brauchen Sie meinen Rat nicht, es spielt also keine Rolle. Wenn Sie es noch nicht zu 100% sicher wissen, kann es hilfreich sein, online nachzuschauen und eine Antwort wie die folgende zu finden: Sie sollten GC.Collect nicht anrufen oder alternativ: Sie sollten sich mit den Einzelheiten der Funktionsweise des GC vertraut machen innen und außen, und nur dann werden Sie wissen die Antwort .
Es gibt einen sicheren Ort, an dem es sinnvoll ist, GC.Collect zu verwenden :
GC.Collect ist eine API, die Sie zum Erstellen von Zeitprofilen für bestimmte Dinge verwenden können. Sie können einen Algorithmus profilieren, sammeln und einen anderen Algorithmus unmittelbar danach profilieren, wenn Sie wissen, dass die GC der ersten Algo nicht während der zweiten Algo aufgetreten ist, wodurch die Ergebnisse verzerrt werden.
Diese Art der Profilerstellung ist das einzige Mal, dass ich jedem empfehlen würde, manuell Daten zu sammeln.
Auf jeden Fall ein erfundenes Beispiel
Ein möglicher Anwendungsfall ist, dass wenn Sie sehr große Objekte laden, diese in den Large Object Heap gelangen, der direkt an Gen 2 weitergeleitet wird. Gen 2 ist jedoch auch für langlebige Objekte gedacht, da es weniger häufig erfasst wird. Wenn Sie wissen, dass Sie kurzlebige Objekte aus irgendeinem Grund in Gen 2 laden, können Sie sie schneller entfernen, um Ihr Gen 2 kleiner und seine Sammlungen schneller zu halten.
Dies ist das beste Beispiel, das ich mir einfallen lassen könnte, und es ist nicht gut - der LOH-Druck, den Sie hier aufbauen, würde zu häufigeren Sammlungen führen, und Sammlungen sind so häufig, wie es ist schnell wie Sie es mit temporären Objekten ausblasen. Ich traue mich einfach nicht zu, eine bessere Erfassungshäufigkeit als die GC selbst anzunehmen - abgestimmt von Leuten, die weitaus schlauer sind als ich.
Sprechen wir also über einige der Semantiken und Mechanismen in .NET GC ... oder ..
Alles , was ich zu wissen glaube , über .NET GC
Bitte, wer hier Fehler findet - korrigiert mich. Ein Großteil der GCs ist als schwarze Magie bekannt, und obwohl ich versucht habe, Einzelheiten, über die ich mir nicht sicher war, auszulassen, habe ich wahrscheinlich immer noch einiges falsch gemacht.
Im Folgenden fehlen absichtlich zahlreiche Details, über die ich nicht sicher bin, sowie eine viel größere Menge an Informationen, die mir einfach nicht bekannt sind. Verwenden Sie diese Informationen auf eigenes Risiko.
GC-Konzepte
Der .NET-GC tritt zu inkonsistenten Zeiten auf, weshalb er als "nicht deterministisch" bezeichnet wird. Dies bedeutet, dass Sie sich nicht darauf verlassen können, dass er zu bestimmten Zeiten auftritt. Es ist auch ein Garbage Collector für Generationen, dh, es unterteilt Ihre Objekte in die Anzahl der GC-Durchläufe, die sie durchlaufen haben.
Objekte im Gen 0-Heap haben 0 Sammlungen durchlaufen. Diese wurden neu erstellt. Daher ist seit ihrer Instanziierung in letzter Zeit keine Sammlung mehr aufgetreten. Objekte in Ihrem Gen-1-Haufen haben einen Sammelpass durchlaufen, und Objekte in Ihrem Gen-2-Haufen haben ebenfalls 2 Sammelpass durchlaufen.
Es lohnt sich jetzt zu erwähnen, warum diese spezifischen Generationen und Partitionen entsprechend qualifiziert werden. Der .NET-GC erkennt nur diese drei Generationen, da die Auflistungsübergänge, die über diese drei Heaps gehen, alle geringfügig unterschiedlich sind. Einige Objekte überleben die Sammlung möglicherweise tausende Male. Der GC belässt diese lediglich auf der anderen Seite der Gen 2-Heap-Partition. Es macht keinen Sinn, sie irgendwo weiter zu partitionieren, da sie tatsächlich Gen 44 sind. Die Weitergabe der Sammlung ist die gleiche wie in Gen 2 Heap.
Diese spezifischen Generationen verfolgen semantische Zwecke sowie implementierte Mechanismen, die diese berücksichtigen, und auf diese werde ich gleich eingehen.
Was ist in einer Sammlung
Das Grundkonzept eines GC-Sammlungsdurchlaufs besteht darin, dass jedes Objekt in einem Heap-Bereich überprüft wird, um festzustellen, ob noch Live-Verweise (GC-Wurzeln) auf diese Objekte vorhanden sind. Wenn ein GC-Stammverzeichnis für ein Objekt gefunden wird, bedeutet dies, dass aktuell ausgeführter Code dieses Objekt weiterhin erreichen und verwenden kann, sodass es nicht gelöscht werden kann. Wenn jedoch kein GC-Stammverzeichnis für ein Objekt gefunden wird, benötigt der ausgeführte Prozess das Objekt nicht mehr, sodass es entfernt werden kann, um Speicher für neue Objekte freizugeben.
Jetzt, nachdem ein Haufen Gegenstände aufgeräumt und einige in Ruhe gelassen wurden, gibt es einen unglücklichen Nebeneffekt: Freiraumlücken zwischen lebenden Objekten, in denen die toten entfernt wurden. Wenn diese Speicherfragmentierung in Ruhe gelassen wird, wird einfach Speicher verschwendet. In der Regel werden in Sammlungen die so genannten "Komprimierungsvorgänge" ausgeführt, bei denen alle verbleibenden Live-Objekte im Heap zusammengepresst werden, sodass der freie Speicher auf einer Seite des Heaps für Gen zusammenhängend ist 0.
Nachdem wir nun die Idee von drei Haufen Speicher haben, die alle nach der Anzahl der durchlaufenen Sammlungsdurchläufe unterteilt sind, wollen wir uns über die Gründe für diese Partitionen unterhalten.
Sammlung Gen 0
Da Gen 0 das absolut neueste Objekt ist, ist es in der Regel sehr klein, sodass Sie es sicher und häufig sammeln können . Die Häufigkeit stellt sicher, dass der Heap klein bleibt und die Sammlungen sehr schnell sind, da sie sich über einem so kleinen Heap sammeln. Dies basiert mehr oder weniger auf einer Heuristik, die besagt: Ein Großteil der von Ihnen erstellten temporären Objekte ist sehr temporär, so dass sie fast unmittelbar nach der Verwendung nicht mehr verwendet oder referenziert werden und somit gesammelt werden können.
Sammlung Gen 1
Da es sich bei Gen 1 um Objekte handelt, die nicht in diese sehr vorübergehende Kategorie von Objekten fallen, ist die Lebensdauer möglicherweise noch relativ kurz, da ein großer Teil der erstellten Objekte noch nicht lange verwendet wird. Aus diesem Grund sammelt Gen 1 auch ziemlich häufig und hält seinen Haufen wieder klein, damit seine Sammlungen schnell sind. Es wird jedoch davon ausgegangen, dass weniger Objekte temporär sind als Gen 0, sodass sie seltener als Gen 0 erfasst werden
Ich werde sagen, dass ich die technischen Mechanismen, die sich zwischen dem Sammelpass von Gen 0 und dem von Gen 1 unterscheiden, offen gesagt nicht kenne, wenn es überhaupt andere gibt als die Häufigkeit, mit der sie sammeln.
Gen 2 Sammlung
Gen 2 muss jetzt die Mutter aller Haufen sein, oder? Nun ja, das ist mehr oder weniger richtig. Hier leben alle Ihre permanenten Objekte - zum Beispiel das Objekt, in dem Sie
Main()
leben, und alles, was daraufMain()
verweist, denn diese werden verwurzelt, bis SieMain()
am Ende Ihres Prozesses zurückkehren.Da Gen 2 im Grunde genommen ein Eimer für alles ist, was die anderen Generationen nicht sammeln konnten, sind seine Objekte größtenteils dauerhaft oder zumindest langlebig. Wenn Sie also nur sehr wenig von dem, was in Gen 2 enthalten ist, erkennen, können Sie es tatsächlich sammeln, da es nicht häufig gesammelt werden muss. Dadurch kann die Erfassung auch langsamer erfolgen, da sie viel seltener ausgeführt wird. Im Grunde haben sie hier alle zusätzlichen Verhaltensweisen für ungerade Szenarien in Angriff genommen, weil sie die Zeit haben, sie auszuführen.
Großer Objekthaufen
Ein Beispiel für das zusätzliche Verhalten von Gen 2 ist, dass es auch die Auflistung auf dem Large Object Heap ausführt. Bisher habe ich mich ausschließlich mit dem Small Object Heap befasst, aber die .NET-Laufzeit ordnet aufgrund der oben genannten Komprimierung Dinge mit bestimmten Größen einem separaten Heap zu. Für die Komprimierung müssen Objekte verschoben werden, wenn die Auflistung auf dem Small Object Heap abgeschlossen ist. Wenn es in Gen 1 ein lebendes 10-MB-Objekt gibt, wird es viel länger dauern, bis die Komprimierung nach der Sammlung abgeschlossen ist, wodurch die Sammlung von Gen 1 verlangsamt wird. Damit wird dieses 10-MB-Objekt dem Large Object Heap zugewiesen und während der Gen 2 gesammelt, die so selten ausgeführt wird.
Finalisierung
Ein weiteres Beispiel sind Objekte mit Finalisierern. Sie platzieren einen Finalizer für ein Objekt, das auf Ressourcen außerhalb des Geltungsbereichs von .NETs GC verweist (nicht verwaltete Ressourcen). Der Finalizer ist die einzige Möglichkeit für den GC, die Erfassung einer nicht verwalteten Ressource anzufordern. Sie implementieren Ihren Finalizer, um die manuelle Erfassung / Entfernung / Freigabe der nicht verwalteten Ressource durchzuführen und sicherzustellen, dass sie nicht aus Ihrem Prozess ausläuft. Wenn der GC den Finalizer für Ihre Objekte ausführt, löscht Ihre Implementierung die nicht verwaltete Ressource, sodass der GC in der Lage ist, Ihr Objekt zu entfernen, ohne ein Ressourcenleck zu riskieren.
Der Mechanismus, mit dem Finalizer dies tun, besteht darin, direkt in einer Finalisierungswarteschlange referenziert zu werden. Wenn die Laufzeit ein Objekt mit einem Finalizer zuweist, fügt sie der Finalisierungswarteschlange einen Zeiger auf dieses Objekt hinzu und sperrt das Objekt (als Fixieren bezeichnet), sodass es durch die Komprimierung nicht verschoben wird, wodurch die Finalisierungswarteschlangenreferenz unterbrochen wird. Wenn Sammlungsdurchläufe stattfinden, hat Ihr Objekt möglicherweise kein GC-Stammverzeichnis mehr, aber die Finalisierung muss ausgeführt werden, bevor es gesammelt werden kann. Wenn das Objekt also tot ist, verschiebt die Sammlung seine Referenz aus der Finalisierungswarteschlange und platziert eine Referenz darauf in der sogenannten "FReachable" -Warteschlange. Dann geht die Sammlung weiter. Zu einem anderen "nicht deterministischen" Zeitpunkt in der Zukunft, Ein separater Thread, der als Finalizer-Thread bezeichnet wird, durchläuft die FReachable-Warteschlange und führt die Finalizer für jedes der referenzierten Objekte aus. Nachdem es fertig ist, ist die FReachable-Warteschlange leer, und es wurde ein bisschen in die Kopfzeile jedes Objekts gekippt, das angibt, dass keine Finalisierung erforderlich ist. (Dieses Bit kann auch manuell mit gekippt werden.)
GC.SuppressFinalize
die gemeinsam istDispose()
Methoden), habe ich auch den Verdacht hat es die Objekte steckte los, aber zitieren Sie mich nicht daran. Die nächste Sammlung, die sich auf dem Haufen befindet, in dem sich dieses Objekt befindet, wird es endlich sammeln. Die Sammlungen von Gen 0 berücksichtigen Objekte mit dem für die Finalisierung erforderlichen Bit nicht einmal, sondern befördern sie automatisch, ohne auch nur nach ihrer Wurzel zu suchen. Ein unbewurzeltes Objekt, das in Gen 1FReachable
finalisiert werden muss , wird in die Warteschlange geworfen , aber die Sammlung macht nichts anderes damit, sodass es in Gen 2 lebt. Auf diese Weise werden alle Objekte, die einen Finalizer haben, und nichtGC.SuppressFinalize
wird in Gen 2 gesammelt.quelle
Ich werde einige Beispiele geben. Alles in allem ist es selten eine gute Idee, einen GC zu erzwingen, aber es kann sich absolut lohnen. Diese Antwort stammt aus meiner Erfahrung mit .NET- und GC-Literatur. Es sollte sich gut auf andere Plattformen übertragen lassen (zumindest auf solche mit einem signifikanten GC).
Wenn Ihr Ziel der Durchsatz ist, ist der GC umso besser, je seltener er ist. In diesen Fällen kann das Erzwingen einer Auflistung keine positiven Auswirkungen haben (mit Ausnahme von Problemen wie der Erhöhung der CPU-Cache-Auslastung durch Entfernen von toten Objekten, die sich in den aktiven Objekten befinden). Die Batch-Sammlung ist für alle mir bekannten Sammler effizienter. Für Produktions-App im Steady-State-Memory-Verbrauch hilft das Induzieren eines GC nicht.
Die oben angegebenen Beispiele zielen auf Konsistenz und Beschränkung der Speichernutzung ab. In diesen Fällen können induzierte GCs sinnvoll sein.
Es scheint eine weit verbreitete Idee zu sein, dass der GC eine göttliche Einheit ist, die eine Sammlung auslöst, wann immer dies in der Tat optimal ist. Kein GC, von dem ich weiß, ist so ausgefeilt und es ist in der Tat sehr schwer, für den GC optimal zu sein. Der GC weiß weniger als der Entwickler. Die Heuristik basiert auf Speicherzählern und Dingen wie Erfassungsrate und so weiter. Die Heuristiken sind in der Regel gut, erfassen jedoch keine plötzlichen Änderungen im Anwendungsverhalten, z. B. die Freigabe großer Mengen an verwaltetem Speicher. Es ist auch blind für nicht verwaltete Ressourcen und für Latenzanforderungen.
Beachten Sie, dass die GC-Kosten von der Größe des Heapspeichers und der Anzahl der Referenzen auf dem Heapspeicher abhängen. Auf einem kleinen Haufen können die Kosten sehr gering sein. Ich habe G2-Erfassungsraten mit .NET 4.5 von 1-2 GB / s auf einer Produktions-App mit 1 GB Heap-Größe gesehen.
quelle
Im Allgemeinen wird ein Garbage Collector bei "Speicherdruck" gesammelt, und es wird als eine gute Idee angesehen, ihn nicht zu anderen Zeiten sammeln zu lassen, da dies zu Leistungsproblemen oder sogar zu merklichen Unterbrechungen bei der Ausführung Ihres Programms führen kann. Tatsächlich ist der erste Punkt vom zweiten abhängig: Für einen Müllsammler der Generation läuft er umso effizienter, je höher das Verhältnis von Müll zu guten Objekten ist, um so weniger Zeit wird für die Unterbrechung des Programms aufgewendet , es muss zögern und den Müll so viel wie möglich stapeln lassen.
Die geeignete Zeit zum manuellen Aufrufen des Garbage Collectors ist dann, wenn Sie etwas erledigt haben, das 1) wahrscheinlich viel Müll verursacht hat und 2) der Benutzer einige Zeit in Anspruch nimmt und das System nicht mehr reagiert sowieso. Ein klassisches Beispiel ist das Ende des Ladens eines großen Objekts (eines Dokuments, eines Modells, einer neuen Ebene usw.).
quelle
Eine Sache, die niemand erwähnt hat, ist, dass der Windows-GC zwar erstaunlich gut ist, der GC auf der Xbox jedoch Müll ist (Wortspiel beabsichtigt) .
Wenn Sie also ein XNA-Spiel programmieren, das auf XBox ausgeführt werden soll, ist es unbedingt erforderlich, die Speicherbereinigung auf die richtigen Momente abzustimmen, da sonst schreckliche, zeitweise auftretende FPS-Probleme auftreten können. Darüber hinaus ist es bei XBox üblich,
struct
s viel häufiger als sonst zu verwenden, um die Anzahl der Objekte zu minimieren, die mit Müll gesammelt werden müssen.quelle
Die Garbage Collection ist in erster Linie ein Tool zur Speicherverwaltung. Daher sammeln sich Müllsammler, wenn Speicherdruck besteht.
Moderne Abfallsammler sind sehr gut und werden immer besser. Daher ist es unwahrscheinlich, dass Sie sie durch manuelles Sammeln verbessern können. Selbst wenn Sie die Dinge heute noch verbessern können, kann es durchaus sein, dass eine zukünftige Verbesserung des ausgewählten Garbage Collectors Ihre Optimierung ineffektiv oder sogar kontraproduktiv macht.
Jedoch , Müllsammler versuchen normalerweise keine Verwendung von Ressourcen zu optimieren andere als Speicher. In Umgebungen mit Speicherbereinigung verfügen die meisten wertvollen Nicht-Speicherressourcen über eine
close
Methode oder eine ähnliche Methode. In einigen Fällen ist dies jedoch aus irgendeinem Grund nicht der Fall, z. B. bei der Kompatibilität mit einer vorhandenen API.In diesen Fällen kann es sinnvoll sein, die Garbage Collection manuell aufzurufen, wenn Sie wissen, dass eine wertvolle Nicht-Speicherressource verwendet wird.
RMI
Ein konkretes Beispiel hierfür ist Javas Remote Method Invocation. RMI ist eine Remote Procedure Call Library. In der Regel verfügen Sie über einen Server, der verschiedene Objekte für die Verwendung durch Clients zur Verfügung stellt. Wenn ein Server weiß, dass ein Objekt nicht von Clients verwendet wird, ist dieses Objekt für die Garbage Collection berechtigt.
Der Server weiß dies jedoch nur, wenn der Client es ihm mitteilt, und der Client teilt dem Server nur mit, dass er kein Objekt mehr benötigt, sobald der Client den Müll eingesammelt hat, der es verwendet.
Dies stellt ein Problem dar, da der Client möglicherweise über viel freien Speicher verfügt und daher die Garbage Collection möglicherweise nicht häufig ausgeführt wird. In der Zwischenzeit hat der Server möglicherweise viele nicht verwendete Objekte im Speicher, die er nicht erfassen kann, da er nicht weiß, dass der Client sie nicht verwendet.
Die Lösung in RMI besteht darin, dass der Client die Garbage Collection regelmäßig ausführt, auch wenn viel Speicher frei ist, um sicherzustellen, dass Objekte sofort auf dem Server erfasst werden.
quelle
using
Block verwenden oder eineClose
Methode anderweitig aufrufen Stellen Sie sicher, dass die Ressource so schnell wie möglich gelöscht wird. Sich beim Bereinigen von Nicht-Speicherressourcen auf GC zu verlassen, ist unzuverlässig und verursacht alle Arten von Problemen (insbesondere bei Dateien, die für den Zugriff gesperrt werden müssen und daher nur einmal geöffnet werden können).close
istusing
dies der richtige Ansatz , wenn eine Methode verfügbar ist (oder die Ressource mit einem Block verwendet werden kann). Die Antwort befasst sich speziell mit den seltenen Fällen, in denen diese Mechanismen nicht verfügbar sind.In den meisten Fällen wird empfohlen, keine Garbage Collection zu erzwingen. (Jedes System, an dem ich gearbeitet habe, hatte erzwungene Speicherbereinigungen, unterstrich Probleme, die, wenn sie gelöst worden wären, die Notwendigkeit beseitigt hätten, die Speicherbereinigung zu erzwingen, und beschleunigte das System erheblich.)
Es gibt ein paar Fälle , wenn Sie mehr über die Speichernutzung wissen dann der Garbage Collector tut. Es ist unwahrscheinlich, dass dies in einer Mehrbenutzeranwendung oder einem Dienst, der auf mehrere Anforderungen gleichzeitig reagiert, der Fall ist.
Bei einigen Batch-Verarbeitungen kennen Sie jedoch mehr als den GC. ZB betrachten Sie eine Anwendung, die.
Möglicherweise können Sie (nach sorgfältigen) Tests feststellen, dass Sie eine vollständige Garbage Collection erzwingen sollten, nachdem Sie die einzelnen Dateien verarbeitet haben.
Ein weiterer Fall ist ein Dienst, der alle paar Minuten zur Verarbeitung einiger Elemente aufwacht und im Schlaf keinen Status beibehält . Dann kann es sich lohnen, kurz vor dem Schlafengehen eine vollständige Sammlung zu erzwingen .
Ich hätte lieber eine Garbage Collection-API, wenn ich Hinweise auf diese Art von Dingen geben könnte, ohne dass ich einen GC erzwingen müsste.
Siehe auch " Rico Marianis Performance-Leckerbissen "
quelle
Es gibt mehrere Fälle, in denen Sie gc () selbst aufrufen möchten.
gc()
Aufruf nur noch sehr wenige Objekte in den Raum älterer Generationen verschoben werden. ] Wenn Sie eine große Sammlung von Objekten erstellen und viel Speicher verwenden möchten. Sie möchten einfach so viel Platz wie möglich für die Vorbereitung freigeben. Das ist nur gesunder Menschenverstand. Wenn Siegc()
manuell aufrufen , wird ein Teil dieser großen Sammlung von Objekten, die Sie in den Speicher laden, nicht auf redundante Referenzgraphen überprüft. Kurz gesagt, wenn Sie ausführen,gc()
bevor Sie viel in den Speicher laden, wird diegc()
Der während des Ladevorgangs verursachte Druck verringert sich um mindestens ein Mal, wenn das Laden beginnt, Speicherdruck zu erzeugen.großObjekte und Sie werden wahrscheinlich nicht mehr Objekte in den Speicher laden. Kurz gesagt, Sie wechseln von der Erstellungsphase zur Verwendungsphase. Durch den Aufrufgc()
je nach Ausführung, wird der Speicher in verwendet verdichtet die Cache - Ort massiv verbessert. Dies führt zu einer massiven Leistungsverbesserung, die Sie durch die Profilerstellung nicht erzielen .gc()
und die Speicherverwaltungsimplementierung dies unterstützt, eine viel bessere Kontinuität für Ihren physischen Speicher schaffen. Dies macht die neue große Sammlung von Objekten wieder durchgängiger und kompakter, was wiederum die Leistung verbessertquelle
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.
Wenn Sie eine Tonne von Objekten in einer Reihe zuordnen, sind die Quoten bereits komprimiert. Wenn überhaupt, kann die Müllabfuhr sie leicht durcheinander bringen. In beiden Fällen hat die Verwendung von Datenstrukturen, die dicht sind und nicht zufällig im Speicher herumspringen, eine größere Auswirkung. Wenn Sie eine naive verknüpfte Liste mit einem Element pro Knoten verwenden, wird dies nicht durch manuelle GC-Tricks wettgemacht.Ein reales Beispiel:
Ich hatte eine Webanwendung, die von einer sehr großen Datenmenge Gebrauch machte, die sich selten änderte und auf die sehr schnell zugegriffen werden musste (schnell genug für eine Antwort per Tastendruck über AJAX).
Hier ist es naheliegend, den entsprechenden Graphen in den Speicher zu laden und von dort aus auf ihn zuzugreifen, anstatt auf die Datenbank zuzugreifen, und den Graphen zu aktualisieren, wenn sich die Datenbank ändert.
Da die Datenmenge jedoch sehr groß ist, hätte eine nicht vorhandene Last mindestens 6 GB Speicherplatz in Anspruch genommen, da sie in Zukunft zunehmen wird. (Ich habe keine genauen Zahlen, als klar war, dass mein 2-GB-Rechner mit mindestens 6 GB zu kämpfen hatte, hatte ich alle Messungen, die ich brauchte, um zu wissen, dass es nicht funktionieren würde.)
Glücklicherweise gab es in diesem Datensatz eine große Anzahl von Eis am Stiel-unveränderlichen Objekten, die einander gleich waren. Sobald ich herausgefunden hatte, dass eine bestimmte Charge mit einer anderen Charge identisch ist, konnte ich einen Verweis auf die andere aliasen, sodass viele Daten erfasst und somit alles in weniger als einen halben Gig gepasst werden konnte.
Alles schön und gut, aber dafür immer noch über 6 GB Objekte innerhalb einer halben Minute durchwühlen, um in diesen Zustand zu gelangen. GC war für sich allein gelassen und kam nicht zurecht. Die Aktivitätsspitze gegenüber dem üblichen Muster der Anwendung (viel weniger stark bei Aufhebungen pro Sekunde) war zu scharf.
Das periodische Aufrufen
GC.Collect()
während dieses Erstellungsprozesses bedeutete, dass das Ganze reibungslos funktionierte. Natürlich habe ichGC.Collect()
den Rest der Zeit, in der die Anwendung ausgeführt wird , nicht manuell aufgerufen .Dieser reale Fall ist ein gutes Beispiel für die Richtlinien, wann wir verwenden sollten
GC.Collect()
:Die meiste Zeit, wenn ich dachte, ich könnte einen Fall haben, in dem
GC.Collect()
es sich lohnt, anzurufen, weil Punkt 1 und 2 zutrafen, schlug Punkt 3 vor, dass die Dinge schlechter oder zumindest nicht besser wurden (und mit wenig oder keiner Verbesserung würde ich es tun Neigen Sie dazu, nicht über einen Anruf zu telefonieren, da sich dieser Ansatz über die Lebensdauer einer Anwendung mit größerer Wahrscheinlichkeit als besser erweist.quelle
Ich habe eine Verwendung für die Müllentsorgung, die etwas unorthodox ist.
Es gibt diese fehlgeleitete Praxis, die leider in der C # -Welt sehr verbreitet ist, die Objektentsorgung unter Verwendung der hässlichen, klobigen, uneleganten und fehleranfälligen IDisposable-Disposing-Sprache zu implementieren . MSDN beschreibt es ausführlich , und viele Leute schwören darauf, befolgen es religiös, diskutieren stundenlang , wie es genau gemacht werden soll usw.
(Bitte beachten Sie, dass das, was ich hier als hässlich bezeichne, nicht das Objektentsorgungsmuster selbst ist. Was ich als hässlich bezeichne, ist die bestimmte
IDisposable.Dispose( bool disposing )
Redewendung.)Diese Redewendung wurde erfunden, weil es angeblich unmöglich ist, zu garantieren, dass der Garbage Collector den Destruktor Ihrer Objekte immer aufruft, um Ressourcen zu bereinigen. Daher führen die Benutzer die Ressourcenbereinigung in diesem Bereich durch
IDisposable.Dispose()
, und falls sie dies vergessen, versuchen sie es auch noch einmal innerhalb des Destruktors. Weißt du, nur für den Fall.Dann müssen
IDisposable.Dispose()
möglicherweise sowohl verwaltete als auch nicht verwaltete Objekte bereinigt werden. Die verwalteten Objekte können jedoch nicht bereinigt werden, wenn sieIDisposable.Dispose()
im Destruktor aufgerufen werden, da sie zu diesem Zeitpunkt bereits vom Garbage Collector bearbeitet wurden Ist dies eine Notwendigkeit für eine separateDispose()
Methode, die einbool disposing
Flag akzeptiert, um zu wissen, ob sowohl verwaltete als auch nicht verwaltete Objekte bereinigt werden sollen, oder nur nicht verwaltete.Entschuldigung, aber das ist einfach verrückt.
Ich gehe nach Einsteins Axiom vor, das besagt, dass die Dinge so einfach wie möglich sein sollten, aber nicht einfacher. Natürlich können wir die Bereinigung von Ressourcen nicht auslassen, daher muss die einfachste mögliche Lösung zumindest diese beinhalten. Die nächst einfachere Lösung besteht darin, immer alles zu dem Zeitpunkt zu entsorgen, an dem es entsorgt werden soll, ohne die Dinge zu komplizieren, indem der Destruktor als alternative Alternative herangezogen wird.
Streng genommen ist es natürlich unmöglich zu garantieren, dass kein Programmierer jemals den Fehler begeht, das Aufrufen zu vergessen
IDisposable.Dispose()
, aber wir können den Destruktor verwenden, um diesen Fehler aufzufangen. Eigentlich ist es sehr einfach: Der Destruktor muss nur einen Protokolleintrag generieren, wenn er feststellt, dass dasdisposed
Flag des Einwegobjekts nie gesetzt wurdetrue
. Die Verwendung des Destruktors ist daher kein wesentlicher Bestandteil unserer Entsorgungsstrategie, sondern unser Mechanismus zur Qualitätssicherung. Und da dies nur ein Test im Debug-Modus ist, können wir unseren gesamten Destruktor in einem#if DEBUG
Block platzieren, sodass wir in einer Produktionsumgebung niemals eine Zerstörungsstrafe erleiden. (DieIDisposable.Dispose( bool disposing )
Redewendung schreibt das vorGC.SuppressFinalize()
sollte genau aufgerufen werden, um den Overhead der Finalisierung zu verringern, aber mit meinem Mechanismus ist es möglich, diesen Overhead für die Produktionsumgebung vollständig zu vermeiden.)Worauf es hinausläuft, ist das ewige Argument zwischen hartem und weichem Fehler : Das
IDisposable.Dispose( bool disposing )
Idiom ist ein Ansatz mit weichem Fehler und stellt einen Versuch dar, dem Programmierer zu ermöglichen, den Aufruf nach Möglichkeit zu vergessen,Dispose()
ohne dass das System ausfällt. Der Hard-Error-Ansatz besagt, dass der Programmierer immer sicherstellen muss, dass dieserDispose()
aufgerufen wird. Die Strafe, die in den meisten Fällen vom Hard Error-Ansatz vorgegeben wird, ist ein Assertionsfehler. In diesem speziellen Fall machen wir jedoch eine Ausnahme und verringern die Strafe für die einfache Ausgabe eines Fehlerprotokolleintrags.Damit dieser Mechanismus funktioniert, muss die DEBUG-Version unserer Anwendung vor dem Beenden eine vollständige Müllentsorgung durchführen, um sicherzustellen, dass alle Destruktoren aufgerufen werden und somit alle
IDisposable
Objekte aufgefangen werden, die wir vergessen haben, zu entsorgen.quelle
Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()
Eigentlich nicht, obwohl ich nicht denke, dass C # dazu in der Lage ist. Stellen Sie die Ressource nicht zur Verfügung. Geben Sie stattdessen eine DSL an, um alles zu beschreiben, was Sie damit tun (im Grunde genommen eine Monade), sowie eine Funktion, die die Ressource erfasst, die Dinge erledigt, sie freigibt und das Ergebnis zurückgibt. Der Trick besteht darin, das Typsystem zu verwenden, um sicherzustellen, dass ein Verweis auf die Ressource, den jemand herausschmuggelt, nicht in einem anderen Aufruf der Ausführungsfunktion verwendet werden kann.Dispose(bool disposing)
(das nicht definiertIDisposable
ist, besteht darin, dass es zum Bereinigen von verwalteten und nicht verwalteten Objekten verwendet wird, die das Objekt als Feld hat (oder anderweitig dafür verantwortlich ist), wodurch das falsche Problem gelöst wird. Wenn Sie alles umbrechen Nicht verwaltete Objekte in einem verwalteten Objekt, für die keine anderen Einwegobjekte zu befürchtenDispose()
sind, sind entweder alle Methoden eine von diesen (der Finalisierer führt bei Bedarf die gleiche Bereinigung durch) oder es müssen nur verwaltete Objekte entsorgt werden (er hat keinen Finalisierer) überhaupt), und die Notwendigkeit fürbool disposing
verschwindetdispose(disposing)
Redewendung Terribad ist, aber ich sage dies, weil die Leute diese Technik und Finalizer so oft anwenden, wenn sie nur verwaltete Ressourcen haben (DbConnection
zum Beispiel wird das Objekt verwaltet , es wird nicht beschlagnahmt oder gemarshallt), und SIE SOLLTEN NUR IMPLEMENTIEREN SIE EINEN FINALISIERER MIT UNVERWALTETEM, PINVOKED, COM MARSHALLED ODER UNSICHEREM CODE . Ich detailliert oben in meiner Antwort , wie erschreckend teuer Finalizers sind, nicht nutzen sie , es sei denn , Sie haben nicht verwaltete in Ihrer Klasse Ressourcen.dispose(dispoing)
Umgangssprache betrachten, aber die Wahrheit ist, dass dies nur deshalb so weit verbreitet ist, weil die Leute so viel Angst vor GC-Dingen haben, dass etwas so Unabhängiges wie Das (dispose
sollte eigentlich mit GC zu tun haben) verdient es, dass sie nur das verschriebene Medikament einnehmen, ohne es überhaupt zu untersuchen. Gut, dass Sie es überprüft haben, aber Sie haben das größte Ganze verpasst (es ermutigt die Finalisten, häufiger als sie sein sollten)Das größte Szenario, das ich mir vorstellen kann, um die Garbage Collection zu erzwingen, ist eine unternehmenskritische Software, bei der logische Lecks gegenüber herabhängenden Zeigerabstürzen vorzuziehen sind, z. B. weil sie abstürzen in unerwarteten Zeiten könnte Menschenleben oder etwas in dieser Art kosten.
Wenn Sie sich einige der schäbigeren Indie-Spiele ansehen, die mit GC-Sprachen wie Flash-Spielen geschrieben wurden, dann lecken sie wie verrückt, aber sie stürzen nicht ab. Sie benötigen möglicherweise das Zehnfache des Speichers 20 Minuten, um das Spiel zu spielen, da ein Teil der Codebasis des Spiels vergessen hat, einen Verweis auf Null zu setzen oder ihn aus einer Liste zu entfernen, und die Bildraten möglicherweise zu leiden beginnen, aber das Spiel funktioniert immer noch. Ein ähnliches Spiel, das mit Shoddy-C- oder C ++ - Codierung geschrieben wurde, stürzt möglicherweise ab, wenn auf baumelnde Zeiger zugegriffen wird, die auf denselben Fehler bei der Ressourcenverwaltung zurückzuführen sind, aber es tritt nicht so häufig ein Fehler auf.
Für Spiele mag der Absturz in dem Sinne vorzuziehen sein, dass er schnell erkannt und behoben werden kann. Für ein unternehmenskritisches Programm kann ein Absturz zu völlig unerwarteten Zeiten jemanden töten. Ich denke, die Hauptfälle sind Szenarien, in denen kein Absturz oder andere Sicherheitsaspekte absolut kritisch sind, und ein logisches Leck ist im Vergleich eine relativ triviale Sache.
Das Hauptszenario, in dem es meiner Meinung nach schlecht ist, GC zu erzwingen, ist für Dinge gedacht, bei denen das logische Leck eigentlich weniger vorzuziehen ist als ein Absturz. Bei Spielen zum Beispiel wird der Absturz nicht unbedingt jemanden töten, und es kann sein, dass er während interner Tests leicht abgefangen und behoben werden kann, während ein logisches Leck selbst nach Auslieferung des Produkts unbemerkt bleibt, es sei denn, es ist so schwerwiegend, dass das Spiel innerhalb von Minuten nicht mehr spielbar ist . In einigen Bereichen ist ein leicht reproduzierbarer Absturz, der beim Testen auftritt, manchmal einem Leck vorzuziehen, das niemand sofort bemerkt.
Ein anderer Fall, bei dem ich mir vorstellen kann, dass es vorzuziehen ist, einem Team die GC aufzuzwingen, ist ein sehr kurzlebiges Programm, wie z. B. etwas, das über die Befehlszeile ausgeführt wird und eine Aufgabe ausführt und dann herunterfährt. In diesem Fall ist die Lebensdauer des Programms zu kurz, um ein logisches Leck nicht mehr zu belasten. Logische Lecks werden selbst bei großen Ressourcen in der Regel erst Stunden oder Minuten nach dem Ausführen der Software problematisch. Daher ist es unwahrscheinlich, dass eine Software, die nur für 3 Sekunden ausgeführt werden soll, jemals Probleme mit logischen Lecks aufweist, und dies kann zu erheblichen Problemen führen Es ist einfacher, solche kurzlebigen Programme zu schreiben, wenn das Team nur GC verwendet.
quelle