Wie verhindert ein Garbage Collector, dass der gesamte Speicher bei jeder Erfassung gescannt wird?


16

Einige (zumindest Monos und .NETs) Garbage Collectors haben einen Kurzzeitspeicherbereich, den sie häufig durchsuchen, und einen sekundären Speicherbereich, den sie seltener durchsuchen. Mono nennt das einen Kindergarten.

Um herauszufinden, welche Objekte entsorgt werden können, scannen sie alle Objekte, beginnend mit Wurzeln, dem Stapel und den Registern, und entsorgen alle Objekte, auf die nicht mehr verwiesen wird.

Meine Frage ist, wie sie verhindern, dass alle verwendeten Speicher bei jeder Erfassung gescannt werden? Grundsätzlich können Sie nur dann herausfinden, welche Objekte nicht mehr verwendet werden, wenn Sie alle Objekte und ihre Referenzen scannen. Dies würde jedoch verhindern, dass das Betriebssystem den Speicher auslagert, obwohl es nicht von der Anwendung verwendet wird, und es scheint, dass eine große Menge an Arbeit erledigt werden muss, auch für "Nursery Collection". Es fühlt sich nicht so an, als würden sie durch die Nutzung eines Kindergartens viel gewinnen.

Vermisse ich etwas oder scannt der Müllsammler tatsächlich jedes Objekt und jede Referenz, wenn er eine Sammlung durchführt?


1
Einen schönen Überblick gibt ein Artikel von Angelika Langer, The Art of Garbage Collection Tuning . Formal geht es um die Art und Weise , wie es in Java getan, aber vorgestellten Konzepte sind so ziemlich sprachunabhängig
gnat

Antworten:


14

Die grundlegenden Beobachtungen, mit denen die Sammlung von Speicherbereinigungen für Generationen vermieden werden kann, dass alle Objekte älterer Generationen gescannt werden müssen, sind:

  1. Nach einer Sammlung werden alle Objekte, die noch existieren, von einer minimalen Generation sein (z. B. in .net, nach einer Sammlung von Gen0 sind alle Objekte Gen1 oder Gen2; nach einer Sammlung von Gen1 oder Gen2 sind alle Objekte Gen2).
  2. Ein Objekt oder ein Teil davon, das seit einer Sammlung, die alles auf Generation N oder höher hochgestuft hat, nicht mehr geschrieben wurde, darf keine Verweise auf Objekte niedrigerer Generationen enthalten.
  3. Wenn ein Objekt eine bestimmte Generation erreicht hat, muss es nicht als erreichbar identifiziert werden, um seine Beibehaltung beim Sammeln niedrigerer Generationen sicherzustellen.

In vielen GC-Frameworks ist es dem Garbage Collector möglich, Objekte oder Teile davon so zu kennzeichnen, dass der erste Versuch, darauf zu schreiben, einen speziellen Code auslöst, der die Tatsache aufzeichnet, dass sie geändert wurden. Ein Objekt oder ein Teil davon, das unabhängig von seiner Erzeugung geändert wurde, muss in der nächsten Sammlung gescannt werden, da es Verweise auf neuere Objekte enthalten kann. Andererseits ist es sehr häufig, dass es viele ältere Objekte gibt, die zwischen den Sammlungen nicht geändert werden. Die Tatsache, dass Scans niedrigerer Generationen solche Objekte ignorieren können, kann dazu führen, dass solche Scans viel schneller ausgeführt werden, als dies sonst der Fall wäre.

Man beachte übrigens, dass selbst wenn man nicht erkennen kann, wann Objekte geändert wurden und bei jedem GC-Durchgang alles gescannt werden müsste, die generationsbedingte Speicherbereinigung die Leistung eines Kompaktierungskollektors im Sweep-Stadium verbessern könnte. In einigen eingebetteten Umgebungen (insbesondere in solchen, in denen die Geschwindigkeit zwischen sequentiellen und zufälligen Speicherzugriffen kaum oder gar nicht unterschiedlich ist) ist das Verschieben von Speicherblöcken im Vergleich zum Markieren von Referenzen relativ teuer. Selbst wenn die "Mark" -Phase mit einem Generationskollektor nicht beschleunigt werden kann, kann es sich daher lohnen, die "Sweep" -Phase zu beschleunigen.


Das Verschieben von Speicherblöcken ist in jedem System teuer. Daher ist die Verbesserung des Sweeps auch für Ihr Quad-Ghz-CPU-System ein Gewinn.
Gbjbaanb

@gbjbaanb: In vielen Fällen wären die Kosten für das Durchsuchen aller Objekte, um lebende Objekte zu finden, erheblich und zu beanstanden, selbst wenn das Verschieben der Objekte völlig kostenlos wäre. Folglich sollte man, wenn es praktisch ist, das Scannen alter Objekte vermeiden. Auf der anderen Seite ist das Verzichten auf die Komprimierung älterer Objekte eine einfache Optimierung, die auch auf einfachen Frameworks durchgeführt werden kann. Übrigens, wenn man ein GC-Framework für ein kleines eingebettetes System entwirft, kann die deklarative Unterstützung für unveränderliche Objekte hilfreich sein. Verfolgen, ob sich ein veränderbares Objekt geändert hat, ist schwierig, aber man könnte es gut machen ...
Supercat

... nehmen Sie einfach an, dass veränderbare Objekte bei jedem GC-Durchgang gescannt werden müssen, unveränderliche Objekte jedoch nicht. Selbst wenn die einzige Möglichkeit, ein unveränderliches Objekt zu erstellen, darin bestand, einen "Prototyp" im veränderlichen Raum zu erstellen und ihn dann zu kopieren, könnte der einzelne zusätzliche Kopiervorgang die Notwendigkeit vermeiden, das Objekt in zukünftigen GC-Vorgängen zu scannen.
Supercat

Im Übrigen könnte die Speicherbereinigungsleistung von Microsoft-abgeleiteten Implementierungen von BASIC für 6502-Mikroprozessoren aus den 1980er Jahren (und möglicherweise auch von anderen) in einigen Fällen erheblich verbessert werden, wenn ein Programm, das viele Zeichenfolgen generiert, die sich niemals ändern würden, die "nächste" kopiert String Allocation "Zeiger auf den" Top of String Space "Zeiger. Eine solche Änderung würde den Müllsammler daran hindern, die alten Zeichenfolgen zu untersuchen, um festzustellen, ob sie noch benötigt werden. Der Commodore 64 war kaum High-Tech, aber ein solcher "Generations-GC" würde auch dort helfen.
Supercat

7

Die GCs, auf die Sie sich beziehen, sind generationsübergreifende Garbage Collectors. Sie wurden entwickelt, um das Beste aus einer als "Kindersterblichkeit" oder "Generationshypothese" bekannten Beobachtung herauszuholen, was bedeutet, dass die meisten Objekte sehr schnell unerreichbar werden. Sie scannen zwar von den Wurzeln aus, ignorieren aber alle alten Objekte . Daher müssen sie nicht die meisten Objekte im Speicher scannen, sondern nur junge Objekte (auf Kosten der Nichterkennung nicht erreichbarer alter Objekte, zumindest nicht zu diesem Zeitpunkt).

"Aber das ist falsch", höre ich Sie schreien, "alte Objekte können und verweisen auf junge Objekte". Sie haben Recht, und es gibt verschiedene Lösungen, bei denen es darum geht, schnell und effizient Wissen zu erlangen, alte Objekte zu überprüfen und zu ignorieren. Sie beschränken sich auf die Aufzeichnung von Objekten oder auf kleine (größer als Objekte, aber viel kleiner als der gesamte Haufen) Speicherbereiche, die Zeiger auf jüngere Generationen enthalten. Andere haben diese weitaus besser beschrieben als ich, deshalb gebe ich Ihnen nur ein paar Stichwörter: Kartenkennzeichnung, gespeicherte Sätze, Schreiben von Barrieren. Es gibt auch andere Techniken (einschließlich Hybriden), aber diese umfassen die gängigen Ansätze, die mir bekannt sind.


3

Um herauszufinden, welche Kindergartenobjekte noch vorhanden sind, muss der Collector nur den Stammsatz und alle alten Objekte scannen, die seit der letzten Sammlung mutiert wurden , da ein altes Objekt, das kürzlich nicht mutiert wurde, möglicherweise nicht auf ein junges Objekt verweisen kann . Es gibt verschiedene Algorithmen, um diese Informationen auf unterschiedlichen Genauigkeitsniveaus zu halten (von einem exakten Satz mutierter Felder bis zu einem Satz von Seiten, auf denen möglicherweise Mutationen aufgetreten sind), aber alle beinhalten im Allgemeinen eine Art Schreibsperre : Code, der auf jeder Referenz ausgeführt wird Feldmutation, die die Buchhaltung des GC aktualisiert.


1

Die älteste und einfachste Generation von Garbage Collectors hat tatsächlich den gesamten Speicher gescannt und musste währenddessen alle anderen Verarbeitungsvorgänge stoppen. Spätere Algorithmen haben dies auf verschiedene Weise verbessert - indem sie das Kopieren / Scannen inkrementell oder parallel ausführen. Die meisten modernen Garbage Collectors unterteilen Objekte in Generationen und verwalten generationsübergreifende Zeiger sorgfältig, sodass neuere Generationen erfasst werden können, ohne ältere zu stören.

Der entscheidende Punkt ist, dass Garbage Collectors eng mit dem Compiler und dem Rest der Laufzeit zusammenarbeiten, um die Illusion aufrechtzuerhalten, dass der gesamte Speicher überwacht wird.


Ich bin nicht sicher, welche Speicherbereinigungsansätze in Minicomputern und Großrechnern vor den späten 1970er Jahren verwendet wurden, aber der Microsoft BASIC-Garbage Collector setzte zumindest auf 6502 Computern den Zeiger "next string" auf den oberen Speicherbereich und suchte dann Alle Zeichenfolgen verweisen auf die höchste Adresse, die sich unter dem "nächsten Zeichenfolgenzeiger" befand. Diese Zeichenfolge würde direkt unterhalb des "Zeigers der nächsten Zeichenfolge" kopiert und dieser Zeiger würde direkt darunter geparkt. Der Algorithmus würde sich dann wiederholen. Es war möglich, dass Code die Zeiger
verzauberte

... so etwas wie eine Generationssammlung. Ich habe mich manchmal gefragt, wie schwierig es wäre, das BASIC zu patchen, um eine "Generations" -Auflistung zu implementieren, indem einfach die Adressen der obersten Generation beibehalten und vor und nach jedem GC-Zyklus einige Zeigerwechseloperationen hinzugefügt werden. Die GC-Leistung wäre immer noch ziemlich schlecht, könnte jedoch in vielen Fällen von einigen zehn Sekunden bis zu einigen Zehntel Sekunden reduziert werden.
Supercat

-2

Grundsätzlich ... GC verwendet "Eimer", um zu trennen, was verwendet wird und was nicht. Sobald es überprüft wurde, löscht es nicht verwendete Objekte und verschiebt alles andere in die 2. Generation (die weniger häufig überprüft wird als die 1. Generation) und verschiebt dann Objekte, die in der 2. Den noch verwendet werden, in die 3. Generation.

Daher sind Dinge in der 3. Generation normalerweise Objekte, die aus irgendeinem Grund offen bleiben, und GC prüft sie dort nicht sehr oft.


1
Aber woher weiß es, welche Objekte verwendet werden?
Pieter van Ginkel

Es verfolgt, welche Objekte über den erreichbaren Code erreichbar sind. Sobald ein Objekt von keinem Code mehr erreichbar ist, der ausgeführt werden kann (z. B. Code für eine zurückgegebene Methode), weiß der GC, dass das Sammeln sicher ist
JohnL

Sie beide beschreiben, wie GCs korrekt und nicht wie effizient sie sind. Nach der Frage zu urteilen, weiß OP das genau.

@delnan Ja, ich habe die Frage beantwortet, woher sie weiß, welche Objekte verwendet werden, und wie lautete der Kommentar von Pieter.
JohnL

-5

Der von diesem GC üblicherweise verwendete Algorithmus ist das Naive Mark-and-Sweep

Sie sollten sich auch der Tatsache bewusst sein, dass dies nicht von C # selbst verwaltet wird, sondern von der sogenannten CLR .


Das ist das Gefühl, das ich beim Lesen über Monos Müllsammler hatte. Was ich jedoch nicht verstehe, ist, warum sie, wenn sie den gesamten Arbeitsumfang von ever collect scannen, einen Generationssammler haben, mit dem die GEN-0-Sammlung sehr schnell erstellt werden kann. Wie kann das mit einem funktionierenden Satz von beispielsweise 2 GB jemals schnell gehen?
Pieter van Ginkel

gut, die wirkliche GC für Mono Sgen, können Sie dieses lesen sollten mono-project.com/Generational_GC oder einige Online - Artikel schani.wordpress.com/tag/mono infoq.com/news/2011/01/SGen , ist der Punkt , dass Diese neuen Technologien wie CLR und CLI sind sehr modular aufgebaut. Die Sprache ist nur eine Möglichkeit, etwas für die CLR auszudrücken, und keine Möglichkeit, Binärcode zu erzeugen. Bei Ihrer Frage geht es um Implementierungsdetails und nicht um Algorithmen. Da ein Algorithmus noch keine Implementierung hat, sollten Sie nur Fachartikel und Artikel von Mono lesen, sonst niemand.
User827992

Ich bin verwirrt. Die Strategie eines Garbage Collectors ist kein Algorithmus?
Pieter van Ginkel

2
-1 Hör auf, OP zu verwirren. Dass der GC Teil der CLR ist und nicht sprachspezifisch, ist überhaupt nicht relevant. Eine GC ist vor allem durch die Art und Weise gekennzeichnet es den Heap legt und bestimmt Erreichbarkeits, und letztere ist alles über den Algorithmus (s) für diese verwendet. Obwohl es viele Implementierungen eines Algorithmus geben kann und Sie sich nicht mit Implementierungsdetails befassen sollten, bestimmt der Algorithmus allein, wie viele Objekte gescannt werden. Ein Generations-GC ist einfach ein Algorithmus + Heap-Layout, bei dem versucht wird, die "Generationshypothese" zu verwenden (dass die meisten Objekte jung sterben). Diese sind nicht naiv.

4
Algorithmus! = Implementierung in der Tat, aber eine Implementierung kann nur so weit abweichen, bevor sie zur Implementierung eines anderen Algorithmus wird. Eine Algorithmusbeschreibung in der GC-Welt ist sehr spezifisch und umfasst Dinge wie das Nicht-Scannen des gesamten Haufens für die Sammlung von Kindergärten und das Auffinden und Speichern von generationsübergreifenden Zeigern. Es ist wahr, dass ein Algorithmus nicht angibt, wie lange ein bestimmter Schritt des Algorithmus dauern wird, aber das ist für diese Frage überhaupt nicht relevant.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.