Warum Garbage Collection, wenn intelligente Zeiger vorhanden sind?


67

In diesen Tagen werden so viele Sprachen Müll gesammelt. Es ist sogar für C ++ von Drittanbietern erhältlich. Aber C ++ hat RAII und intelligente Zeiger. Wozu dient die Garbage Collection? Macht es etwas extra?

Und wenn in anderen Sprachen wie C # alle Verweise nach Spezifikation und Implementierung als intelligente Zeiger behandelt werden (RAII beiseite lassen), werden dann immer noch Garbage Collectors benötigt? Wenn nein, warum ist das nicht so?


1
Nachdem ich diese Frage gestellt hatte, verstand ich Folgendes: Smart Pointer benötigen RAII, um die automatische Freigabe zu verwalten .
Gulshan

8
Intelligente Zeiger bedeuten die Verwendung von RAII für GC;)
Dario

Heh, c # sollte eine Option für die Behandlung aller "Garbage Collection" mit RAII haben. Zirkuläre Verweise können beim Herunterfahren der Anwendung erkannt werden. Wir müssen nur sehen, welche Zuordnungen sich noch im Speicher befinden, nachdem die Zuordnung der Program.cs-Klasse aufgehoben wurde. Dann können Zirkelverweise durch eine Art Wochenverweis ersetzt werden.
AareP

Antworten:


67

Wozu also die Garbage Collection?

Ich gehe davon aus, dass Sie Smart Pointer mit Referenzzähler meinen, und ich stelle fest, dass es sich um eine (rudimentäre) Form der Speicherbereinigung handelt. Daher beantworte ich die Frage: "Was sind die Vorteile anderer Formen der Speicherbereinigung gegenüber Smart Pointer mit Referenzzähler?" stattdessen.

  • Genauigkeit . Alleine die Referenzzählung führt zu einem Verlust von Zyklen, sodass bei intelligenten Zeigern mit Referenzzählung im Allgemeinen ein Verlust von Speicher auftritt, sofern keine anderen Techniken zum Abfangen von Zyklen hinzugefügt werden. Sobald diese Techniken hinzugefügt wurden, ist der Vorteil der Einfachheit der Referenzzählung verschwunden. Beachten Sie auch, dass auf dem Gültigkeitsbereich basierende Referenzzählungs- und Ablaufverfolgungs-GCs Werte zu unterschiedlichen Zeitpunkten erfassen, manchmal werden Referenzzählungen früher erfasst und manchmal werden Ablaufverfolgungs-GCs früher erfasst.

  • Durchsatz . Intelligente Zeiger sind eine der am wenigsten effizienten Formen der Speicherbereinigung, insbesondere im Kontext von Multithread-Anwendungen, bei denen die Referenzzahlen atomar erhöht werden. Es gibt fortschrittliche Referenzzähltechniken, mit denen dies gelindert werden soll. In Produktionsumgebungen sind Tracing-GCs jedoch immer noch der Algorithmus der Wahl.

  • Latenz . Typische Smart Pointer-Implementierungen ermöglichen Destruktoren eine Lawine, was zu unbegrenzten Pausenzeiten führt. Andere Arten der Müllabfuhr sind wesentlich inkrementeller und können sogar in Echtzeit erfolgen, z. B. das Laufband von Baker.


23
Ich kann nicht glauben, dass diese Antwort die beste Antwort war. Es zeigt einen völligen Mangel an Verständnis für intelligente C ++ - Zeiger und macht Behauptungen, die so sehr nicht mit der Realität übereinstimmen, dass es einfach lächerlich ist. Erstens ist der intelligente Zeiger, der in einem gut gestalteten Teil des C ++ - Codes am dominantesten ist, der eindeutige Zeiger und nicht der gemeinsame Zeiger. en.cppreference.com/w/cpp/memory/unique_ptr Und zweitens, ich kann nicht glauben , dass Sie tatsächlich ‚Leistung‘ Vorteile und Echtzeit - Vorteile von nicht deterministische Garbage Collection über intelligente Zeiger behaupten.
user1703394

4
@ user1703394 Es scheint, dass der Antwortende geteilte Zeiger im Sinn hatte (zu Recht oder zu Unrecht, ich bin mir nicht ganz sicher, was die OPs vorschlagen), die weniger performant sind als nicht deterministische Garbage Collection.
Nathan Cooper

8
Dies sind alles Strohmann-Argumente und nur gültig, wenn Sie entweder die eigentliche Frage vollständig ignorieren oder die tatsächlichen Verwendungsmuster der verschiedenen Arten von intelligenten Zeigern ignorieren. Die Frage war über kluge Zeiger. Ja, shared_ptr ist ein intelligenter Zeiger, und yes, shared_ptr ist der teuerste intelligente Zeiger, aber nein, es gibt kein tatsächliches Argument für ihre allgegenwärtige Verwendung, das nahe daran liegt, ein Leistungsargument für relevant zu erklären. Im Ernst, diese Antwort sollte auf eine Frage zur Referenzzählung verschoben werden. Ich bin eine schlechte Antwort auf eine gute Smart Pointer Frage.
user1703394

4
"Intelligente Zeiger sind kein umfassenderes Konzept", im Ernst? Sie haben keine Ahnung, wie sehr diese Aussage alle möglicherweise gültigen Argumente untergräbt, die Sie bisher vorgebracht haben. Vielleicht werfen Sie einen Blick auf Rust Ownership und Move-Semantik: koerbitz.me/posts/… Es ist für Menschen mit historischer Erfahrung mit C ++ einfach, die Tatsache zu übersehen, dass C ++ 11 / C ++ 14 mit seinem Speichermodell smart verbessert wurde Zeiger und Bewegungssemantik ist ein ganz anderes Biest als ihre Vorgänger. Schauen Sie sich an, wie Rust es macht, es ist sauberer als C ++ und bietet eine neue Perspektive.
user1703394

6
@ user1703394: "Ungebundene Pausen aufgrund von Destruktoren sind eine unglückliche Eigenschaft von RAII, das für Nicht-Speicherressourcen verwendet wird." Nein, das hat überhaupt nichts mit Nicht-Speicher-Ressourcen zu tun.
Jon Harrop

63

Da niemand es aus diesem Blickwinkel betrachtet hat, werde ich Ihre Frage umformulieren: Warum etwas in die Sprache bringen, wenn Sie es in einer Bibliothek tun können? Das Ignorieren spezifischer Implementierungs- und syntaktischer Details bei GC / Smart Pointern ist im Grunde ein Sonderfall dieser Frage. Warum einen Garbage Collector in der Sprache selbst definieren, wenn Sie ihn in einer Bibliothek implementieren können?

Es gibt ein paar Antworten auf diese Frage. Das Wichtigste zuerst:

  1. Sie stellen sicher, dass der gesamte Code für die Interaktion verwendet werden kann. Dies ist meiner Meinung nach der Hauptgrund, warum sich die Wiederverwendung von Code und die gemeinsame Nutzung von Code erst mit Java / C # / Python / Ruby wirklich bemerkbar gemacht haben. Bibliotheken müssen kommunizieren, und die einzige zuverlässige gemeinsame Sprache, die sie haben, ist die Sprache selbst (und zu einem gewissen Grad die Standardbibliothek). Wenn Sie jemals versucht haben, Bibliotheken in C ++ wiederzuverwenden, haben Sie wahrscheinlich den entsetzlichen Schmerz erfahren, den keine Standard-Speichersemantik verursacht. Ich möchte eine Struktur an eine Bibliothek übergeben. Übergebe ich eine Referenz? Zeiger? scoped_ptr?smart_ptr? Übergebe ich das Eigentum oder nicht? Gibt es eine Möglichkeit, das anzuzeigen? Was ist, wenn die Bibliothek etwas zuweisen muss? Muss ich ihm einen Allokator geben? Da die Speicherverwaltung nicht Teil der Sprache ist, muss jedes Bibliothekspaar in C ++ eine eigene Strategie aushandeln, und es ist wirklich schwierig, alle zur Übereinstimmung zu bringen. GC macht das zu einem absoluten No-Issue.

  2. Sie können die Syntax darum herum entwerfen. Da C ++ die Speicherverwaltung selbst nicht kapselt, muss es eine Reihe syntaktischer Hooks bereitstellen, damit Code auf Benutzerebene alle Details ausdrückt. Sie haben Zeiger, Verweise, constDereferenzierungsoperatoren, Indirektionsoperatoren, Adressen usw. Wenn Sie die Speicherverwaltung in die Sprache selbst übertragen, kann die Syntax entsprechend angepasst werden. Alle diese Operatoren verschwinden und die Sprache wird übersichtlicher und einfacher.

  3. Sie erzielen einen hohen Return on Investment. Der Wert, den ein bestimmtes Stück Code generiert, wird mit der Anzahl der Benutzer multipliziert. Dies bedeutet, je mehr Benutzer Sie haben, desto mehr können Sie sich leisten, für ein Stück Software auszugeben. Wenn Sie ein Feature in die Sprache verschieben, wird es von allen Benutzern der Sprache verwendet. Dies bedeutet, dass Sie mehr Aufwand dafür aufwenden können als für eine Bibliothek, die nur von einer Teilmenge dieser Benutzer verwendet wird. Aus diesem Grund verfügen Sprachen wie Java und C # über absolut erstklassige VMs und fantastisch hochwertige Garbage Collectors: Die Kosten für deren Entwicklung amortisieren sich bei Millionen von Benutzern.


Fantastische Antwort! Wenn ich nur mehr als einmal abstimmen könnte ...
Dean Harding

10
Es ist erwähnenswert, dass die Garbage Collection nicht in der C # -Sprache selbst implementiert ist, sondern in .NET Framework , insbesondere der Common Language Runtime (CLR).
Robert Harvey

6
@RobertHarvey: Es wird nicht von der Sprache implementiert , aber es würde ohne die Mitarbeit der Sprache nicht funktionieren . Beispielsweise muss der Compiler Informationen enthalten, die an jedem Punkt im Code die Position jedes Registers oder Stack-Frame-Offsets angeben, das einen Verweis auf ein nicht fixiertes Objekt enthält. Das ist eine absolute, ausnahmslose Unveränderlichkeit , die ohne Sprachunterstützung nicht aufrechterhalten werden kann.
Supercat

Ein wesentlicher Vorteil der Unterstützung der Sprache und des erforderlichen Frameworks durch GC besteht darin, dass sichergestellt wird, dass keine Verweise auf Speicher vorhanden sind, die für einen anderen Zweck zugewiesen werden könnten. Wenn man Disposeein Objekt aufruft, das eine Bitmap kapselt, ist jeder Verweis auf dieses Objekt ein Verweis auf ein entsorgtes Bitmap-Objekt. Wenn das Objekt vorzeitig gelöscht wurde, während es von einem anderen Code noch verwendet werden soll, kann die Bitmap-Klasse sicherstellen, dass der andere Code auf vorhersehbare Weise fehlschlägt . Im Gegensatz dazu ist die Verwendung eines Verweises auf den freigegebenen Speicher ein undefiniertes Verhalten.
Supercat

34

Speicherbereinigung bedeutet im Grunde nur, dass Ihre zugewiesenen Objekte automatisch freigegeben werden, sobald sie nicht mehr erreichbar sind.

Genauer gesagt, werden sie freigegeben, wenn sie für das Programm nicht mehr erreichbar sind , da Objekte, auf die zirkulär verwiesen wird, ansonsten niemals freigegeben würden.

Intelligente Zeiger beziehen sich nur auf jede Struktur, die sich wie ein gewöhnlicher Zeiger verhält, jedoch einige zusätzliche Funktionen enthält. Dazu gehören unter anderem die Freigabe, aber auch Copy-on-Write-Prüfungen, gebundene Schecks, ...

Wie Sie bereits ausgeführt haben, können jetzt intelligente Zeiger verwendet werden , um eine Form der Garbage Collection zu implementieren.

Aber der Gedankengang geht folgendermaßen:

  1. Garbage Collection ist eine coole Sache, da es praktisch ist und ich mich um weniger Dinge kümmern muss
  2. Deshalb: Ich möchte Müllabfuhr in meiner Sprache
  3. Wie kann ich GC in meine Sprache bringen?

Natürlich können Sie es von Anfang an so gestalten. C # wurde entwickelt , um Müll zu sammeln, also nur newIhr Objekt und es wird freigegeben, wenn die Verweise außerhalb des Gültigkeitsbereichs fallen. Wie das gemacht wird, liegt beim Compiler.

In C ++ war jedoch keine Garbage Collection vorgesehen. Wenn wir einen Zeiger zuweisen int* p = new int;und dieser außerhalb des Gültigkeitsbereichs liegt, pwird er vom Stapel entfernt, aber niemand kümmert sich um den zugewiesenen Speicher.

Jetzt haben Sie von Anfang an nur noch deterministische Destruktoren . Wenn ein Objekt den Bereich verlässt, in dem es erstellt wurde, wird sein Destruktor aufgerufen. In Kombination mit Vorlagen und Überladen von Operatoren können Sie ein Wrapper-Objekt entwerfen, das sich wie ein Zeiger verhält, jedoch Destruktorfunktionen zum Bereinigen der damit verbundenen Ressourcen (RAII) verwendet. Sie nennen dies einen intelligenten Zeiger .

Dies ist alles sehr spezifisch für C ++: Überladen von Operatoren, Vorlagen, Destruktoren, ... In dieser speziellen Sprachsituation haben Sie intelligente Zeiger entwickelt, um Ihnen den gewünschten GC bereitzustellen.

Wenn Sie jedoch von Anfang an eine Sprache mit GC entwerfen, handelt es sich lediglich um ein Implementierungsdetail. Sie sagen nur, dass das Objekt bereinigt wird und der Compiler dies für Sie erledigt.

Intelligente Zeiger wie in C ++ wären wahrscheinlich nicht einmal in Sprachen wie C # möglich, die überhaupt keine deterministische Zerstörung aufweisen (C # umgeht dies, indem es syntaktischen Zucker zum Aufrufen .Dispose()bestimmter Objekte bereitstellt ). Nicht referenzierte Ressourcen werden schließlich vom GC zurückgefordert, aber es ist nicht definiert, wann genau dies geschieht.

Dies wiederum kann es dem GC ermöglichen, seine Arbeit effizienter zu erledigen. Wird in tiefer in die Sprache als intelligenter Zeiger gebaut, die oben drauf gesetzt werden, kann das .NET GC zB Speicheroperationen verzögern und sie in den Blöcken führen sie billiger zu machen oder sogar bewegen Speicher um zur Steigerung der Effizienz auf , wie oft Objekte abgerufen werden.


C # hat eine Form der deterministischen Zerstörung über IDisposableund using. Es erfordert jedoch ein wenig Programmieraufwand, weshalb es normalerweise nur für sehr knappe Ressourcen wie Datenbankverbindungshandles verwendet wird.
JSB ձոգչ

7
@ JSBangs: Genau. Genau wie C ++ intelligente Zeiger um RAII erstellt, um GC zu erhalten, geht C # in die andere Richtung und erstellt "intelligente Disposer" um den GC, um RAII zu erhalten;) Tatsächlich ist es eine Schande, dass RAII in C # so schwierig ist, da es für Ausnahmefälle großartig ist. sicherer Umgang mit Ressourcen. F # zum Beispiel versucht eine einfachere IDisposableSyntax, indem es nur konventionelle let ident = valuedurch use ident = value... ersetzt
Dario

@Dario: "C # geht in die andere Richtung und baut" intelligente Disposer "um den GC, um RAII zu erhalten". RAII in C # mit usinghat überhaupt nichts mit Garbage Collection zu tun, sondern ruft nur eine Funktion auf, wenn eine Variable aus dem Geltungsbereich fällt, genau wie Destruktoren in C ++.
Jon Harrop

1
@ Jon Harrop: Bitte was? Die Aussage, die Sie zitieren, bezieht sich auf einfache C ++ - Destruktoren ohne Referenzzählung / Smart Pointer / Garbage Collection.
Dario

1
"Speicherbereinigung bedeutet im Grunde nur, dass Ihre zugewiesenen Objekte automatisch freigegeben werden, wenn sie nicht mehr referenziert werden. Genauer gesagt, sie werden freigegeben, wenn sie für das Programm nicht mehr erreichbar sind, da Objekte, auf die zirkulär verwiesen wird, sonst niemals freigegeben würden." ... Präziser wäre zu sagen , dass sie automatisch freigegeben werden irgendwann nach , nicht , wenn . Beachten Sie, dass, wenn impliziert, dass die Rückforderung sofort erfolgt, die Rückforderung jedoch häufig erst viel später erfolgt.
Todd Lehman

4

Meiner Meinung nach gibt es zwei große Unterschiede zwischen Garbage Collection und Smart Pointern für die Speicherverwaltung:

  1. Intelligente Zeiger können keinen zyklischen Müll sammeln. Mülltonne
  2. Intelligente Zeiger erledigen die gesamte Arbeit zum Zeitpunkt des Referenzierens, Dereferenzierens und Aufhebens der Zuordnung für den Anwendungsthread. Müllabfuhr muss nicht

Ersteres bedeutet, dass GC Müll sammelt, den intelligente Zeiger nicht sammeln. Wenn Sie intelligente Zeiger verwenden, müssen Sie diese Art von Müll vermeiden oder darauf vorbereitet sein, manuell damit umzugehen.

Letzteres bedeutet, dass die Funktionsweise von intelligenten Zeigern die Arbeitsthreads in Ihrem Programm verlangsamt, unabhängig davon, wie intelligent sie sind. Garbage Collection kann die Arbeit verschieben und in andere Threads verschieben. das macht es insgesamt effizienter (in der Tat sind die Laufzeitkosten eines modernen GC geringer als bei einem normalen malloc / free-System, auch ohne den zusätzlichen Aufwand von intelligenten Zeigern) und erledigen die Arbeit, die es noch tun muss, ohne in das System einzusteigen Weg der Anwendung Threads.

Beachten Sie nun, dass intelligente Zeiger als programmatische Konstrukte verwendet werden können, um alle möglichen anderen interessanten Dinge zu erledigen - siehe Darios Antwort -, die vollständig außerhalb des Bereichs der Garbage Collection liegen. Wenn Sie diese ausführen möchten, benötigen Sie intelligente Zeiger.

Zum Zwecke der Speicherverwaltung sehe ich jedoch keine Aussicht darauf, dass intelligente Zeiger die Speicherbereinigung ersetzen. Sie sind einfach nicht so gut darin.


6
@Tom: Einzelheiten zu intelligenten Zeigern finden Sie in Darios Antwort. Was die Vorteile von intelligenten Zeigern angeht, kann die deterministische Freigabe ein enormer Vorteil sein, wenn sie zur Steuerung von Ressourcen (nicht nur Speicher) verwendet wird. Tatsächlich hat sich dies als so wichtig erwiesen , dass Microsoft den usingBlock in nachfolgenden Versionen von C # eingeführt hat. Darüber hinaus kann das nicht deterministische Verhalten von GCs in Echtzeitsystemen verbieten (weshalb GCs dort nicht verwendet werden). Vergessen wir auch nicht, dass GCs so komplex sind, dass tatsächlich ein Speicherverlust auftritt und ziemlich ineffizient sind (z. B. Boehm…).
Konrad Rudolph

6
Der Nicht-Determinismus von GCs ist meiner Meinung nach ein roter Faden - es gibt GC-Systeme, die für den Echtzeitbetrieb geeignet sind (wie IBMs Recycler), auch wenn dies bei Desktop- und Server-VMs nicht der Fall ist. Außerdem bedeutet die Verwendung intelligenter Zeiger, dass malloc / free verwendet wird, und herkömmliche Implementierungen von malloc sind nicht deterministisch, da die freie Liste durchsucht werden muss. Verschieben von GC - Systeme haben mehr deterministisch Zuordnungszeiten als malloc / free - Systeme, obwohl natürlich weniger deterministisch Deallokation mal.
Tom Anderson

3
Was die Komplexität betrifft: Ja, GCs sind komplex, aber ich bin mir nicht bewusst, dass "die meisten tatsächlich Speicherverluste aufweisen und ziemlich ineffizient sind", und ich wäre interessiert, andere Beweise zu sehen. Boehm ist kein Beweis, da es sich um eine sehr einfache Implementierung handelt, die für eine Sprache namens C entwickelt wurde, in der eine genaue GC aufgrund mangelnder Typensicherheit grundsätzlich nicht möglich ist. Es ist eine mutige Anstrengung, und dass es überhaupt funktioniert, ist sehr beeindruckend, aber Sie können es nicht als Beispiel für GC nehmen.
Tom Anderson

8
@ Jon: definitiv kein Bullshit. bugzilla.novell.com/show_bug.cgi?id=621899 oder allgemeiner: flyingfrogblog.blogspot.com/2009/01/... Dies ist wohl bekannt und eine Eigenschaft aller konservativen GCs.
Konrad Rudolph

3
"Die Laufzeitkosten eines modernen GC sind geringer als bei einem normalen malloc / free-System." Roter Hering hier. Dies liegt nur daran, dass traditionelles Malloc ein schrecklich ineffizienter Algorithmus ist. Moderne Zuweiser, die mehrere Buckets für unterschiedliche Blockgrößen verwenden, sind viel schneller zuzuweisen, weitaus weniger anfällig für Heap-Fragmentierung und bieten dennoch eine schnelle Freigabe.
Mason Wheeler

3

Der Begriff Müllabfuhr impliziert, dass Müll gesammelt werden muss. In C ++ gibt es intelligente Zeiger in verschiedenen Varianten, vor allem den unique_ptr. Der unique_ptr ist im Grunde genommen ein Konstrukt mit einem einzigen Eigentümer und Gültigkeitsbereich. In einem gut gestalteten Teil des Codes würden sich die meisten Heap-zugewiesenen Dinge normalerweise hinter den intelligenten Zeigern unique_ptr befinden, und der Besitz dieser Ressourcen ist jederzeit gut definiert. Es gibt kaum Overhead in unique_ptr und unique_ptr beseitigt die meisten manuellen Speicherverwaltungsprobleme, die die Benutzer traditionell zu verwalteten Sprachen geführt haben. Da immer mehr Kerne gleichzeitig ausgeführt werden, werden die Entwurfsprinzipien, die den Code dazu bringen, zu jedem Zeitpunkt eindeutige und genau definierte Eigentumsrechte zu verwenden, für die Leistung immer wichtiger.

Selbst in einem gut gestalteten Programm, insbesondere in Umgebungen mit mehreren Threads, kann nicht alles ohne gemeinsam genutzte Datenstrukturen ausgedrückt werden, und für die Datenstrukturen, die wirklich benötigt werden, müssen Threads kommunizieren. RAII in c ++ funktioniert in einem Single-Thread-Setup ziemlich gut, da in einem Multi-Thread-Setup die Lebensdauer von Objekten möglicherweise nicht vollständig hierarchisch gestapelt ist. In diesen Situationen bietet die Verwendung von shared_ptr einen großen Teil der Lösung. Sie erstellen ein gemeinsames Eigentum an einer Ressource, und dies ist in C ++ der einzige Ort, an dem wir Müll sehen. Bei so geringen Mengen sollte jedoch ein ordnungsgemäß gestaltetes c ++ - Programm eher als Implementierung einer 'Wurf'-Auflistung mit gemeinsam genutzten ptrs als als vollständige Garbage-Auflistung betrachtet werden in anderen Sprachen implementiert. C ++ hat einfach nicht so viel 'Müll'

Wie von anderen angegeben, sind intelligente Zeiger mit Referenzzählung eine Form der Speicherbereinigung, und eine für diese hat ein Hauptproblem. Das Beispiel, das hauptsächlich als Nachteil referenzierter Formen der Speicherbereinigung verwendet wird, ist das Problem bei der Erstellung verwaister Datenstrukturen, die mit intelligenten Zeigern miteinander verbunden sind und Objektcluster erstellen, die sich gegenseitig davon abhalten, gesammelt zu werden. Während in einem Programm, das nach dem Akteurmodell der Berechnung entworfen wurde, die Datenstrukturen normalerweise nicht zulassen, dass solche nicht sammelbaren Cluster in C ++ entstehen, wenn Sie den umfassenden Ansatz der geteilten Daten für die Multithread-Programmierung verwenden, wie er überwiegend verwendet wird In der Branche können diese verwaisten Cluster schnell Realität werden.

Zusammenfassend lässt sich sagen, dass Sie unter Verwendung gemeinsamer Zeiger die weit verbreitete Verwendung von unique_ptr in Kombination mit dem Rechenansatz des Actor-Modells für die Multithread-Programmierung und die eingeschränkte Verwendung von shared_ptr als andere Formen der Garbage Collection nicht kaufen zusätzliche Vorteile. Wenn Sie jedoch bei einem Ansatz, bei dem alles gemeinsam genutzt wird, überall auf shared_ptr stoßen, sollten Sie erwägen, entweder die Parallelitätsmodelle zu wechseln oder auf eine verwaltete Sprache zu wechseln, die stärker auf die gemeinsame Nutzung von Eigentümern und den gleichzeitigen Zugriff auf Datenstrukturen ausgerichtet ist.


1
Bedeutet das, dass Sie Rustkeine Müllabfuhr benötigen?
Gulshan

1
@ Gulshan Rust ist eine der wenigen Sprachen, die sichere eindeutige Zeiger unterstützt.
CodesInChaos

2

Die meisten intelligenten Zeiger werden mithilfe der Referenzzählung implementiert. Das heißt, jeder Smart Pointer, der sich auf ein Objekt bezieht, erhöht den Referenzzähler des Objekts. Wenn dieser Zähler auf Null geht, wird das Objekt freigegeben.

Das Problem ist, wenn Sie Zirkelverweise haben. Das heißt, A hat einen Verweis auf B, B hat einen Verweis auf C und C hat einen Verweis auf A. Wenn Sie intelligente Zeiger verwenden, müssen Sie den mit A, B und C verknüpften Speicher manuell freigeben Holen Sie sich dort ein "break" der Zirkelreferenz (zB mit weak_ptrin C ++).

Die Speicherbereinigung funktioniert (normalerweise) ganz anders. Die meisten Müllsammler verwenden heutzutage einen Erreichbarkeitstest . Das heißt, es werden alle Verweise auf dem Stapel und die global zugänglichen Verweise betrachtet und anschließend alle Objekte, auf die sich diese Verweise beziehen, sowie die Objekte, auf die sie sich beziehen, usw. verfolgt. Alles andere ist Müll.

Auf diese Weise spielen Zirkelverweise keine Rolle mehr - solange weder A noch B oder C erreichbar sind , kann der Speicher zurückgewonnen werden.

Die "echte" Müllabfuhr bietet noch weitere Vorteile. Zum Beispiel ist die Speicherzuweisung extrem billig: Erhöhen Sie einfach den Zeiger auf das "Ende" des Speicherblocks. Die Aufhebung der Zuordnung hat ebenfalls konstante fortgeführte Anschaffungskosten. Natürlich können Sie in Sprachen wie C ++ die Speicherverwaltung so gut wie beliebig implementieren, sodass Sie eine noch schnellere Allokationsstrategie entwickeln können.

Natürlich ist in C ++ die Menge des Heap-zugewiesenen Speichers in der Regel geringer als in einer referenzlastigen Sprache wie C # /. NET. Aber das ist nicht wirklich ein Problem mit der Garbage Collection im Vergleich zu Smart Pointern.

In jedem Fall ist das Problem nicht einfach zu lösen, eines ist besser als das andere. Sie haben jeweils Vor- und Nachteile.


2

Es geht um Leistung . Das Aufheben der Speicherzuweisung erfordert viel Verwaltungsaufwand. Wenn die Aufhebung der Zuordnung im Hintergrund ausgeführt wird, erhöht sich die Leistung des Vordergrundprozesses. Leider kann die Speicherzuweisung nicht träge sein (die zugewiesenen Objekte werden im nächsten heiligen Moment verwendet), das Freigeben von Objekten jedoch.

Versuchen Sie in C ++ (ohne GC), eine große Menge von Objekten zuzuweisen, drucken Sie "Hallo" und löschen Sie sie dann. Sie werden überrascht sein, wie lange es dauert, Objekte freizugeben.

GNU libc bietet außerdem effektivere Tools zum Aufheben der Speicherzuweisung (siehe Hindernisse) . Muss beachten, ich habe keine Erfahrung mit Hindernissen, ich habe sie nie benutzt.


Grundsätzlich haben Sie einen Punkt, aber es sollte beachtet werden, dass dies ein Problem ist, das eine sehr einfache Lösung bietet: Verwenden Sie einen Pool-Allokator oder einen kleinen Objekt-Allokator, um Aufhebungszuordnungen zu bündeln. Dies ist allerdings (geringfügig) aufwändiger als ein GC-Lauf im Hintergrund.
Konrad Rudolph

Ja, sicher, GC ist viel komfortabler. (Besonders für Anfänger: Es gibt keine
Besitzprobleme

3
@ ern0: nein. Der springende Punkt bei intelligenten (Referenzzähl-) Zeigern ist, dass es kein Besitzproblem und keinen Löschoperator gibt.
Konrad Rudolph

3
@ Jon: Das ist ehrlich gesagt die meiste Zeit. Wenn Sie den Objektstatus wohl oder übel zwischen verschiedenen Threads teilen, treten völlig unterschiedliche Probleme auf. Ich gebe zu, dass viele Leute auf diese Weise programmieren, aber dies ist eine Folge der schlechten Threading-Abstraktionen, die bis vor kurzem bestanden haben, und es ist keine gute Möglichkeit, Multithreading durchzuführen.
Konrad Rudolph

1
Die Freigabe erfolgt oft nicht "im Hintergrund", sondern pausiert alle Vordergrund-Threads. Batch-Modus-Garbage-Collection ist im Allgemeinen ein Leistungsgewinn, obwohl Vordergrund-Threads angehalten wurden, da nicht genutzter Speicherplatz konsolidiert werden kann. Man könnte die Prozesse der Speicherbereinigung und der Heap-Komprimierung voneinander trennen, aber - insbesondere in Frameworks, die direkte Verweise anstelle von Handles verwenden - sind beide in der Regel "Stop-the-World" -Prozesse, und es ist oft am praktischsten, sie auszuführen zusammen.
Supercat

2

Die Speicherbereinigung kann effizienter sein - sie "stapelt" den Overhead der Speicherverwaltung und erledigt alles auf einmal. Im Allgemeinen führt dies dazu, dass insgesamt weniger CPU für die Aufhebung der Speicherzuweisung aufgewendet wird. Dies bedeutet jedoch, dass Sie irgendwann einen großen Stoß an Aktivitäten zur Aufhebung der Zuweisung haben. Wenn der GC nicht richtig ausgelegt ist, kann dies für den Benutzer als "Pause" sichtbar werden, während der GC versucht, den Speicher freizugeben. Die meisten modernen GCs sind sehr gut darin, dies für den Benutzer unsichtbar zu halten, außer unter den widrigsten Bedingungen.

Intelligente Zeiger (oder ein beliebiges Referenzzählschema) haben den Vorteil, dass sie genau dann auftreten, wenn Sie vom Anzeigen des Codes ausgehen (intelligente Zeiger verlieren den Gültigkeitsbereich, was gelöscht wird). Hier und da gibt es kleine Schübe von Aufhebungen. Sie können insgesamt mehr CPU-Zeit für die Aufhebung der Zuweisung verbrauchen, aber da dies auf alle Vorgänge in Ihrem Programm verteilt ist, ist es weniger wahrscheinlich, dass Ihr Benutzer die Aufhebung der Zuweisung von Monsterdatenstrukturen sieht.

Wenn Sie etwas tun, bei dem es auf Reaktionsfähigkeit ankommt, empfiehlt es sich, dass Sie durch intelligente Zeiger- / Referenzzählung genau wissen, wann etwas passiert, damit Sie beim Codieren wissen, was für Ihre Benutzer wahrscheinlich sichtbar wird. In einer GC-Umgebung haben Sie nur die kurzlebigste Kontrolle über den Garbage Collector und müssen einfach versuchen, die Sache zu umgehen.

Wenn der Gesamtdurchsatz Ihr Ziel ist, ist ein GC-basiertes System möglicherweise die bessere Wahl, da es die für die Speicherverwaltung erforderlichen Ressourcen minimiert.

Zyklen: Ich betrachte das Problem der Zyklen nicht als bedeutsam. In einem System mit intelligenten Zeigern tendieren Sie zu Datenstrukturen ohne Zyklen, oder Sie achten nur darauf, wie Sie solche Dinge loslassen. Bei Bedarf können Aufbewahrungsobjekte verwendet werden, die wissen, wie man die Zyklen in den eigenen Objekten unterbricht, um automatisch die ordnungsgemäße Zerstörung sicherzustellen. In einigen Bereichen der Programmierung mag dies wichtig sein, aber für die alltägliche Arbeit ist es irrelevant.


1
"Sie werden irgendwann eine große Menge an Aktivitäten zur Aufhebung der Zuweisung haben". Baker's Laufband ist ein Beispiel für einen wunderschön inkrementellen Müllsammler. memorymanagement.org/glossary/t.html#treadmill
Jon Harrop

1

Die größte Einschränkung bei intelligenten Zeigern ist, dass sie nicht immer gegen Zirkelverweise helfen. Beispiel: Sie haben Objekt A, das einen intelligenten Zeiger auf Objekt B speichert, und Objekt B, das einen intelligenten Zeiger auf Objekt A speichert. Wenn sie zusammen gelassen werden, ohne einen der Zeiger zurückzusetzen, werden sie niemals freigegeben.

Dies liegt daran, dass ein intelligenter Zeiger eine bestimmte Aktion ausführen muss, die im obigen Szenario nicht ausgelöst wird, da beide Objekte für das Programm nicht erreichbar sind. Die Garbage Collection wird zurechtkommen - es wird korrekt erkannt, dass Objekte für das Programm nicht erreichbar sind, und sie werden gesammelt.


1

Es ist ein Spektrum .

Wenn Sie nicht an Ihre Leistung gebunden sind und bereit sind, die Weichen zu stellen, müssen Sie die richtigen Entscheidungen treffen und die Freiheit, dies zu tun , die ganze Freiheit, es zu vermasseln:

"Ich sage dir, was du tun sollst, du tust es. Vertrau mir."

Die Garbage Collection ist das andere Ende des Spektrums. Sie haben nur sehr wenig Kontrolle, aber für Sie ist gesorgt:

"Ich sage dir, was ich will, du machst es möglich".

Dies hat viele Vorteile, zumeist, dass Sie nicht so vertrauenswürdig sein müssen, wenn Sie genau wissen möchten, wann eine Ressource nicht mehr benötigt wird, aber (trotz einiger der hier schwebenden Antworten) nicht gut für die Leistung sind die Vorhersehbarkeit der Leistung. (Wie bei allen Dingen kann es zu schlechteren Ergebnissen kommen, wenn Sie die Kontrolle haben und etwas Dummes tun. Wenn Sie jedoch während der Kompilierung wissen, unter welchen Bedingungen Speicher freigegeben werden kann, kann dies nicht als Leistungsgewinn verwendet werden.) jenseits der Naivität).

RAII, Scoping, Ref Counting usw. sind alles Hilfsmittel, mit denen Sie sich weiter in diesem Spektrum bewegen können, aber es ist nicht der ganze Weg dorthin. All diese Dinge müssen noch aktiv genutzt werden. Sie erlauben und verlangen weiterhin, dass Sie mit der Speicherverwaltung auf eine Weise interagieren, wie dies bei der Garbage Collection nicht der Fall ist.


0

Bitte denken Sie daran, dass am Ende alles auf eine CPU hinausläuft, die Anweisungen ausführt. Meines Wissens verfügen alle Consumer-CPUs über Befehlssätze, bei denen Sie Daten an einem bestimmten Ort im Speicher ablegen müssen, und Sie haben Zeiger auf diese Daten. Das ist alles, was Sie auf der Grundstufe haben.

Alles darüber hinaus, mit Garbage Collection, Verweisen auf möglicherweise verschobene Daten, Heap-Komprimierung usw. usw., erledigt die Arbeit innerhalb der Einschränkungen, die durch das oben genannte Paradigma "Speicherblock mit Adresszeiger" vorgegeben sind. Das Gleiche gilt für intelligente Zeiger - Sie müssen den Code NOCH auf der tatsächlichen Hardware ausführen.

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.