it-swarm.com.de

Nachteile der bereichsbasierten Speicherverwaltung

Ich mag Scope-based Memory Management (SBMM) oder RAII , wie es in der C++ - Community häufiger (verwirrender?) Bezeichnet wird. Soweit ich weiß, gibt es außer C++ (und C) keine andere gängige Sprache, die SBMM/RAII zu ihrem Hauptspeicherverwaltungsmechanismus macht, und stattdessen bevorzugen sie die Verwendung der Garbage Collection (GC).

Ich finde das seitdem ziemlich verwirrend

  1. SBMM macht Programme deterministischer (Sie können genau erkennen, wann ein Objekt zerstört wird).
  2. in Sprachen, die GC verwenden, müssen Sie häufig eine manuelle Ressourcenverwaltung durchführen (siehe z. B. 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_ptr in C++).

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

38
Paul

Beginnen wir mit der Annahme, dass Speicher bei weitem (Dutzende, Hunderte oder sogar Tausende von Zeiten) häufiger vorkommt als alle anderen Ressourcen zusammen. Jeder einzelnen Variablen, jedem Objekt und jedem einzelnen Objektelement wird Speicher zugewiesen, 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 gezogenen Daten zu speichern. Jeder TCP Stream wird zusammen mit einer unbegrenzten Anzahl temporärer Byte-Strings erstellt, die zum Schreiben in den Stream erstellt wurden. Sind wir hier auf derselben Seite? Großartig.

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

Die Speicherbereinigung 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. geteilte) Eigentumsverhältnisse sind auch weniger verbreitet. Die mentale Belastung wird erheblich reduziert.

Nun nennen Sie einige Nachteile, wenn Sie alle Werte sammeln. Es ist jedoch äußerst schwierig, beide speichersicheren GC nd Werttypen mit RAII in eine Sprache zu integrieren. Vielleicht ist es besser, diese Kompromisse auf andere Weise zu lösen?

Der Verlust des Determinismus stellt sich in der Praxis als nicht so schlimm heraus, da er nur deterministische Objektlebensdauer betrifft. Wie im nächsten Absatz beschrieben, sind die meisten Ressourcen (abgesehen von Speicher, der reichlich vorhanden ist und ziemlich träge recycelt werden kann) nicht an die Objektlebensdauer in diesen Sprachen 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 behandelt, die eine bereichsbezogene Bereinigung durchführt, diese Bereinigung jedoch nicht an die Lebensdauer des Objekts koppelt (daher nicht mit dem GC und der Speichersicherheit interagiert). Dies ist using in C #, with in Python, try- mit Ressourcen in den letzten Java -Versionen.

27
user7043

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

Mit Müll gesammelte Sprachen können nicht verwenden RAII direkt, bieten jedoch häufig Syntax mit einem äquivalenten Effekt. In Java haben wir die Anweisung try-with-ressource

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

dies ruft beim Block-Exit automatisch .close() für die Ressource auf. C # hat die Schnittstelle IDisposable, über die .Dispose() aufgerufen werden kann, wenn eine Anweisung using (...) { ... } hinterlassen wird. Python hat die Anweisung with:

with open(filename) as f:
    ...

das funktioniert in ähnlicher Weise. 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.

14
amon

Meiner Meinung nach ist der überzeugendste Vorteil der Garbage Collection, dass sie Composability ermöglicht. Die Richtigkeit der Speicherverwaltung ist eine lokale Eigenschaft in einer Umgebung, in der Müll gesammelt wird. Sie können jedes Teil isoliert betrachten und feststellen, ob Speicherplatz verloren gehen kann. Kombinieren Sie eine beliebige Anzahl speicherkorrekter Teile und diese bleiben korrekt.

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

Dies hat einen sehr sichtbaren Einfluss auf die Gestaltung von Programmen in den verschiedenen Sprachen. Programme in GC-Sprachen sind in der Regel etwas mehr Suppen von Objekten mit vielen Interaktionen, während in GC-freien Sprachen strukturierte Teile mit streng kontrollierten und begrenzten Interaktionen zwischen ihnen bevorzugt werden.

14
Patrick

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

C++ hat sie erst 40 Jahre nach allen anderen bekommen, und viele kluge Leute haben viel harte Arbeit geleistet, um sie richtig zu machen. Im Gegensatz dazu verfügen viele Skriptsprachen, die von Personen entwickelt und implementiert wurden, die keine Kenntnisse im Entwerfen und Implementieren von Programmiersprachen haben.

7
Jörg W Mittag
  1. SBMM macht Programme deterministischer (Sie können genau erkennen, 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 und daher von Natur aus nicht deterministisch. Das Hinzufügen der Einschränkung, dass ein Destruktor genau am Ende des Bereichs und nicht kurz davor oder kurz danach aufgerufen wird, ist für die überwiegende Mehrheit der Programmierer kein wesentlicher praktischer Vorteil.

  1. in Sprachen, die GC verwenden, müssen Sie häufig eine manuelle Ressourcenverwaltung durchführen (siehe z. B. Schließen von Dateien in Java), was den Zweck von GC teilweise zunichte macht und auch fehleranfällig ist.

Siehe using in C # und use in F #.

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

Mit anderen Worten, Sie könnten den Heap, der eine Allzwecklösung ist, so ändern, dass er nur in einem bestimmten Fall funktioniert, der ernsthaft einschränkt. Das stimmt natürlich, ist aber nutzlos.

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

SBMM schränkt Ihre Möglichkeiten ein:

  1. SBMM erstellt das Aufwärtsproblem mit erstklassigen lexikalischen Verschlüssen, weshalb Verschlüsse in Sprachen wie C # beliebt und einfach zu verwenden sind, in C++ jedoch selten und knifflig. Beachten Sie, dass es einen allgemeinen Trend zur Verwendung funktionaler Konstrukte in der Programmierung gibt.

  2. SBMM erfordert 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. Am Ende schreiben Sie effektiv Ihren eigenen GC.

  4. Die gleichzeitige Programmierung ist schwieriger, da der Kontrollfluss und damit die Objektlebensdauer hier von Natur aus nicht deterministisch sind. Praktische Lösungen in Nachrichtenübermittlungssystemen sind in der Regel das gründliche Kopieren von Nachrichten und die Verwendung übermäßig langer Lebensdauern.

  5. SBMM hält Objekte bis zum Ende ihres Gültigkeitsbereichs im Quellcode am Leben, der häufig länger als erforderlich ist und weitaus länger als erforderlich sein kann. Dies erhöht die Menge an schwimmendem Müll (nicht erreichbare Objekte, die darauf warten, recycelt zu werden). Im Gegensatz dazu werden durch die Verfolgung der Speicherbereinigung Objekte bald nach dem Verschwinden des letzten Verweises auf sie freigegeben, was viel früher sein kann. Siehe Mythen zur Speicherverwaltung: Schnelligkeit .

SBMM ist so einschränkend, dass Programmierer einen Fluchtweg für Situationen benötigen, in denen Lebenszeiten nicht zum Verschachteln gebracht werden können. In C++ ist shared_ptr bietet einen Fluchtweg, aber er kann ~ 10x langsamer sein als die Verfolgung der Speicherbereinigung . Die Verwendung von SBMM anstelle von GC würde die meisten Menschen die meiste Zeit auf die falsche Seite stellen. 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 möchten Sie vielleicht Forth und Ada besuchen und sich über die Arbeit von Nicolas Wirth informieren.

5
Jon Harrop

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 die" Benutzerfreundlichkeit "und die Abstraktionsmittel eine viel größere Bedeutung haben als deterministisches Verhalten. Von den verbleibenden "kompilierten" Sprachen gibt es ungefähr 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 genauso beliebt wie diejenigen ohne.

4
Doc Brown

Ein Hauptvorteil eines GC-Systems, den noch niemand erwähnt hat, ist, dass eine Referenz in einem GC-System behält garantiert ihre Identität, solange sie existiert. Wenn man für ein Objekt IDisposable.Dispose (.NET) oder AutoCloseable.Close (Java) aufruft, 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++ der gesamte Status des Systems völlig undefiniert, wenn Code delete für ein Objekt aufruft und später versucht, es zu verwenden.

Ein weiterer wichtiger Punkt ist, dass die bereichsbereichsbasierte Speicherverwaltung für Objekte mit klar definiertem Besitz sehr gut funktioniert. Es funktioniert viel weniger gut und manchmal geradezu schlecht mit Objekten, die keinen definierten Besitz 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 veränderlicher Typen, um unveränderliche Daten zu speichern, indem sichergestellt wird, dass keine Referenz verfügbar ist Code, der die Instanz mutieren könnte. In einem solchen Szenario können Instanzen der veränderlichen Klasse von mehreren unveränderlichen Objekten gemeinsam genutzt werden und haben daher keinen eindeutigen Besitz.

3
supercat