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
- SBMM macht Programme deterministischer (Sie können genau sagen, wann ein Objekt zerstört wird);
- 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.
- Heap-Speicher kann auch (sehr elegant, imo) bereichsgebunden sein (siehe
std::shared_ptr
in C ++).
Warum wird SBMM nicht häufiger eingesetzt? Was sind ihre Nachteile?
finalize()
Methode eines Objekts vor der Garbage Collection aufgerufen wird. Tatsächlich entsteht dadurch dieselbe Problemklasse, die die Garbage Collection lösen soll.Antworten:
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
using
in C #,with
in Python,try
-mit-Ressourcen in neueren Java-Versionen.quelle
using
Aussagen sind nur lokal möglich. Es ist unmöglich, Ressourcen in Mitgliedsvariablen auf diese Weise zu bereinigen.using
ist ein Witz im Vergleich zu RAII, nur damit Sie es wissen.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
Das ruft
.close()
die Ressource beim Beenden des Blocks automatisch auf. C # hat dieIDisposable
Schnittstelle, die.Dispose()
beim Verlassen einerusing (...) { ... }
Anweisung aufgerufen werden kann . Python hat diewith
Aussage: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.
Ich denke, Node.js verwendet die gleiche Strategie.
quelle
with-open-filehandle
Funktionen die Datei öffnen, an eine Funktion übergeben und bei Rückkehr der Funktion die Datei wieder schließen.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.
quelle
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.
quelle
[&]
Syntax absichtlich . Jeder C ++ - Programmierer verknüpft das&
Zeichen bereits mit Referenzen und kennt veraltete Referenzen.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.
Siehe
using
in C # unduse
in F #.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.
SBMM schränkt Ihre Möglichkeiten ein:
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.
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.
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.
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.
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_ptr
einen 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.
quelle
shared_ptr
ist 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 ershared_ptr
um 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.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.
quelle
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) oderAutoCloseable.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 Codedelete
ein 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.
quelle
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.
quelle
File.ReadLines file |> Seq.length
die Abstraktionen das Schließen für mich handhaben. Sperren und Threads, die ich durch .NETTask
und F # ersetzt habeMailboxProcessor
. Das Ganze "Wir haben den Umfang des manuellen Ressourcenmanagements explodiert" ist einfach völliger Unsinn.