Nachteile der bereichsbasierten Speicherverwaltung

38

Ich mag Scope-based Memory Management (SBMM) oder RAII , wie es in der C ++ - Community häufiger (verwirrenderweise?) Genannt wird . Soweit ich weiß, gibt es bis auf C ++ (und C) keine andere Mainstream-Sprache, die SBMM / RAII zu ihrem Hauptspeicherverwaltungsmechanismus macht. Stattdessen bevorzugen sie die Garbage Collection (GC).

Ich finde das ziemlich verwirrend, da

  1. SBMM macht Programme deterministischer (Sie können genau sagen, wann ein Objekt zerstört wird);
  2. In Sprachen, die GC verwenden, müssen Sie häufig manuell Ressourcen verwalten (siehe Schließen von Dateien in Java), was den Zweck von GC teilweise zunichte macht und auch fehleranfällig ist.
  3. Heap-Speicher kann auch (sehr elegant, imo) bereichsgebunden sein (siehe std::shared_ptrin C ++).

Warum wird SBMM nicht häufiger eingesetzt? Was sind ihre Nachteile?

Paul
quelle
1
Einige Nachteile (insbesondere hinsichtlich der Geschwindigkeit) werden in Wikipedia diskutiert: en.wikipedia.org/wiki/…
Philipp
2
Das Problem der manuellen Ressourcenverwaltung in Java ist ein Nebeneffekt darin, dass nicht garantiert wird, dass die finalize()Methode eines Objekts vor der Garbage Collection aufgerufen wird. Tatsächlich entsteht dadurch dieselbe Problemklasse, die die Garbage Collection lösen soll.
Blrfl
7
@Blrfl Quatsch. "Manuelle" Ressourcenverwaltung (natürlich für andere Ressourcen als Speicher) ist vorzuziehen, auch wenn dieses "Problem" nicht vorhanden ist, da der GC sehr lange ausgeführt werden kann, nachdem die Ressource ungenutzt ist oder gar nicht ausgeführt wird. Das ist kein Problem für den Speicher , und die Speicherverwaltung ist alles, was die Garbage Collection lösen soll.
4
btw. Ich bezeichne es gerne als SBRM, da Sie den gleichen Mechanismus verwenden können, um Ressourcen im Allgemeinen zu verwalten, nicht nur Speicher.
PlasmaHH

Antworten:

27

Beginnen wir mit der Annahme, dass Speicher weitaus häufiger (Dutzende, Hunderte oder sogar Tausende) ist als alle anderen Ressourcen zusammen. Jede einzelne Variable, jedes Objekt und jedes Objektmitglied benötigt Speicher, der ihr zugewiesen und später freigegeben wird. Für jede Datei, die Sie öffnen, erstellen Sie Dutzende bis Millionen von Objekten, um die aus der Datei abgerufenen Daten zu speichern. Jeder TCP-Stream enthält eine unbegrenzte Anzahl temporärer Bytefolgen, die zum Schreiben in den Stream erstellt wurden. Sind wir hier auf der gleichen Seite? Groß.

Damit RAII funktioniert (auch wenn Sie vorgefertigte intelligente Zeiger für jeden Anwendungsfall unter der Sonne haben), müssen Sie das Eigentumsrecht erwerben. Sie müssen analysieren, wem dieses oder jenes Objekt gehören soll, wem nicht und wann das Eigentum von A auf B übertragen werden soll. Sicher, Sie könnten das gemeinsame Eigentum für alles verwenden , aber dann würden Sie einen GC über intelligente Zeiger emulieren. An diesem Punkt wird es viel einfacher und schneller, den GC in die Sprache zu integrieren.

Die Garbage Collection befreit Sie von dieser Sorge um die bei weitem am häufigsten verwendete Ressource Speicher. Sicher, Sie müssen immer noch die gleiche Entscheidung für andere Ressourcen treffen, aber diese sind weitaus seltener (siehe oben) und komplizierte (z. B. gemeinsame) Eigentumsverhältnisse sind ebenfalls seltener. Die mentale Belastung wird deutlich reduziert.

Nun nennen Sie einige Nachteile, um alle Werte müllsammeln zu lassen. Doch beiden Speichersichere GC zu integrieren und mit RAII in eine Sprache Werttypen ist extrem hart, so ist es vielleicht besser , diese Abwägungen über andere Mittel , um zu migitate?

Der Verlust des Determinismus erweist sich in der Praxis als nicht so schlimm, da er sich nur auf die Lebensdauer des deterministischen Objekts auswirkt . Wie im nächsten Abschnitt beschrieben, sind die meisten Ressourcen (abgesehen vom Arbeitsspeicher, der reichlich vorhanden ist und ziemlich träge recycelt werden kann) in diesen Sprachen nicht an die Lebensdauer von Objekten gebunden. Es gibt einige andere Anwendungsfälle, die meiner Erfahrung nach jedoch selten sind.

Ihr zweiter Punkt, die manuelle Ressourcenverwaltung, wird heutzutage über eine Anweisung angesprochen, die eine bereichsbezogene Bereinigung durchführt, diese Bereinigung jedoch nicht an die Objektlebensdauer koppelt (also nicht mit dem GC und der Speichersicherheit interagiert). Dies ist usingin C #, within Python, try-mit-Ressourcen in neueren Java-Versionen.


quelle
1
Dies erklärt nicht, warum nicht deterministische Modelle wie GC deterministischen überlegen sein sollten. usingAussagen sind nur lokal möglich. Es ist unmöglich, Ressourcen in Mitgliedsvariablen auf diese Weise zu bereinigen.
Philipp
8
@Philipp Geteilter Besitz für alles ist ein GC, nur ein sehr armer. Wenn Sie "Shared Ownership" annehmen, um das Nachzählen zu implizieren, sagen Sie dies bitte, aber behalten Sie die Diskussion über Zyklen in den Kommentaren zu Amons Antwort bei. Ich bin mir auch nicht sicher, ob die Referenzzählung in dem Sinne deterministisch ist, an dem OP interessiert ist. Darüber hinaus ist die Ref-Zählung für alles langsam, viel langsamer als bei einem modernen Tracing-GC.
16
usingist ein Witz im Vergleich zu RAII, nur damit Sie es wissen.
DeadMG
3
@Philipp Bitte beschreiben Sie Ihre Metrik für "überlegen". Zwar ist die manuelle Speicherverwaltung zur Laufzeit für die Speicherverwaltung schneller. Die Softwarekosten können jedoch nicht allein anhand der für die Speicherverwaltung aufgewendeten CPU-Zeit beurteilt werden.
Kunst
2
@ArTs: Dem würde ich nicht unbedingt zustimmen. RAII hat die Anforderung, dass ein Objekt zerstört werden muss, da es den Spielraum verlässt. Eine Schleife ist dann erforderlich, um n Objektzerstörungen durchzuführen. Unter einem modernen Generations-GC könnten diese Destruktionen bis zum Ende der Schleife oder sogar später zurückgestellt werden und nur eine Operation ausführen, um Hunderte von Speicheriterationen zu zerstören. Ein GC kann in guten Fällen sehr, sehr schnell sein.
Phoshi
14

RAII ergibt sich auch aus der automatischen Speicherverwaltung mit Referenzzählung, wie sie z. B. von Perl verwendet wird. Die Referenzzählung ist zwar einfach zu implementieren, deterministisch und sehr performant, kann jedoch keine Zirkelreferenzen verarbeiten (sie verursachen ein Leck), weshalb sie nicht häufig verwendet wird.

Speicherbereinigte Sprachen können RAII nicht direkt verwenden, bieten jedoch häufig eine Syntax mit gleichem Effekt. In Java haben wir die Anweisung try-with-ressource

try (BufferedReader br = new BufferedReader(new FileReader(path))) { ... }

Das ruft .close()die Ressource beim Beenden des Blocks automatisch auf. C # hat die IDisposableSchnittstelle, die .Dispose()beim Verlassen einer using (...) { ... }Anweisung aufgerufen werden kann . Python hat die withAussage:

with open(filename) as f:
    ...

was in ähnlicher Weise funktioniert. Interessanterweise erhält Rubys Methode zum Öffnen von Dateien einen Rückruf. Nachdem der Rückruf ausgeführt wurde, wird die Datei geschlossen.

File.open(name, mode) do |f|
    ...
end

Ich denke, Node.js verwendet die gleiche Strategie.

amon
quelle
4
Die Verwendung von Funktionen höherer Ordnung für die Ressourcenverwaltung reicht weit vor Ruby zurück. In Lisps ist es durchaus üblich, dass beispielsweise with-open-filehandleFunktionen die Datei öffnen, an eine Funktion übergeben und bei Rückkehr der Funktion die Datei wieder schließen.
Jörg W Mittag
4
Das zyklische Referenzargument ist weit verbreitet, aber wie wichtig ist es wirklich? Zyklische Verweise können mit schwachen Zeigern abgeschwächt werden, wenn die Eigentümerschaft klar ist.
Philipp
2
@Philipp Wenn Sie die Referenzzählung verwenden, ist der Besitz normalerweise nicht klar. In dieser Antwort geht es auch um Sprachen, die ausschließlich und automatisch die Referenzzählung verwenden, sodass schwache Referenzen entweder nicht existieren oder viel schwerer zu verwenden sind als starke Referenzen.
3
@Philipp Zyklische Datenstrukturen sind sehr selten, es sei denn, Sie arbeiten mit komplizierten Diagrammen. Schwache Zeiger helfen in einem allgemeinen zyklischen Objektdiagramm nicht weiter, obwohl sie in häufigeren Fällen hilfreich sind, z. B. bei übergeordneten Zeigern in einem Baum. Eine gute Lösung besteht darin, ein Kontextobjekt beizubehalten, das die Referenz auf das gesamte Diagramm darstellt und die Zerstörung verwaltet. Refcounting ist kein Dealbraker, aber es erfordert, dass der Programmierer sich seiner Einschränkungen sehr bewusst ist. Das heißt, es hat etwas höhere kognitive Kosten als GC.
Amon
1
Ein wichtiger Grund, warum Ref Counting selten verwendet wird, ist, dass es trotz seiner Einfachheit oft langsamer als GC ist.
Rufflewind
14

Meiner Meinung nach ist der überzeugendste Vorteil der Speicherbereinigung, dass sie die Komposition ermöglicht . Die Richtigkeit der Speicherverwaltung ist eine lokale Eigenschaft in einer Umgebung mit Speicherbereinigung. Sie können jedes Teil einzeln betrachten und feststellen, ob es einen Speicherverlust verursachen kann. Kombinieren Sie beliebig viele speicherkorrekte Teile und sie bleiben korrekt.

Wenn Sie sich auf die Referenzzählung verlassen, verlieren Sie diese Eigenschaft. Ob Ihre Anwendung Speicherverluste verursachen kann, wird mit der Referenzzählung zu einer globalen Eigenschaft der gesamten Anwendung. Jede neue Interaktion zwischen Teilen hat die Möglichkeit, den falschen Eigentümer zu verwenden und die Speicherverwaltung zu unterbrechen.

Es hat einen sehr sichtbaren Einfluss auf die Gestaltung von Programmen in den verschiedenen Sprachen. Programme in GC-Sprachen bestehen in der Regel aus ein wenig mehr Ansammlungen von Objekten mit vielen Interaktionen, während in GC-weniger Sprachen strukturierte Teile mit streng kontrollierten und begrenzten Interaktionen bevorzugt werden.

Patrick
quelle
1
Die Richtigkeit ist nur dann zusammensetzbar, wenn Objekte nur Verweise in ihrem eigenen Namen und nicht im Namen der Ziele dieser Verweise enthalten. Sobald Dinge wie Benachrichtigungen in den Mix eingehen (Bob hat einen Verweis auf Joe, weil Joe Bob gebeten hat, ihn zu benachrichtigen, wenn etwas passiert ist, und Bob versprochen hat, dies zu tun, aber Bob kümmert sich sonst nicht um Joe), erfordert die GC-Korrektheit oft eine gezielte Ressourcenverwaltung [In vielen Fällen manuell implementiert, da GC-Systeme nicht über die Automatisierung von C ++ verfügen].
Supercat
@supercat: "Die GC-Korrektheit erfordert häufig eine umfassende Ressourcenverwaltung." Wie? Gültigkeitsbereich existiert nur im Quellcode und GC existiert nur zur Laufzeit (und ist daher völlig unberücksichtigt von der Existenz des Gültigkeitsbereichs).
Jon Harrop
@ JonHarrop: Ich habe den Begriff "scope" im gleichen Sinne wie einen C ++ - Zeiger mit Gültigkeitsbereich verwendet (die Lebensdauer des Objekts sollte die des Containers sein, in dem es sich befindet), da dies die Verwendung ist, die in der ursprünglichen Frage impliziert wird. Mein Punkt ist, dass Objekte potenziell langlebige Verweise auf sich selbst erstellen, um beispielsweise Ereignisse zu empfangen, die in einem reinen GC-System möglicherweise nicht komponierbar sind. Für die Richtigkeit müssen bestimmte Referenzen stark und bestimmte Referenzen schwach sein, und welche Referenzen welche sein müssen, hängt davon ab, wie ein Objekt verwendet wird. Zum Beispiel ...
Supercat
Nehmen wir an, Objekte Fred und Barney melden sich zur Benachrichtigung an, wenn etwas in einem bestimmten Verzeichnis geändert wird. Freds Handler erhöht lediglich einen Zähler, dessen Wert er auf Anfrage melden kann, für den er jedoch keine andere Verwendung hat. Barneys Handler öffnet ein neues Fenster, wenn eine bestimmte Datei geändert wird. Aus Gründen der Korrektheit sollte Fred ein schwaches Ereignis haben, aber Barneys sollte stark sein, aber das Timer-Objekt wird keine Möglichkeit haben, das zu wissen.
Superkatze
@supercat: Richtig. Ich würde nicht sagen, dass das "oft" passiert. Ich habe es in 30 Jahren Programmierung nur einmal erlebt.
Jon Harrop
7

Verschlüsse sind ein wesentliches Merkmal so ziemlich aller modernen Sprachen. Sie sind mit GC sehr einfach zu implementieren und mit RAII nur sehr schwer (wenn auch nicht unmöglich) in Ordnung zu bringen, da eines ihrer Hauptmerkmale darin besteht, dass Sie mit ihnen über die Lebensdauer Ihrer Variablen abstrahieren können!

C ++ bekam sie erst 40 Jahre nach allen anderen und es war eine Menge harter Arbeit von vielen klugen Leuten, um sie richtig hinzubekommen. Im Gegensatz dazu verfügen viele Skriptsprachen, die von Personen entwickelt und implementiert wurden, die keine Kenntnisse im Entwerfen und Implementieren von Programmiersprachen haben.

Jörg W. Mittag
quelle
9
Ich denke nicht, dass Closures in C ++ ein gutes Beispiel sind. Die Lambdas in C ++ 11 sind nur syntaktischer Zucker für Funktorklassen (die deutlich älter sind als C ++ 11) und sind ebenfalls speichersicher: Sie erhalten einfach UB, ähnlich wie wenn Sie eine Referenz länger als gültig behalten. Dass sie 40 Jahre zu spät erschienen, liegt an der verspäteten Anerkennung von FP, nicht daran, wie man sie sicher macht. Und obwohl das Entwerfen sicherlich eine große Aufgabe war, bezweifle ich, dass der größte Teil der Anstrengungen auf Überlegungen zur Lebenszeit entfiel.
Ich bin mit Delnan einverstanden: C ++ hat die Closures nicht richtig programmiert: Sie müssen sie sehr sorgfältig programmieren, wenn Sie beim Aufrufen keinen Core-Dump erhalten möchten.
Giorgio
2
@delnan: Capture-by-Reference Lambda hat diese [&]Syntax absichtlich . Jeder C ++ - Programmierer verknüpft das &Zeichen bereits mit Referenzen und kennt veraltete Referenzen.
MSalters
2
@MSalters Was ist dein Punkt? Ich habe die Referenzverbindung selbst gezeichnet. Ich habe nicht gesagt, dass C ++ - Lambdas außergewöhnlich unsicher sind, ich habe gesagt, dass sie genauso unsicher sind wie Referenzen. Ich habe nicht argumentiert, dass C ++ - Lambdas schlecht sind, ich habe mich gegen die Behauptung dieser Antwort ausgesprochen (dass C ++ sehr spät geschlossen wurde, weil sie herausfinden mussten, wie man es richtig macht).
5
  1. SBMM macht Programme deterministischer (Sie können genau sagen, wann ein Objekt zerstört wird);

Für die meisten Programmierer ist das Betriebssystem nicht deterministisch, ihr Speicherzuweiser ist nicht deterministisch, und die meisten Programme, die sie schreiben, sind gleichzeitig ablaufend und daher von Natur aus nicht deterministisch. Das Hinzufügen der Einschränkung, dass ein Destruktor genau am Ende des Gültigkeitsbereichs und nicht kurz davor oder kurz danach aufgerufen wird, ist für die große Mehrheit der Programmierer kein wesentlicher praktischer Vorteil.

  1. In Sprachen, die GC verwenden, müssen Sie häufig manuell Ressourcen verwalten (siehe Schließen von Dateien in Java), was den Zweck von GC teilweise zunichte macht und auch fehleranfällig ist.

Siehe usingin C # und usein F #.

  1. Heap-Speicher kann auch (sehr elegant, imo) bereichsgebunden sein (siehe std :: shared_ptr in C ++).

Mit anderen Worten, Sie können den Heap-Speicher, bei dem es sich um eine allgemeine Lösung handelt, so ändern, dass er nur in einem bestimmten, stark einschränkenden Fall funktioniert. Das ist natürlich wahr, aber nutzlos.

Warum wird SBMM nicht häufiger eingesetzt? Was sind ihre Nachteile?

SBMM schränkt Ihre Möglichkeiten ein:

  1. SBMM schafft das Aufwärtsproblem mit erstklassigen lexikalischen Closures, weshalb Closures in Sprachen wie C # beliebt und einfach zu verwenden sind, in C ++ jedoch selten und knifflig. Es ist zu beachten, dass es einen allgemeinen Trend zur Verwendung von Funktionskonstrukten in der Programmierung gibt.

  2. SBMM benötigt Destruktoren und sie behindern Tail-Aufrufe, indem sie mehr Arbeit hinzufügen, bevor eine Funktion zurückkehren kann. Tail-Aufrufe sind nützlich für erweiterbare Zustandsautomaten und werden von Dingen wie .NET bereitgestellt.

  3. Einige Datenstrukturen und Algorithmen sind mit SBMM bekanntermaßen schwer zu implementieren. Grundsätzlich überall dort, wo Zyklen natürlich vorkommen. Vor allem Graph-Algorithmen. Sie schreiben letztendlich Ihren eigenen GC.

  4. Die gleichzeitige Programmierung ist schwieriger, da der Steuerungsfluss und damit die Objektlebensdauer inhärent nicht deterministisch sind. Praktische Lösungen in Nachrichtenübermittlungssystemen neigen dazu, Nachrichten tief zu kopieren und übermäßig lange Lebensdauern zu verwenden.

  5. SBMM hält Objekte bis zum Ende ihres Gültigkeitsbereichs im Quellcode am Leben, der häufig länger als erforderlich und weitaus länger als erforderlich sein kann. Dies erhöht die Menge an schwimmendem Müll (nicht erreichbare Objekte, die auf das Recycling warten). Im Gegensatz dazu werden bei der Speicherbereinigung Objekte schnell wieder freigegeben, nachdem der letzte Verweis auf sie verschwunden ist. Dies kann sehr viel schneller geschehen. Siehe Mythen zur Speicherverwaltung: Schnelligkeit .

SBMM ist so einschränkend, dass Programmierer einen Fluchtweg für Situationen benötigen, in denen die Verschachtelung von Lebensdauern nicht möglich ist. Bietet in C ++ shared_ptreinen Fluchtweg, kann aber ~ 10x langsamer sein als das Verfolgen der Garbage Collection . Die Verwendung von SBMM anstelle von GC würde die meisten Menschen die meiste Zeit in die Irre führen. Das heißt jedoch nicht, dass es nutzlos ist. SBMM ist im Kontext von Systemen und eingebetteter Programmierung, bei denen die Ressourcen begrenzt sind, immer noch von Wert.

FWIW, vielleicht möchten Sie Forth und Ada besuchen und sich über die Arbeit von Nicolas Wirth informieren.

Jon Harrop
quelle
1
Wenn Sie sagen, welche Teile ich möglicherweise in der Lage bin, Artikel auszuarbeiten oder zu zitieren.
Jon Harrop
2
Wie relevant ist es, in einigen seltenen Anwendungsfällen 10x langsamer zu sein als in allen Anwendungsfällen allgegenwärtig zu sein? C ++ hat unique_ptr und für die meisten Zwecke ist es ausreichend. Wenn Sie RAII angreifen möchten, indem Sie eine Sprache angreifen, versuchen Sie es mit einem jüngeren Geschwister der RAII-Familie, z. B. Rust. Rust macht im Grunde alles richtig, was C ++ falsch gemacht hat, während es die meisten Dinge richtig macht, dass C ++ auch richtig gemacht hat. Weiteres 'Verwenden' führt zu einer sehr begrenzten Anzahl von Anwendungsfällen und ignoriert die Komposition.
user1703394
2
"Wie relevant ist es, in einigen seltenen Anwendungsfällen 10x langsamer zu sein als in allen Anwendungsfällen allgegenwärtig zu sein?" Erstens ist das ein zirkuläres Argument: shared_ptrist in C ++ nur selten, weil es so langsam ist. Zweitens ist das ein Vergleich zwischen Äpfeln und Orangen (wie der Artikel, den ich bereits zitiert habe, gezeigt hat), weil er shared_ptrum ein Vielfaches langsamer ist als ein Produktions-GC. Drittens sind GCs nicht allgegenwärtig und werden in Software wie LMax und der FIX-Engine von Rapid Addition vermieden.
Jon Harrop
1
@ Jon Harrop, Wenn du es nicht getan hast, bitte kläre mich auf. Welches magische Rezept haben Sie über 30 Jahre lang angewendet, um die vorübergehenden Auswirkungen des Einsatzes tiefer Ressourcen zu mildern? Ohne ein solches Zauberrezept könnte ich nach über 30 Jahren nur den Schluss ziehen, dass Sie die falsche Zuordnung zu anderen Ursachen haben müssen.
user1703394
1
@Jon Harrop, shared_ptr ist nicht selten, weil es langsam ist. Es ist selten, weil in einem anständigen System die Notwendigkeit einer gemeinsamen Eigentümerschaft selten ist.
user1703394
4

Wenn Sie sich einen Beliebtheitsindex wie TIOBE ansehen (was natürlich fraglich ist, aber ich denke, für Ihre Art von Frage ist es in Ordnung, dies zu verwenden), sehen Sie zuerst, dass ~ 50% der Top 20 "Skriptsprachen" oder "SQL-Dialekte" sind ", wo" Benutzerfreundlichkeit "und Abstraktionsmittel eine viel größere Bedeutung haben als deterministisches Verhalten. Von den verbleibenden "kompilierten" Sprachen gibt es rund 50% der Sprachen mit SBMM und ~ 50% ohne. Wenn Sie also die Skriptsprachen aus Ihrer Berechnung herausnehmen, würde ich sagen, dass Ihre Annahme einfach falsch ist. Unter den kompilierten Sprachen sind diejenigen mit SBMM so beliebt wie diejenigen ohne.

Doc Brown
quelle
1
Inwiefern unterscheidet sich "Benutzerfreundlichkeit" vom Determinismus? Sollte eine deterministische Sprache nicht als einfacher zu gebrauchen angesehen werden als eine nicht deterministische?
Philipp
2
@Philipp Nur das, was deterministisch ist oder eigentlich nicht zählt. Die Objektlebenszeit spielt für sich genommen keine Rolle (obwohl C ++ und Freunde viele wichtige Dinge mit der Objektlebenszeit verknüpfen, weil sie dies können). Wenn ein nicht erreichbares Objekt freigegeben wird, spielt es keine Rolle mehr, da Sie es definitionsgemäß nicht mehr verwenden.
Darüber hinaus verwenden verschiedene "Skriptsprachen" wie Perl und Python die Referenzzählung als primäres Mittel für die Speicherverwaltung.
Philipp
1
@Philipp Zumindest in der Python-Welt wird dies als Implementierungsdetail von CPython betrachtet, nicht als Eigenschaft der Sprache (und praktisch jede andere Implementierung vermeidet das Nachzählen). Darüber hinaus würde ich argumentieren, dass das allumfassende Referenzzählen ohne Opt-out mit Backup-Zyklus-GC nicht als SBMM oder RAII qualifiziert ist. Tatsächlich wird es Ihnen schwer fallen, RAII-Befürworter zu finden, die diese Art der Speicherverwaltung als mit RAII vergleichbar ansehen (vor allem, weil dies nicht der Fall ist, können Zyklen überall eine sofortige Freigabe an einer anderen Stelle im Programm verhindern).
3

Ein Hauptvorteil eines GC-Systems, den noch niemand erwähnt hat, besteht darin, dass eine Referenz in einem GC-System garantiert ihre Identität behält, solange sie existiert . Wenn Sie ein Objekt IDisposable.Dispose(.NET) oder AutoCloseable.Close(Java) aufrufen, während Kopien der Referenz vorhanden sind, verweisen diese Kopien weiterhin auf dasselbe Objekt. Das Objekt ist für nichts mehr nützlich, aber Versuche, es zu verwenden, haben ein vorhersehbares Verhalten, das vom Objekt selbst gesteuert wird. Im Gegensatz dazu wird in C ++, wenn Code deleteein Objekt aufruft und später versucht, es zu verwenden, der gesamte Status des Systems vollständig undefiniert.

Ein weiterer wichtiger Punkt ist, dass die bereichsbezogene Speicherverwaltung für Objekte mit klar definiertem Besitz sehr gut funktioniert. Es funktioniert viel weniger gut und manchmal sogar schlecht mit Objekten, die keine definierte Eigentümerschaft haben. Im Allgemeinen sollten veränderbare Objekte Eigentümer haben, unveränderliche Objekte müssen dies nicht, aber es gibt eine Falte: Code verwendet häufig eine Instanz eines veränderlichen Typs, um unveränderliche Daten zu speichern, indem sichergestellt wird, dass keine Referenz verfügbar gemacht wird Code, der die Instanz möglicherweise verändert. In einem solchen Szenario können Instanzen der veränderlichen Klasse von mehreren unveränderlichen Objekten gemeinsam genutzt werden und haben daher keine eindeutige Eigentümerschaft.

Superkatze
quelle
4
Die Eigenschaft, auf die Sie sich in der ersten Hälfte beziehen, ist die Speichersicherheit. Während ein GC ein sehr einfacher Weg ist, um Speichersicherheit zu erreichen, ist ein GC nicht erforderlich: Schauen Sie sich Rust an, um ein gelungenes Beispiel zu erhalten.
@delnan: Wenn ich rust-lang.org ansehe, kann mein Browser von dort aus nicht sinnvoll navigieren. Wo soll ich nach weiteren Informationen suchen? Mein Eindruck ist, dass die Speichersicherheit ohne GC bestimmte Einschränkungen für Datenstrukturen mit sich bringt, die möglicherweise nicht zu allen Aufgaben einer Anwendung passen, aber ich würde mich freuen, wenn mir das Gegenteil bewiesen wird.
Superkatze
1
Ich kenne keine einzigen (oder auch nur wenige) guten Referenzen dafür; Mein Wissen über Rust hat sich über ein oder zwei Jahre beim Lesen der Mailingliste angesammelt (und all die Dinge, die in Mails verlinkt sind, einschließlich verschiedener Tutorials, Blog-Posts zum Sprachdesign, Github-Themen, ThisWeekInRust und mehr). Um kurz auf Ihren Eindruck einzugehen: Ja, jedes sichere Konstrukt unterwirft (notwendigerweise) Einschränkungen, aber für praktisch jeden speichersicheren Code existiert ein geeignetes sicheres Konstrukt oder kann geschrieben werden. Die mit Abstand gebräuchlichsten existieren bereits in Sprache und Stdlib, alle anderen können in Benutzercode geschrieben werden.
@delnan: Benötigt Rust ineinandergreifende Aktualisierungen von Referenzzählern oder verfügt es über eine andere Möglichkeit, mit unveränderlichen Objekten (oder unveränderlich verpackten veränderlichen Objekten) umzugehen, die keinen bestimmten Eigentümer haben? Hat Rust ein Konzept von Zeigern, die sowohl Objekte besitzen als auch nicht besitzen? Ich erinnere mich an einen Artikel über "Xonor" -Zeiger, in dem die Idee diskutiert wurde, dass Objekte eine einzige Referenz haben, die sie "besitzt", und andere Referenzen, die dies nicht tun. Wenn die "besitzende" Referenz aus dem Geltungsbereich
gerät
1
Ich denke nicht, dass Stack Exchange-Kommentare das richtige Medium sind, um eine Tour durch eine Sprache zu geben. Wenn Sie immer noch interessiert sind, können Sie direkt zur Quelle (#rust IRC, rust-dev-Mailingliste usw.) gehen und / oder mich mit einem Chatroom ansprechen (Sie sollten in der Lage sein, einen zu erstellen).
-2

Zunächst einmal ist es sehr wichtig zu erkennen, dass RAII mit SBMM gleichgesetzt wird. oder sogar zu SBRM. Eine der wesentlichsten (und am wenigsten bekannten oder am wenigsten geschätzten) Eigenschaften von RAII ist die Tatsache, dass es "eine Ressource sein" zu einer Eigenschaft macht, die für die Zusammensetzung NICHT transitiv ist.

In dem folgenden Blog-Beitrag wird dieser wichtige Aspekt von RAII erörtert und mit der Ressourcenverwaltung in GC-Sprachen, die nicht deterministische GC verwenden, verglichen.

http://minorfs.wordpress.com/2011/04/29/why-garbage-collection-is-anti-productive/

Es ist wichtig zu beachten, dass RAII zwar hauptsächlich in C ++ verwendet wird, Python (zuletzt die nicht auf VM basierende Version) jedoch Destruktoren und deterministische GCs enthält, mit denen RAII zusammen mit GC verwendet werden kann. Das Beste aus beiden Welten, wenn es so wäre.

user1703394
quelle
1
-1 Das ist einer der schlimmsten Artikel, die ich je gelesen habe.
Jon Harrop
1
Das Problem in Sprachen ist nicht, dass sie GC unterstützen, sondern dass sie RAII aufgeben. Es gibt keinen Grund, warum eine Sprache / ein Framework nicht beide unterstützen sollte.
Superkatze
1
@ Jon Harrop, könnten Sie näher darauf eingehen. Gibt es von den Behauptungen, die in dem Artikel gemacht wurden, überhaupt eine der ersten drei Behauptungen, die nicht zutreffen? Ich denke, Sie stimmen dem Produktivitätsanspruch möglicherweise nicht zu, aber die anderen drei Ansprüche sind absolut gültig. Vor allem die erste, die sich mit der Transitivität von Ressourcen befasst.
user1703394
2
@ user1703394: Erstens basiert der gesamte Artikel auf der "GCed-Sprache" eines Strohmanns, obwohl diese überhaupt nichts mit Garbage Collection zu tun hat. Zweitens macht er die Garbage Collection dafür verantwortlich, dass die Fehler in der objektorientierten Programmierung liegen. Schließlich ist sein Argument 10 Jahre zu spät. Die überwiegende Mehrheit der Programmierer hat bereits eine Modernisierung vorgenommen, um die gesammelten Sprachen zu löschen, gerade weil sie eine viel höhere Produktivität bieten.
Jon Harrop
1
Seine konkreten Beispiele (RAM, offene Datei-Handles, Sperren, Threads) sind ziemlich aussagekräftig. Es fällt mir schwer, mich daran zu erinnern, wann ich das letzte Mal Code schreiben musste, der sich direkt mit diesen befasste. Mit RAM automatisiert der GC alles. Mit Dateihandles schreibe ich Code so, als ob File.ReadLines file |> Seq.lengthdie Abstraktionen das Schließen für mich handhaben. Sperren und Threads, die ich durch .NET Taskund F # ersetzt habe MailboxProcessor. Das Ganze "Wir haben den Umfang des manuellen Ressourcenmanagements explodiert" ist einfach völliger Unsinn.
Jon Harrop