RAII vs. Garbage Collector
Kürzlich sah ich einen tollen Vortrag von Herb Sutter über "Leck-Freien C++ - ..." bei CppCon 2016, wo er Sprach über die Verwendung von smart-Pointer implementieren RAII (Resource acquisition is initialization) - Konzepte und wie Sie zu lösen die meisten memory leaks Probleme.
Nun war ich gefragt. Wenn ich strikt RAII Regeln, das scheint eine gute Sache, warum sollte das anders sein von einem garbage collector in C++? Ich weiß, dass mit RAII ist der Programmierer die volle Kontrolle, wenn die Ressourcen werden freigegeben, wieder, aber, dass in jedem Fall von Vorteil nur mit einem garbage-collector? Wäre es wirklich weniger leistungsfähig? Ich habe sogar gehört, dass ein garbage collector effizienter sein kann, als es können Sie kostenlos mehr Speicher zu einer Zeit statt, die Befreiung der kleinen Speicher-Stücke, die alle über den code.
- Deterministic resource management ist entscheidend, in allen möglichen Fällen, vor allem, wenn Sie den Umgang mit unmanaged Ressourcen (z.B. Datei-handles, Datenbanken, etc.). Außer, dass die garbage collection immer eine Art von overhead, in der Erwägung, dass RAII hat nicht mehr Aufwand als das schreiben den code korrekt in den ersten Platz. "Befreiung der kleinen Speicher-Stücke, die alle über den code" ist in der Regel Recht ein bisschen mehr effizient, weil es viel weniger störend für die Ausführung der Anwendung.
- "warum sollte das anders sein von einem garbage collector in C++?" Ich erinnere mich Stroustrup sagt in einem seiner Vorträge (nicht wörtlich): "C++ ist Müll gesammelt, denn es hat RAII".
- Während der "Befreiung der kleinen Speicher-Stücke, die alle über den code" könnte effizienter sein, die aus einer runtime-perfomance Sicht, es ist nicht von einer Speicher-Effizienz-Perspektive, wie es führt zu mehr Fragmentierung.
- Es gibt viele Speicher-management-Strategien, die Fragmentierung zu verringern, ohne dass die garbage collection.
- Stark Verwandte Frage: stackoverflow.com/questions/147130/...
- Hinweis: Sie sprechen über Ressourcen, aber es gibt mehr als eine Art von Ressource. Ein garbage collector aufgerufen wird, wenn es Zeit ist, geben Sie Speicherplatz frei, aber es wird nicht aufgerufen, wenn es Zeit, schließen Sie die Dateien.
- alles ist besser als garbage collection
- RAII kann helfen, vermeiden Sie Leckagen, aber es nicht in sich selbst zu verhindern, verwenden-nach-frei, was ein GC in der Regel können.
- RAII ist aufgrund seiner deterministischen Natur ermöglicht die Verwendung von Destruktoren, OTOH mit GC-es gibt keinen Punkt in es, da Sie nicht wissen, Wann es passiert
- Herb Sutter ' s eigene
deferred_ptr
intelligente Zeiger verwendet, um bieten eine bequeme Schnittstelle, um die garbage collection. Es heißtdeferred_ptr
weil Destruktoren aufgeschoben werden, bis Sie Sie bitten, für eine Sammlung (so Sie noch vorkommen, in einer vorhersehbaren Zeit). - eine GC kann tatsächlich effizienter sein. Anstelle sich kostenlos die kleine von Speicher und auf der Suche nach dem kleinen bits zugeordnet werden kann, wieder (Fragmentierung des Speichers) eine GC können einfach kopieren Sie die wenigen live-Objekte und markieren Sie eine riesige Menge an Speicher so frei auf einmal. Da die meisten der Zeiten, die Programme machen viele kleine, kurzlebige Objekte, dies kann sehr nützlich sein.
- Sie können immer noch Destruktoren mit einem GC. In der Tat, C# hat Sie. Aber Sie sind eigentlich nur eine Notlösung im Falle einer Ressource wurde nicht ordnungsgemäß geschlossen. Als Sie sagte, es ist nicht deterministisch, aber das macht es nicht sinnlos.
- Java-Programme sicher zuordnen, viele kleine, kurzlebige Objekte (einfach weil es schwer ist tun viel von nichts in Java ohne Zuweisung), aber ich bin nicht sicher, dass C++ - Programme tun das gleiche, denn C++ ermöglicht die Zuordnung auf dem stack statt (einschließlich
boost::container::small_vector
und ähnliches). Ich wäre daran interessiert zu sehen eine Quelle, wenn Sie eine haben. - Wenn Sie voll sind verpflichtet, RAII und Verwendung der smart-Pointer überall, Sie sollten nicht use-after-free-Fehler. Aber auch wenn die GC (oder ref gezählt smart Pointer) würde gespeichert haben, werden Sie von der use-after-free-Fehler, könnte es sein, Maskierung ein Szenario, wo hast du unwissentlich gehalten Verweise auf Ressourcen länger, als Sie erwartet.
- Ich nenne BS auf dieser wie ich schon zu jemand anderes, der sagte das gleiche, was ich entlarvt auf CS.SE. Wenn Sie ein legit Beispiel um das zu beweisen, zeigen Sie es. So weit wie ich kann sagen, es ist ein urbaner Mythos, der will einfach nicht sterben, weil die Menschen so verzweifelt, dass es um wahr zu sein.
- Unterschiedliche Bedeutung von Effizienz. Ich habe ausdrücklich argumentiert, dass das RAII-Stil ist "weniger störenden Einfluss auf die Ausführung der Anwendung". Die Garbage collection hat, um das Programm zu stoppen die Ausführung, um zu tun, alle, die Kennzeichnung und die Befreiung auf einmal, was sehr störend. Nun, ich denke, man kann behaupten, dass die gleichzeitige garbage-Kollektoren existieren, aber Sie sind nicht sehr beliebt in der wildnis, und auch deutlich begrenzen den Durchsatz. Ich bin offen für eigentlich jemand zeigt einen benchmark, der zeigt, dass deterministische Zerstörung ist weniger effizient als die GC, aber ich habe noch keine gesehen. Ganz im Gegenteil. @patrick
- Haben Sie nicht nur Probleme wie this in den Sprachen, die zur Umsetzung von RAII, ohne garbage collection. Natürlich gibt es workarounds, so gibt es für jedes problem, aber du gehst zu haben eine harte Zeit zu sagen, dass Kerl, der die garbage collection erhöht die Effizienz seiner app. Oder von seinem workflow, zu umgehen, indem man im Grunde nicht, Objekte zu erstellen. Und dieser Mythos über GC, der einzige Weg, um eine Zersplitterung zu vermeiden, muss sterben, auch. Schreiben Sie eine bessere Speicher-manager.
- Nicht helfen, Kniebeugen mit nicht-besitzen-Referenzen.
- Es ist ziemlich einfach zu zeigen, dass die GCs haben einen höheren Durchsatz als
malloc
/free
; reserviert ein paar Dinge über den Haufen und werfen Sie Weg. Was schwerer ist, zeigt ein Fall, wo, was zählt. GC würd Sprachen neigen zu reservieren pathologisch große Mengen von Zeug, das ist, warum Sie benötigen einen GC, aber lower-level-Sprachen nicht, so dass der Durchsatz der Zuweisungen spielt keine Rolle, dass viel zu Ihnen. - Äh, Nein. Wenn Sie nur alloc/dealloc einen Haufen, dann alles, was Sie zu Messen ist, wie gut die heap-Implementierung ist im Umgang mit einem unrealistisch dumm Zuweisung Muster, nicht alles angibt, ist der eigentliche Unterschied zwischen Hand - & GC. Aber ich habe gerade versucht auf meinem computer (Windows) und MSVC nahm 1328 und 675 ms zu tun allocate und deallocate, VC# 2375 nahm und 0 ms, & GCC nahm 2421 und 2032 ms. Wenn wir könnten, ziehen Sie eine Schlussfolgerung aus dieser, Sie wäre immer noch falsch. Also, nochmals: zeigen Sie mir code (nicht Englisch), dass legitly (d.h. wissenschaftlich) zeigt die stärkste Forderung, die Sie machen können.
- gist.github.com/Veedrac/a3f0d6a0b1a2a64a28ac6da4637ae59d
- über use-after-free: ein GC-trades einen sofortigen Absturz in debug-Modus für eine automatische memory-leak-und phantom-Objekte. Ein symptom kann besser sein, als der andere, je nach Fall, aber immer noch der bug nicht gelöst wird.
- Use-after-frees brechen, Ihren code, wenn Sie zerstören ein Objekt vor dem letzten Einsatz. Lecks verursachen transiente Ineffizienz, wenn Sie zerstören ein Objekt zu lange nach der letzten Verwendung. Diese disjunkt sind Probleme, und beheben nicht vorstellen das andere. Beachten Sie auch, dass RAII nicht wirklich beheben von Lecks, es ist nur räumt Sie ein wenig auf. Wenn ein GC ist das festhalten an einem Objekt zu lange, andere als nur die GC Sammlung Latenz, die äquivalente besitzen RAII-pointer wäre auch noch festzuhalten, dass-Objekt. Auch beachten Sie, dass use-after-frees in C++ nur selten verursachen reinigen stürzt ab, auch im debug.
- Dass unklar war, mein schlechtes. Ich spreche über die Art von Fehlern, wo Ein Objekt verweist, und verwenden Sie ein Objekt B, wenn es sollte nicht mehr, denn B ist, soll tot sein. RAII (oder manuelle Verwaltung für diese Angelegenheit) töten B und hinterlassen Einen mit eindeutig ungültigen Zeiger, dass kann erkannt werden auf den nächsten Einsatz (entweder per segfault, oder mit tools wie valgrind). Eine GC wird halten B lebendig so lang wie Ein Leben. In den meisten Fällen das einzige symptom, abgesehen von dem leak, ist, dass Einer ruft veraltete Daten aus B, die möglicherweise wirklich schwer zu fassen.
- so wählen Sie das symptom, das wird am einfachsten zu Debuggen macht Sinn, aber weder RAII noch ein GC beheben können, die zugrunde liegenden Fehler, der ein Programm Logik-Problem (korrekt meldende Ein, dass B tot sein sollte jetzt).
- Das scheint wie eine wirklich seltsame Art der Betrachtung der Dinge. Etwas soll lebendig sein, wenn du gehst, es zu benutzen, nicht die andere Weise herum. Zwar gibt es auch Ausnahmen, ich würde postulieren, dass Sie sind eine kleine Minderheit.
- Es ist wirklich nur eine andere Art zu formulieren, die Logik, die Sie implementieren müssen. "An diesem Punkt B muss sterben", "Periode" ist eine durchaus vernünftige design-Anforderung. Es kommt die ganze Zeit in der Spiele-Entwicklung: zum Beispiel B ist ein Feind, du hast gerade getötet, und a ist Eine zielsuchende Rakete noch im Flug in Richtung B. Lassen Sie uns sagen, dass Sie vergessen, Benachrichtigen Sie Einen, das ist ein bug. Wenn Sie gewaltsam töten B, das Spiel stürzt auf dem nächsten Bild. Wenn B ist, lebendig gehalten durch eine GC, die Rakete wird jetzt orbit um eine unsichtbare, immaterielle Feind, denn niemand anderes ist, als Eine weiß, dass es tatsächlich noch am Leben.
- Ein (vernünftiges) Spiel nicht das umsetzen dieser Logik mit Destruktoren, weil alles in arrays verweisen auf andere arrays. Zu versuchen, "gewaltsam töten", B durch die memory management einfach nicht skalieren, und auch wenn es möglich war, Sie würde sicherlich nicht geben Ihnen einen sauberen Absturz. Sie haben zu handhaben, das Zeug, wenn man die Iteration durch das array von Raketen, und das bedeutet, dass Sie benötigen, um irgendeine Art getötet Flagge auf
B
, die, wenn Sie nichts bedeutetB
muss noch gültig sein Gedächtnis! - Das ist nur ein Beispiel, wenn eine unmittelbare Objekt der Tod erforderlich ist. Es gibt viele Möglichkeiten, um dies zu implementieren das Verhalten und die direkte Benachrichtigung könnte tatsächlich interessant sein, in einigen Fällen (live-Fahnen und zwei-pass-Zerstörung sind eine weitere beliebte Umsetzung in der Tat). Aber mein Punkt ist: wenn Sie ein Objekt verwenden, nachdem seine vorgesehene Lebensdauer, dann ist entweder die, die Sie geschrieben haben einen Fehler(vergessen, Benachrichtigen Sie Einen), oder du unterschätzt die Lebensdauer-Anforderungen bei der Gestaltung (warten, B Leben müssen, eine weitere Rahmen für alle zu Zeugen dessen Tod). Ein GC nicht lösen, es wird ändern Sie einfach das symptom.
- Oder, am wahrscheinlichsten, die Sie gerade zerstört es noch zu früh. Trivial einfach zu tun, in C++.
- Schwache Zeiger helfen Kniebeuge mit nicht-besitzen-Referenzen.
- Es gibt eine ganze Reihe von Problemen mit der Verwendung von weak-Pointer auf diese Weise. Es ist nicht nur die Gottlosen langsam, aber es ist semantisch völlig falsch. Schwache Zeiger für den Fall bieten, wo die referenzierten Daten verschwindet, machen Sie den Fall behandeln, wo Sie erhalten eine plötzliche use-after-free, aber Sie don ' T stop the use-after-free passiert. (Selbst wenn Sie es täten, C++, verwendet nicht-besitzenden Referenzen viel zu liberal für die, die helfen.) Zu beheben, die in einer RAII-system müssen Sie so etwas wie Rust ' s borrow-checker, aber das kommt mit seinen eigenen Nachteile.
- OK, das ist besser (Java ist 10x "schneller"). Aber nochmal: es ist immer noch schrecklich. Wieder die gleiche Frage: du bist benchmarking der heap-Implementierung, nicht GC vs. manuelle Speicherverwaltung. Zum Beispiel habe ich versucht, läuft Ihr code mit einem einfachen Haufen, die recycelt Knoten anstelle von de-Zuweisung und Neuzuweisung (nur stellen Sie Vektor-und überlast -
Tree::operator {new,delete}
), und Java ging von 10x schneller zu 3x schneller. Ich werde nicht Zeit verschwenden auf den rest der Flaschenhals, aber es ist eindeutig in der Zuweisung Funktionen, die ich optimieren können auch. (Fortsetzung) - Und bevor Sie springen auf mich und sagen, ich habe geschummelt, beachten Sie, dass Java nicht selbst freigeben, überhaupt nichts, also dein Vergleich ist schon unfair, und ich bin nur so dass es noch fair ist. (Ich sage es
System.gc()
bevor die timing-Messungen, aber ich sehe nicht den Speicher an das system zurückgegeben an alle.) Das heißt, auch wenn es zurück die Erinnerung an diesem Punkt, würde dies nur beweisen, Ihre heap-Implementierung ist gut abgestimmt für den Fall, wo die GC nicht selbst ausführen zu müssen. Es kaum so etwas sagt, wie z.B. der Möglichkeit, einen GC zu verbinden deallocations macht es schneller als die manuelle deallocations. - Sie streiten gegen einen Punkt, den ich nicht machen. Da ist nichts unfair, der Vergleich, die ich gemacht, solange Sie nicht davon ausgehen, dass ich etwas gesagt, wusste ich nicht.
- Natürlich ist es unfair. Sie gab mir zwei Programme zu vergleichen, eine, die Speicher freigibt, eine, die nicht. Um einen fairen Vergleich, den Sie ausführen müssen, um eine realistische Arbeitsbelastung, die tatsächlich erfordert die GC, wissen Sie, kick in. Sitzt nicht im Leerlauf. Und Sie brauchen, um eine dynamische und realistische memory allocation Muster, nicht-FIFO-oder LIFO-oder eine Variante davon. Es ist nicht gerade unfassbar zu behaupten, dass ein Programm, das nie freigibt Speicher ist schneller als eine, die funktioniert, oder dass ein Haufen dran LIFO deallocations wird schneller sein als eine, die nicht. Nun, duh, natürlich, es wäre.
- Ihre Kritik keinen Sinn machen. Läuft
System.gc(); System.runFinalization();
dauert fast keine Zeit (offensichtlich, wenn Sie verstehen, wie die GCs Arbeit) und befreit alle junk "Zuweisung". GCs sind auch besser bei der Speicherfreigabe schnell, als Sie auf die Zuteilung von Speicher schnell. Eine "realistische", aber ebenso schwere Allokation-Muster ist nur noch zu verschärfen dieses Problem (auch offensichtlich, wenn Sie verstehen, wie GCs), da verdichten GCs Griff Fragmentierung viel besser alsmalloc
/free
, und Ihre tiered deallocations sind genau für solche workloads. - Du wolltest Beweise für meine Behauptung, und ich zeigte Ihnen Beweise dafür. Sie sind unnötig defensive von Ihren Vorurteilen, anstatt einfach zu akzeptieren diesen Punkt und bewegen Sie sich auf etwas produktiver, wie Sie wissen, ein argument, das zählt.
- nur durch die Annahme von" was ist der Punkt? Der Punkt, dass ein Programm mit einem GC, der nie selbst läuft (was dasselbe ist, ein Programm mit deaktiviertem GC) wird schneller sein als eine, die manuell befreit seine Erinnerung? Ja, es ist. Ein system, das nicht einmal seine Arbeit zu tun, wird schneller sein als eine, die das macht. Definitiv ein sehr gültiges argument, und Sie völlig blies meinen Verstand Recht gibt, indem Sie nur, wie elegant Sie erwies GCs kann schneller sein als die manuelle Speicherverwaltung. Sehr wissenschaftlich, Brillant, realistisch, und alles rund um gut getan, in der Tat.
- Wieder, Sie sind nicht uneins mit dem Punkt, den ich gemacht habe. I war nicht die beabsichtigen, auf weht Ihre Meinung, ich habe nur versucht zu rechtfertigen ist eine ziemlich offensichtliche, unbestrittene Behauptung, dass im Prinzip alle damit einverstanden sind. Warum ist das ein problem?
- ist das ein problem"? Die Diskussion über GC vs. manuelles memory-management, zeig mir ein Programm, wo die GC nicht noch Speicher frei, und Sie Fragen mich "warum ist das ein problem"? Gut, wenn das ist Ihr Spiel, dann halt die Speicherfreigabe in der manuellen version auch, und dann vergleichen. Warum ist das ein problem?!
- Ich zeigte Ihnen genau, wie stellen Sie sicher, eine GC-routine wird ausgeführt, bevor das Programm endet. (Offensichtlich), macht praktisch null Unterschied zu den Ergebnissen.
- Und ich habe bereits gesagt Sie genau, dass die GC verweigert, um Speicher frei, obwohl ich es lief. Sie wissen einfach nicht, Lesen anscheinend. Aber die Hölle, selbst wenn es das hat es ausgelöst, es wäre befreien Sie alle der Speicher reserviert in 1 gedreht, d.h. die Objekte wold alle sterben werden, in der ersten generation, d.h. der garbage collection ist künstlich auf 100% Wirkungsgrad, also eine völlig unrealistische Szenario, außer vielleicht in Ihrer Welt. Ja, du hast völlig überzeugend.
- Nur, dass Sie nicht verstehen, wie Erinnerung funktioniert. Allocators nicht wieder den Arbeitsspeicher des Systems zu, Sie schicken Sie es zu Ihrer gratis-pools.
malloc
/free
normal arbeiten genau das gleiche. Sie sollten mitRuntime runtime = Runtime.getRuntime(); System.out.println("Used Memory:" + (runtime.totalMemory() - runtime.freeMemory()));
oder somesuch zum Messen der tatsächlichen Speicher zu verwenden. - Nein, es ist eher so, dass Sie nicht verstehen, wie Erinnerung funktioniert.
malloc
/free
definitiv NICHT funktionieren genau das gleiche. Überprüfen Sie Ihre task-manager. Sie machen einen Hölle, sehr viel mehr Arbeit, als nur den Arbeitsspeicher, um das Programm; Sie sind der Rückgabe Sie den freigegebenen Speicher für den ganzen rest des Betriebssystems. Wenn Sie ein äpfel mit äpfeln Vergleich, dass, wie ich oben über den Knoten recycling, erhalten Sie anteilig bessere performance, wie ich schon oben gesagt. Wenn äpfel mit Birnen vergleichen ist, Sie Ihren Weg aber dann weiß ich nicht, was Sie zu sagen. - Ich bin hier fertig.
- Ich sehe nicht, wie man vermute, dass ein use-after-free-bug ist "wahrscheinlich" ein Objekt, das früh starb und nicht eine Referenz gehalten zu lange, da beide Klassen von Fehlern existieren. Aber ich habe meine Stelle, damit ich nicht Durcheinander dieser Kommentar-thread weiter.
- Meine Standard-Annahme ist, dass wenn ich einen Zeiger, es ist, weil ich will, was, die Zeiger, nicht so, dass ich einen Zeiger, weil ich es habe.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Während beide befassen sich mit der Zuteilung, Sie tun das auf ganz verschiedene Weise. Wenn Sie es auf einen Tisch wie in Java, dass fügt seine eigenen overhead, entfernt einige der Determinismus, die aus der resource release process und Griffe Zirkelbezüge.
Können Sie implementieren, GC, obwohl in bestimmten Fällen, mit sehr unterschiedlichen Leistungsmerkmalen. Ich habe implementiert eine einmal für socket schließen verbindungen, die in einem high-performance/high-throughput-server (nur Aufruf der socket schließen API dauerte zu lange geschlafen und der Durchsatz (performance). Diese beteiligten keine Erinnerung, aber Netzwerk-verbindungen und keine zyklische Abhängigkeit handling.
Dieser Determinismus ist eine Funktion, die GC einfach nicht zulassen. Manchmal wollen in der Lage sein zu wissen, dass nach einem gewissen Punkt, eine Säuberungsaktion durchgeführt wurde (das löschen einer temporären Datei, schließen einer Netzwerk-Verbindung, etc).
In solchen Fällen GC nicht schneiden Sie es, das ist der Grund, warum in C# (zum Beispiel) Sie haben die
IDisposable
- Schnittstelle.Sein kann ... hängt von der Implementierung.
shared_ptr
Mittel, eine moderne GC völlig vernichten, es sei denn, Sie tun ein viel der Arbeit ersetzen die Standard-allocation-Strategien. (Das ist die Zeit, die Sie verbringen könnte etwas nützliches tut.) RAII ist nur zu gewinnen, für eine Leistung falls, die Sie tatsächlich schreiben in einer Weise, dass es ein Gewinn.IDisposable
ist .NET, nicht C#java.io.Closeable
- Schnittstelle und die "try-with-resources" - Blöcke), die ist vollständig deterministisch. Also, der Teil der Antwort, der Determinismus eine "Säuberungsaktion" ist falsch.Garbage collection löst bestimmte Klassen von Ressourcen-Probleme, die RAII nicht lösen kann. Im Grunde läuft es auf zirkuläre Abhängigkeiten, in denen Sie nicht identifizieren, der Zyklus, bevor die hand.
Diesem gibt es zwei Vorteile. Erste, es werden bestimmte Arten von Problemen, die RAII nicht lösen kann. Diese sind, meiner Erfahrung nach, selten.
Je größer einer ist, dass es ermöglicht dem Programmierer faul und egal über die memory-Ressource Lebenszeit und bestimmte andere Ressourcen, die Sie nicht Verstand verzögerte Bereinigung auf. Wenn Sie don ' T haben zu kümmern, bestimmte Arten von Problemen, man kann sich kümmern mehr über andere Probleme. Dadurch können Sie konzentrieren sich auf die Teile des Problems sind, das Sie fokussieren möchten.
Der Nachteil ist, dass ohne RAII, Verwaltung von Ressourcen, deren Lebensdauer Sie wollen, eingeschränkt ist schwer. GC-Sprachen, im Grunde reduzieren Sie entweder mit extrem einfachen scope-gebunden Leben oder das verlangen, dass Sie die Ressource-management-manuell, wie in C, mit manuell besagt, dass Sie fertig sind mit einer Ressource. Ihre Objekt-Lebenszeit-system ist stark gebunden an die GC, und funktioniert nicht gut für enge lifetime-management von großen komplexen (noch-Zyklus-freie) Systeme.
Um fair zu sein, Ressource management in C++ wird eine Menge Arbeit zu tun richtig, in so großer komplexe (noch-Zyklus-frei) - Systeme. C# und ähnliche Sprachen es nur einen Hauch härter, im Austausch, den Sie machen, die einfach Fall einfach.
Meisten GC-Implementierungen auch Kräfte, die nicht-Lokalität vollwertige Klassen; erstellen von zusammenhängenden Puffer, Allgemeine Gegenstände, oder Komponieren generell Objekte zu einem größeren Objekt, ist nicht etwas, dass die meisten GC-Implementierungen einfach zu machen. Auf der anderen Seite, C# erlaubt das erstellen von Wert-Typ
struct
s mit etwas eingeschränkten Funktionen. In der aktuellen ära der CPU-Architektur, die cache-Freundlichkeit ist der Schlüssel, und das fehlen von Lokalität GC-Kräfte ist eine schwere Last. Da diese Sprachen haben eine bytecode-runtime für die meisten Teil, in der Theorie der JIT-Umfeld bewegen konnten, Häufig verwendete Daten zusammen, aber mehr als oft nicht, erhalten Sie gerade eine einheitliche performance-Verlust aufgrund der häufigen cache-misses, im Vergleich zu C++.Das Letzte problem mit dem GC ist, dass die Freigabe ist unbestimmt, und kann manchmal dazu führen, dass performance-Probleme. Moderne GCs dies weniger ein problem als es in der Vergangenheit gewesen.
malloc
Implementierungen. Solcher Logik, könnte sich die false-sharing, das ist ein Problem für den Multi-Thread-Umgebung, aber es ist eine andere Geschichte. In C können Sie tun, explizite tricks zur Verbesserung der Lokalität, aber wenn Sie nicht tun, dass ich erwarte, dass GC besser zu sein. Was vermisse ich?malloc
zwingt, nicht-Lokalität, die Sie haben zu kämpfen gegen eher als GC und somit denke ich, dass behaupten in Ihrer Antwort, dass "die Meisten GC-Implementierungen auch Kräfte, die nicht-Lokalität" ist nicht wirklich wahr.struct
D. H. von Wert eingebenstruct
im .Net ist ein Gültiger Datentyp. Und die Palette vonstruct
zugeordnet ist im Speicher als eine kontinuierliche Stück, die alle Felder derstruct
anstatt nur Hinweise.FooWidgetManager
verwaltet IhreFoo
Objekte, ist es sehr wahrscheinlich stores registriertFoo
's in einer unbegrenzt wachsenden Datenstruktur. Solche "registriertFoo
" - Objekt ist außerhalb der Reichweite Ihres GC-Systems, weilFooWidgetManager
's interne Liste oder was auch immer hält eine Referenz darauf. Zur Freigabe dieses Speichers, die Sie benötigen, zu FragenFooWidgetManager
um die Registrierung dieses Objekt. Wenn Sie vergessen, dies ist im wesentlichen "ohne löschen"; nur die Namen haben sich geändert... und die GC kann es nicht reparieren.FooWidgetManager
oder zumindest nicht haben es enthalten Verweise aufFoo
sBeachten Sie, dass RAII ist eine Programmier-Sprache, während GC ist ein Speicher-management-Technik. So vergleichen wir äpfel mit Birnen.
Aber wir können Sie einschränken, RAII, um seine Speicher-management-Aspekte nur und vergleichen, die auf GC-Techniken.
Den wichtigsten Unterschied zwischen der so genannten RAII-basierten Speicher-management-Techniken (was eigentlich bedeutet Referenz zählen, zumindest wenn man bedenkt, memory-Ressourcen und ignorieren die anderen, wie Dateien) und echte garbage collection Techniken ist die Umgang mit kreisförmigen Referenzen (für zyklische Graphen).
Mit reference counting, müssen Sie code, der speziell für Sie (mit schwache Referenzen oder andere Sachen).
In vielen Fällen nützlich (man denke an
std::vector<std::map<std::string,int>>
) das "reference counting" ist implizit (da es nur 0 oder 1) und ist geradezu ausgelassen, aber die contructor-und Destruktor-Funktionen (wesentliche zu RAII) Verhalten sich, als ob es war ein "reference counting" bit (die ist praktisch nicht vorhanden). Instd::shared_ptr
es ist eine echte Referenz-Zähler. Aber der Speicher ist immer noch implizit manuell verwaltet (mitnew
unddelete
ausgelöst, die innerhalb von Konstruktoren und Destruktoren), aber das "implizite"delete
(im Destruktor) gibt die illusion der automatischen Speicherverwaltung. Ruft man jedoch mitnew
unddelete
immer noch passieren (und Sie Kosten Zeit).BTW die GC Umsetzung kann (und oft tut) behandeln zirkularität auf Besondere Weise, aber Sie verlassen, dass die Belastung für die GC (Z. B. Lesen Sie über die Cheney-Algorithmus).
Einige GC-algorithmen (insbesondere generational copying garbage collector) nicht die Mühe, das freigeben von Speicher für einzelnen Objekte, es ist Version en masse nach dem kopieren. In der Praxis der Ocaml-GC (oder die SBCL ein) kann schneller sein als ein echter C++ ist RAII Programmier-Stil (für einige, nicht alle Art von algorithmen).
Einige GC bieten Fertigstellung (meist verwendet, um zu verwalten nicht-Speicher - externen Ressourcen wie Dateien), aber Sie werden nur selten verwenden (da die meisten Werte verbrauchen nur Speicher-Ressourcen). Der Nachteil ist, dass der Finalisierung bietet keine timing-Garantie. Praktisch gesprochen, ein Programm mit der Finalisierung ist mit es als letzten Ausweg (z.B. schließen von Dateien sollte noch geschehen, mehr oder weniger explizit auch außerhalb der Fertigstellung, und auch mit Ihnen).
Kann man noch über Speicher-Lecks mit GC (und auch mit RAII, zumindest wenn es falsch verwendet wird), z.B. bei einem Wert gehalten wird, in einigen Variablen oder irgendein Feld, aber niemals in der Zukunft verwendet werden. Sie passieren einfach weniger oft.
Empfehle ich die Lektüre des garbage collection handbook.
In Ihrem C++ - code, die Sie verwenden könnten Boehm-GC - oder Ravenbrook ' s MPS oder code Ihre eigenen tracing garbage collector. Natürlich mit einem GC ist ein Kompromiss (es gibt einige Unannehmlichkeiten, wie z.B. nicht-Determinismus, Mangel an timing-Garantien, etc...).
Ich glaube nicht, dass RAII ist die ultimative Möglichkeit, den Umgang mit dem Speicher in allen Fällen. In mehreren Gelegenheiten, Codierung, Ihr Programm in einem wirklich und effizient GC-Implementierungen (man denke an Ocaml oder SBCL) kann einfacher sein (zu entwickeln) und schneller (ausführen) als in der Programmierung mit ausgefallenen RAII-Stil in C++17. In anderen Fällen ist es nicht. YMMV.
Als ein Beispiel, wenn Sie den code ein Scheme-interpreter in C++17 mit den ausgefallensten RAII-Stil, würden Sie noch brauchen, um code (oder Verwendung) eines explizite GC drin (da ein Schema heap circularities). Und die meisten beweisassistenten kodiert GC-ed-Sprachen, oft funktionale diejenigen, (die einzige, die ich kenne, die codiert ist in C++ ist Lean) für gute Gründe.
BTW, ich bin daran interessiert, wie ein C++ - 17-Implementierung von Scheme (aber weniger interessiert in der Programmierung selbst), vorzugsweise mit einigen multi-threading-Fähigkeit.
shared_ptr
die nicht über eine explizite Zähler) haben Probleme mit der Handhabung von Szenarien, in denen zirkuläre Referenzen. Wenn Sie sprechen nur über einfache Fälle, in denen eine Ressource verwendet, die nur innerhalb einer einzigen Methode, die Sie nicht brauchen, auchshared_ptr
dies ist aber nur ein sehr begrenzter Teilbereich für die GC-basierten Welt auch verwendet ähnliche Mittel, wie zum Beispiel C#using
oder Javatry-with-resources
. Aber die Reale Welt hat auch kompliziertere Szenarien.unique_ptr
ist ein "intelligenter Zeiger" und eine "Umsetzung des RAII, dass Werke über einzelne Funktion (d.h. stack-basiert) Bereich". C++ - Programmierern im Allgemeinen betrachten es als ein best practice für die Ressourcen, um eine klare und eindeutige Besitzer, und daher verwendenunique_ptr
eher alsshared_ptr
.unique_ptr
, könnten Sie mir sagen, wie es besser ist, alsshared_ptr
im Hinblick auf den Umgang mit komplexen Objekt-Graphen, die umfassen kreisförmige Referenzen? Ich kann definitiv nicht sehen. Nehme an, ich bin der Gestaltung ORM, die Sie unterstützen sollten, bidirektionale Eltern-Kind-Beziehungen. Wie könnte ichunique_ptr
zu lassen, mir die Objekte graph, indem ein Zeiger auf eine Eltern-oder Kind-Objekt?unique_ptr
Griffe Zirkelbezüge? Diese Antwort explizit behauptet, dass "die so genannte RAII-Techniken" "wirklich bedeutet " reference counting". Wir können (und ich) ablehnen, die behaupten -- und deshalb Streit viel von dieser Antwort (sowohl in Bezug auf Genauigkeit und in Bezug auf die Relevanz) -- ohne notwendigerweise die Ablehnung jeder einzelne Anspruch auf diese Antwort. (Übrigens, es gibt real-world-garbage-Kollektoren, die nicht in der Lage zirkuläre Referenzen, entweder.)unique_ptr
als non-reference-counting-Ansatz. Für mich ist es genau das reference counting mit einer zusätzlichen API-erzwungene Einschränkung, die Referenz-Zählung ist immer entweder 1 oder 0, und dies ist der Grund, warum es erbt alle die Grenzen der Verweiszählung.RAII und GC, Probleme zu lösen, die in völlig verschiedene Richtungen. Sie sind völlig Verschieden, trotz allem, was einige sagen würden.
Beide behandeln das Problem, dass die Verwaltung von Ressourcen ist schwer. Die Garbage-Collection löst es, indem Sie es so, dass der Entwickler braucht nicht zu zahlen so viel Aufmerksamkeit auf die Verwaltung dieser Ressourcen. RAII löst es, indem macht es einfacher für Entwickler, um die Aufmerksamkeit auf Ihre Ressourcen-management. Wer sagt, dass Sie das gleiche tun, ist etwas, Sie zu verkaufen.
Wenn Sie einen Blick auf aktuelle trends in Sprachen, die Sie sehen, beide Ansätze die gleiche Sprache, weil, ehrlich gesagt, Sie wirklich brauchen beide Seiten des Puzzles. Sie sehen, viele Sprachen, die verwenden der garbage collection von Arten, so dass Sie nicht haben, um die Aufmerksamkeit auf die meisten Objekte, und diese Sprachen bieten auch RAII-Lösungen (z.B. python ' s
with
Betreiber) für die Zeiten, die Sie wirklich wollen, um Ihnen Aufmerksamkeit zu schenken.shared_ptr
(Wenn ich es machen kann das argument, dass refcounting und GC sind in der gleichen Klasse von Lösungen, weil Sie sind beide entworfen, um zu helfen, die Sie nicht brauchen, um die Aufmerksamkeit auf Lebensdauer)with
- und GC durch eine refcounting-system plus ein garbage collectorIDisposable
undusing
- und GC durch einen generationellen garbage collectorDem Muster zuschneiden in jede Sprache.
Einer der problem-Müll-Sammler ist, dass es schwer ist, vorherzusagen, Programm Leistung.
Mit RAII Sie wissen, dass die genaue Zeit, die Ressourcen gehen aus dem Bereich, den Sie löschen, werden einige Speicher und es wird einige Zeit dauern. Aber wenn Sie nicht ein Meister der garbage collector-Einstellungen Sie können nicht Vorhersagen, Wann die Bereinigung passieren wird.
Zum Beispiel: Reinigung einer Reihe von kleinen Objekten getan werden kann, effektiver mit GC, weil es können Sie kostenfrei auf dem großen Brocken, aber es wird nicht schneller Vorgang, und es ist schwer vorherzusagen, Wann in auftreten und wegen der "großen Batzen cleanup" es wird einige Prozessor-Zeit und kann sich negativ auf Ihre Programm-performance.
Grob gesagt. Das RAII-idiom kann besser für die Latenz und jitter. Ein garbage collector kann besser für das system ist Durchsatz.
"Effizient" ist ein sehr breiter Begriff, der, im Sinne der Entwicklung Bemühungen RAII ist in der Regel weniger effizient als GC, aber in Bezug auf die Leistung von GC ist in der Regel weniger effizient als RAII. Es ist jedoch möglich, contr-Beispiele für beide Fälle. Umgang mit generic GC, wenn Sie sehr klar Ressource (de)Allokation prasselt in verwalteten Sprachen können ziemlich lästig, wie Sie den code mit RAII kann überraschend ineffizient, wenn
shared_ptr
wird für alles verwendet, ohne Grund.Garbage collection und RAII jede Unterstützung ein gemeinsames Konstrukt für die anderen ist Sie nicht wirklich geeignet.
In ein garbage Collection-system, code kann effizient behandeln von Referenzen auf unveränderliche Objekte (wie strings) als Stellvertreter für die darin enthaltenen Daten; Weitergabe um solche Verweise ist fast so Billig wie vorbei "dumm" Zeiger, und ist schneller als eine separate Kopie der Daten für jeden Eigentümer, oder versuchen, die Spur Eigentümer eines freigegebenen Kopie der Daten. Zusätzlich, garbage Collection-Systeme machen es einfach zu erstellen immutable Objekttypen durch das schreiben einer Klasse, die erzeugt ein veränderliches Objekt, füllen es wie gewünscht, und die Bereitstellung von accessor-Methoden, die alle während das unterlassen undicht Verweise auf alles, was möglicherweise mutieren, sobald der Konstruktor abgeschlossen ist. In Fällen, In denen Referenzen auf unveränderliche Objekte müssen allgemein kopiert, aber die Objekte selbst nicht, GC beats RAII Hände nach unten.
Auf der anderen Seite, RAII ist ausgezeichnet im Umgang mit Situationen, in denen ein Objekt erwerben muss exklusive Dienstleistungen von anderen Unternehmen. Während viele GC-Systeme erlauben es, Objekte zu definieren, die "Finalize" - Methoden-und-Anforderung-Benachrichtigung, wenn Sie aufgegeben werden, und solche Methoden können manchmal zu verwalten, Freigabe von externen Dienstleistungen, die nicht mehr benötigt werden, sind Sie selten zuverlässig genug, um einen befriedigenden Weg zur Sicherstellung der rechtzeitigen Veröffentlichung von externen Dienstleistungen. Für das management von nicht-fungibel außerhalb Ressourcen, RAII beats GC Hände nach unten.
Der entscheidende Unterschied zwischen den Fällen, in denen GC gewinnt im Vergleich zu jenen, wo RAII gewinnt, ist, dass GC gut bei der Verwaltung vertretbare Speicher, der freigegeben werden kann auf einer as-needed basis, aber schlecht im Umgang mit nicht-vertretbare Ressourcen. RAII ist gut bei der Handhabung von Objekten mit klaren Eigentum, aber schlecht im Umgang herrenlos unveränderliche Daten-Inhaber, die haben keine wirkliche Identität, abgesehen von den Daten, die Sie enthalten.
Weil weder GC noch RAII behandelt alle Szenarien gut, es wäre hilfreich für Sprachen, um eine gute Unterstützung für beide von Ihnen. Leider Sprachen die sich auf eine tendenziell behandeln die anderen wie ein nachträglicher Einfall.
Den wichtigsten Teil der Frage über, ob der eine oder der andere "Vorteil" oder eher "effizient" kann nicht beantwortet werden ohne viel Kontext und dem Streit über die Definition dieser Begriffe.
darüber Hinaus können Sie im Grunde fühlen Sie die Spannung der alten "Ist Java oder C++ ist die bessere Sprache?" flamewar Knistern in den Kommentaren. Ich Frage mich, was eine "akzeptable" Antwort auf diese Frage Aussehen könnte, und bin neugierig zu sehen, es schließlich.
Aber ein Punkt, über eine eventuell wichtige konzeptionelle Unterschied wurde noch nicht darauf hingewiesen: Mit RAII, Sie sind gebunden an den thread, der ruft den Destruktor. Wenn Ihre Anwendung ist single-threaded (und das obwohl er Herb Sutter, der erklärte, dass Das Kostenlose Mittagessen Ist Vorbei: die Meiste software heute noch effektiv ist single-threaded), dann ist ein single-core ist möglicherweise beschäftigt mit der Verarbeitung der Löschungen von Objekten, die nicht mehr relevant für das eigentliche Programm...
Im Gegensatz zu, dass der garbage collector läuft in einem eigenen thread oder sogar mehrere threads, und ist somit (teilweise) entkoppelt von der Ausführung der anderen Teile.
(Hinweis: Einige Antworten bereits versucht, darauf hinzuweisen, Anwendung Muster mit verschiedenen Eigenschaften, die erwähnt Effizienz, Leistung, Latenz und Durchsatz - aber in diesem spezifischen Punkt wurde noch nicht erwähnt)
RAII gleichmäßig beschäftigt sich mit allem, was ist, beschreiben, wie eine Ressource. Dynamische Zuweisungen sind eine solche Ressource, aber Sie sind keineswegs die einzige, und wohl nicht das wichtigste. Dateien, sockets, Datenbankverbindungen, gui-feedback und mehr sind alles Dinge, die verwaltet werden können deterministisch mit RAII.
GCs nur mit dynamischen Zuweisungen, die Entlastung der Programmierer, der sich Gedanken über das Gesamtvolumen der zugeteilten Objekten über die Lebensdauer des Programms (Sie haben nur, um sich über die peak concurrent-Zuweisung Lautstärke Anpassung)
RAII und garbage collection sind vorgesehen, um verschiedene Probleme zu lösen.
Wenn Sie RAII Sie lassen ein Objekt auf dem stack, der alleiniger Zweck es ist, zu bereinigen, was auch immer es ist, Sie wollen verwaltet (sockets, Speicher, Dateien, etc.) beim verlassen des scope der Methode. Dies ist für exception-Sicherheit, nicht nur die garbage collection, die ist, warum Sie bekommen Antworten zu schließen Steckdosen und freigeben von mutexes und dergleichen. (Okay, damit niemand erwähnt Mutexe neben mir.) Wenn eine Ausnahme geworfen wird, wird der stack-unwinding natürlich bereinigt die Ressourcen, die durch eine Methode.
Garbage collection ist die programmgesteuerte Verwaltung von Speicher, allerdings könnte man "Müll-sammeln" anderen knappen Ressourcen, wenn Sie möchten. Ausdrücklich befreien Sie macht mehr Sinn als 99% der Zeit. Der einzige Grund für die Verwendung von RAII für so etwas wie eine Datei oder ein socket ist, Sie erwarten, dass die Verwendung der Ressource beendet, wenn die Methode zurückgibt.
Garbage collection beschäftigt sich auch mit Objekten, die heap-allocated, wenn zum Beispiel eine Fabrik erstellt eine Instanz von einem Objekt und gibt es zurück. Mit persistenten Objekten in Situationen, in denen eine Kontrolle gehen muss ein Bereich ist, was macht die garbage-collection attraktiv. Aber man könnte RAII in der Fabrik, so dass, wenn eine Ausnahme geworfen wird, bevor Sie zurück, Sie müssen nicht Leck Ressourcen.
Ist das perfekt machbar - und in der Tat, ist tatsächlich geschehen - mit RAII (oder mit einfachen malloc/free). Sie sehen, Sie müssen nicht unbedingt immer die Standard-Zuweisung, die plant nur Stückwerk. In bestimmten Kontexten, die Sie verwenden benutzerdefinierte allocators mit verschiedenen Arten von Funktionalität. Einige allocators haben die eingebaute Fähigkeit, die Befreiung alles in einigen Zuweisung region, alle auf einmal, ohne zu iterieren einzelnen zugewiesenen Elemente.
Natürlich bekommen Sie dann in der Frage, Wann deallocate-alles - ob die Verwendung dieser allocators (oder die Tafel der Erinnerung, mit der Sie verbunden werden RAIIed oder nicht, und wie.