garbage collection - Referenzen finden und updaten
-
314159265358979 schrieb:
GCs sind Verschwendung von Resourcen. Die Dinger sind sau ineffizient, verglichen mit Speicherverwaltung über Smartpointer. Wenn sie wenigstens deterministisch wären und sich um Finalization kümmern würden. Aber ney, das soll ja angeblich schlecht für die Performance sein *hust
GCs sind n Witz. Ein Hype, der hoffentlich irgendwann ausstirbt.
Sorry...
package app01; public class App01 { // Disposeable, Deleteable,... aber nein AutoCloseable *sigh* public static class Resource implements AutoCloseable { @Override public void close() throws Exception { System.out.println("Ich räume mich selbst auf"); } } public static void main(String[] args) { try(Resource r = new Resource()) {} catch(Exception e) {} try { Thread.sleep(10000);} catch (InterruptedException ex) {} } }alles entwickelt sich...
-
Shade Of Mine schrieb:
Die müssen jedesmal locken
shared_ptr locken nie, außer wenn atomics nicht zur Verfügung stehen. Aber das muss schon ein sehr exotisches System sein - auf dem wird ein GC nicht mal laufen.
Desweiteren: shared_ptr braucht man so gut wie nie. Meistens reicht ein unique_ptr völlig aus.Shade Of Mine schrieb:
und wenn man Pech hat gibts sogar ne extra Allokation.
Schwachsinn.
Shade Of Mine schrieb:
Ein GC allokiert deutlich schneller als zB operator new es kann.
-> Eigener Allokator, der auf die Anwendung zugeschnitten sein kann.
Shade Of Mine schrieb:
Weiters verhindert ein GC Fragmentierung des Speichers, was nicht nur Allokationen gleich schnell hält (die Performance von new degeneriert mit der Zeit), sondern auch noch die Page Faults reduzieren kann - was effektiv wieder heisst: mehr Speed.
Kann man auch implementieren.
Shade Of Mine schrieb:
Deallokationen sind natürlich um Dimensionen schneller als operator delete.
-> Eigener Allokator, der auf die Anwendung zugeschnitten sein kann.
Shade Of Mine schrieb:
Und hier ist es auch was du verwechselst: deterministische Destruktion ist nichts was mit einem GC in Konkurrenz steht. Du scheinst nur Java zu kennen und schliesst davon auf GCs. Das ist falsch. GCs sind für Speicherreservierung da, nicht für deterministische Destruktion. Dafür hat man andere Mittel.
Falsch. GCs haben sich darum zu kümmern, dass Resourcen weggeräumt werden, bevor das Objekt weggeräumt wird. Java ist nicht die einzige Sprache, das wird fast überall so gehandhabt.
Shade Of Mine schrieb:
Weiters sind GCs kein Hype
Die gibt es schon ewig und wird es immer geben. Weil es für Speicherverwaltung einfach oft ideal ist einen GC zu haben.Praktisch, nicht ideal.
Zeus schrieb:
package app01; public class App01 { // Disposeable, Deleteable,... aber nein AutoCloseable *sigh* public static class Resource implements AutoCloseable { @Override public void close() throws Exception { System.out.println("Ich räume mich selbst auf"); } } public static void main(String[] args) { try(Resource r = new Resource()) {} catch(Exception e) {} try { Thread.sleep(10000);} catch (InterruptedException ex) {} } }alles entwickelt sich...

War ja klar, dass du damit kommst. Dieses try ist auch nur ein in die Sprache gepresstes Feature. Es packt das Problem nicht bei der Wurzel, nämlich dass ich trotzdem explizit etwas tun muss, um pseudoautomatische Freigabe zu kriegen. Bei Membern funktioniert das übrigens auch nicht.
-
314159265358979 schrieb:
Shade Of Mine schrieb:
Die müssen jedesmal locken
shared_ptr locken nie, außer wenn atomics nicht zur Verfügung stehen. Aber das muss schon ein sehr exotisches System sein - auf dem wird ein GC nicht mal laufen.
Desweiteren: shared_ptr braucht man so gut wie nie. Meistens reicht ein unique_ptr völlig aus.Siehe mein Edit.
Und unique_ptr ist natürlich nicht so lahm wie shared_ptr, aber unique_ptr bietet keine Performance Features, wie es ein GC bietet. unqiue_ptr ist nur ein RAII Objekt - das ich in einem GC auch verwenden könnte.Shade Of Mine schrieb:
und wenn man Pech hat gibts sogar ne extra Allokation.
Schwachsinn.
shared_ptr<Foo> p(new Foo());
und schon habe ich ne doppelte Allokation.
Shade Of Mine schrieb:
Ein GC allokiert deutlich schneller als zB operator new es kann.
-> Eigener Allokator, der auf die Anwendung zugeschnitten sein kann.
Riesen Aufwand und generisch nicht immer Möglich. Erhöht die Komplexität und wird deshalb nur in extrem Situationen gemacht und auch dann idR nicht generisch.
So ist die Welt nunmal.
Shade Of Mine schrieb:
Weiters verhindert ein GC Fragmentierung des Speichers, was nicht nur Allokationen gleich schnell hält (die Performance von new degeneriert mit der Zeit), sondern auch noch die Page Faults reduzieren kann - was effektiv wieder heisst: mehr Speed.
Kann man auch implementieren.
Nein. Nicht ohne enorme Probleme. Ein GC kann Objekte verschieben, das kannst du in C++ nicht trivial machen. Die kosten für sowas sind enorm hoch.
Die korrekte C++ Lösung ist also die Fragmentierung nicht aufkommen zu lassen. Was ungleich schwerer ist als automatisch keine Fragmentierung zu haben

Shade Of Mine schrieb:
Deallokationen sind natürlich um Dimensionen schneller als operator delete.
-> Eigener Allokator, der auf die Anwendung zugeschnitten sein kann.
Das ist sehr schwer möglich. Siehe Allokationen. Das delete ist noch schwerer zu optimieren als das new.
Shade Of Mine schrieb:
Und hier ist es auch was du verwechselst: deterministische Destruktion ist nichts was mit einem GC in Konkurrenz steht. Du scheinst nur Java zu kennen und schliesst davon auf GCs. Das ist falsch. GCs sind für Speicherreservierung da, nicht für deterministische Destruktion. Dafür hat man andere Mittel.
Falsch. GCs haben sich darum zu kümmern, dass Resourcen weggeräumt werden, bevor das Objekt weggeräumt wird. Java ist nicht die einzige Sprache, das wird fast überall so gehandhabt.
Nein. Das ist einfach falsch. Der GC verwaltet Speicher und keine Resourcen. Nur weil Java früher keine Trennung hatte zwischen Speicher und Resource ist das kein Problem von einem GC.
Mittlerweile hat auch Java das eingesehen und man hat AutoCloseable. Und schon kann man RAII und GC haben. Cool, nicht?
Shade Of Mine schrieb:
Weiters sind GCs kein Hype
Die gibt es schon ewig und wird es immer geben. Weil es für Speicherverwaltung einfach oft ideal ist einen GC zu haben.Praktisch, nicht ideal.
Ziemlich ideal. Ein GC ist nämlich besser als kein handoptimierter Allokator. Das ist schon ziemlich cool. Genauso wie C++ ziemlich ideal ist was performance betrifft auch wenn ein Guru in ASM schnelleren Code hinbekommen könnte - man macht es nicht. Weil sich der Aufwand nicht dafür steht.
War ja klar, dass du damit kommst. Dieses try ist auch nur ein in die Sprache gepresstes Feature. Es packt das Problem nicht bei der Wurzel, nämlich dass ich trotzdem explizit etwas tun muss, um pseudoautomatische Freigabe zu kriegen. Bei Membern funktioniert das übrigens auch nicht.
Du hast den Code nicht verstanden. Man muss garnichts tun. AutoCloseable schliesst die Resource automatisch. Das ist RAII.
Natürlich ist der Java Ansatz ziemlich dumm, aber das ist halt Java. Das hat nichts mit GC zu tun. Du kannst RAII und GC haben.
Ein GC ist ein Ersatz für new/delete und nicht für CTor/DTor.
GC und RAII funktioniert gemeinsam. RAII ist sau dumm für Memory Management, weil RAII langsam ist. Ein GC kann viele Allokationen/Deallokationen aufeinmal machen. Dafür braucht man in C++ spezielle Allokatoren - was technisch natürlich geht, aber nicht trivial ist. Ein GC kann ohne Probleme warten bis die Anwendung idled und dann allen Speicher freigeben oder vielleicht sogar garnicht freigeben weil er ja Statistiken über die Speichernutzung hat.Das ist mit einem einfachen Allokator nicht trivial möglich.
Lies dich mal in die Materie ein. Es gibt viele Mythen über GCs. Und vergiss Java. Du argumentiert hier als wäre es eine Diskussion C++ vs Java Memory Management. In der Tat ist es aber eine Diskussion manuelles new/delete vs automatisches.
Das Problem von RAII ist, dass RAII davon ausgeht dass die Resource die verwaltet wird wichtig ist. Das ist aber nicht immer der Fall. Im Fall von rohem Speicher ist die Resource nämlich uninteressant - und das ist die Stärke von einem GC.
Schreib einen Allokator der richtig sau schnell ist, sowohl was allokation und deallokation betrifft. Nimm dir tcmalloc/jemalloc als Vorbild und sei schneller.
Das ist ne interessante Aufgabe - und dann verstehst du nachher vielleicht warum ein GC garnicht mal so blöd ist.
-
314159265358979, einfach mal die Fresse halten wenn man ausser dummem und unfundiertem Basching nix zu sagen hat.
-
Shade Of Mine schrieb:
Ein GC kann Objekte verschieben, das kannst du in C++ nicht trivial machen. Die kosten für sowas sind enorm hoch.
Zum Verschieben brauchst du aber doppelte statt einfacher Verweise, die Kosten für sowas sind enorm hoch.
GC und RAII funktioniert gemeinsam.
Wie das? So wie ich GCs kenne, sind sie u.a. gerade deshalb so effizient, weil sie keine Destruktoren aufrufen müssen.
-
fdfdg schrieb:
GC und RAII funktioniert gemeinsam.
Wie das? So wie ich GCs kenne, sind sie u.a. gerade deshalb so effizient, weil sie keine Destruktoren aufrufen müssen.
Na, RAII ruft den Destruktor auf, der GC räumt den Speicher weg.
-
Danke für den link Shade of Mine, war hilfreich.
fdfdg schrieb:
Shade Of Mine schrieb:
Ein GC kann Objekte verschieben, das kannst du in C++ nicht trivial machen. Die kosten für sowas sind enorm hoch.
Zum Verschieben brauchst du aber doppelte statt einfacher Verweise, die Kosten für sowas sind enorm hoch.
Ich glaub das stimmt so nicht zwingend. Ich habe zwar nirgends gefunden wie das konkret gemacht wird, aber so könnte man es ohne doppelte Verweise (jedenfalls während der normalen Ausführung) und O(n) machen:
Bevor man mit mark-and-compact anfängt, erstellt man für jeden n-byte Speicherblock eine Datenstruktur, die alte Adressen auf neue mappen kann.
Man kann zu einer beliebigen Speicheradresse im alten Speicherbereich die address map durch array indexing finden, d.h. in konstanter Zeit. Das macht man einfach, indem man das offset zum Beginn verwendeten Speichers ermittelt und durch n (die Größe eines Speicherblocks) teilt.
Das suchen einer Adresse in einer address map hat wegen dem beschränkten Speicherbereich jeder map einen Maximaldauer, daher ist auch diese Operation O(1). (Welche Datenstruktur man nimmt ist nicht eindeutig, der konstante overhead beim suchen und Speichern spielt hier eine große Rolle)
Wenn man ein Objekt kopiert, speichert man in der zugehörigen address map, wo das Objekt hinkopiert wurde. Nach dem compacting wird dann für jede Referenz auf dem stack und im neuen Speicherbereich in den address maps nach dem neuen Speicherort gesucht.GC und RAII funktioniert gemeinsam.
Wie das? So wie ich GCs kenne, sind sie u.a. gerade deshalb so effizient, weil sie keine Destruktoren aufrufen müssen.
In c++ kann man Objekte auf den stack oder den heap legen. Es wäre genauso gut vorstellbar, Objekte in einer anderen Sprache auf den stack oder in den gc zu legen.
-
Vielleicht ganz interessant: http://herbsutter.com/2011/10/25/garbage-collection-synopsis-and-c/
http://dlang.org/garbage.html (Auch wenn ich die Argumente hier kaum nachvollziehen kann..)Wobei ich mich frage: Was ist an new/delete eigentlich so langsam? Nur der Ring-Übergang?
-
Ethon schrieb:
Na, RAII ruft den Destruktor auf, der GC räumt den Speicher weg.
Und warum erzeugst du eine künstliche Unterscheidung zwischen Speicher und anderen Resourcen? Speicher ist eine Resource.
-
314159265358979 schrieb:
Ethon schrieb:
Na, RAII ruft den Destruktor auf, der GC räumt den Speicher weg.
Und warum erzeugst du eine künstliche Unterscheidung zwischen Speicher und anderen Resourcen? Speicher ist eine Resource.
Man unterscheidet oder vereinheitlich es per Definition, daher kannst du diese Frage gern philisophisch klären...
-
314159265358979 schrieb:
Und warum erzeugst du eine künstliche Unterscheidung zwischen Speicher und anderen Resourcen? Speicher ist eine Resource.
Speicher ist eine Ressource mit besonderen Eigenschaften. Ein Sonderfall. Der GC nutzt dieses zusätzliche Wissen aus.
-
Ethon schrieb:
Na, RAII ruft den Destruktor auf, der GC räumt den Speicher weg.
Dann skizzier das mal. RAII läuft ja in C++ deshalb so gut, weil für Stackobjekte gut Code generiert werden kann, der nach Verlassen des Scopes aufgerufen wird. Der Einsatz eines GC verträgt sich aber nicht gut mit der Nutzung des Stacks, deshalb hast du kein automatisches Aufräumen, ergo musst du herausfinden, welche Objekte nicht mehr gültig sind => Mark-And-Compact verliert seinen Vorteil.
GorbGorb schrieb:
fdfdg schrieb:
Zum Verschieben brauchst du aber doppelte statt einfacher Verweise, die Kosten für sowas sind enorm hoch.
Ich glaub das stimmt so nicht zwingend. Ich habe zwar nirgends gefunden wie das konkret gemacht wird, aber so könnte man es ohne doppelte Verweise (jedenfalls während der normalen Ausführung) und O(n) machen
Klar kann man das machen, aber praktikabel ist das nicht, darum geht's ja.
Dieses blinde Argumentieren Pro-GC ist genauso dumm wie strikt dagegen zu reden. Einen GC einzusetzen bedeutet eben auch, entsprechende, teils starke, Nachteile hinzunehmen. Damit ist ein GC je nach Einsatzzweck "besser" oder "schlechter". Das wissen auch Shade und hustbaer, die müssen aber nunmal PI widersprechen.
-
java ist unter anderem wegen dem gc so elendig langsam
-
fdfdg schrieb:
Ethon schrieb:
Na, RAII ruft den Destruktor auf, der GC räumt den Speicher weg.
Dann skizzier das mal. RAII läuft ja in C++ deshalb so gut, weil für Stackobjekte gut Code generiert werden kann, der nach Verlassen des Scopes aufgerufen wird.
In C++ gibt es nur "automatic storage duration", wo der Speicher für solche Objekte herkommt ist nicht festgeschrieben. Natürlich ist der Standard dahingehend ausgelegt, Compilern zu ermöglichen den Stack dafür zu nutzen. Der Compiler könnte den Speicher aber genau so gut vom GC holen wenn er meint dass das schlau ist.
Der Destruktor kann ja nach wie vor deterministisch beim Verlassen des Scope aufgerufen werden.Der Einsatz eines GC verträgt sich aber nicht gut mit der Nutzung des Stacks, deshalb hast du kein automatisches Aufräumen, ergo musst du herausfinden, welche Objekte nicht mehr gültig sind => Mark-And-Compact verliert seinen Vorteil.
z.T. Stack siehe oben.
Ansonsten...
In Sprachen ohne GC musst du dafür sorgen, dass keine Referenzen auf Objekte die es nicht mehr gibt verwendet werden. Wenn man dabei einen Fehler macht, gibt es undefined behaviour. Das gilt für "non-RAII" Objekte und natürlich genau so für RAII-Objekte.Wenn du ein Programm nimmst das ohne GC korrekt funktioniert, und RAII einsetzt, dann wird es auch mit GC korrekt funktionieren und RAII einsetzen.
Der einzige Unterschied ist dann, dass der Speicher der RAII Objekte erst später freigegeben wird. Der Aufruf des Destruktors bleibt dort wo er immer war. Man bekommt sogar noch einen Vorteil: die Referenzen (die man nicht haben/verwenden sollte) auf "freigegebene" Objekte zeigen jetzt nicht mehr ins Nirvana, sondern auf Objekte der jeweiligen Klasse die sich in einem (u.U. wohldefinierten) Zombie-Zustand befinden.
Statt undefined behaviour kann man dann kontrolliert das Programm abbrechen (oder eine Exception werfen oder was auch immer).Probleme ergeben sich erst, wenn man vom GC jetzt zusätzlich noch fordert dass er deterministisch und ohne Verzögerung das "end of life" von Objekten mit shared ownership erkennt. Damit eben alle Objekte die Resourcen besitzen automatisch, deterministisch und sofort freigegeben werden können (wie man es z.B. von shared_ptr haben kann so lange man keine Zyklen baut). Das ist aber etwas was ein GC noch nie konnte, und was man auch vorher ohne GC nicht konnte. Die Forderung ist also unvernünftig.
(Bzw. gleich gut wie shared_ptr kann es der GC natürlich können, Python macht das z.B. so (bzw. hat es zumindest in einigen Versionen so gemacht))Ansonsten... das ursprüngliche Thema war dass das ewige try-finally schreiben nervt, und das manuelle Aufrufen des Finalizers für Member nervt. Was jetzt mit dem GC wenig (nix) zu tun hat, sondern nur damit ob die Sprache die man verwendet eine Syntax dafür anbietet.
z.B. in C++/CLI muss ich weder try-finally verwenden um Objekte mit "automatic storage duration" zu finalisieren, noch muss ich den Finalizer für Basisklassen oder Member manuell aufrufen.
Wo ist jetzt also das Problem?
fdfdg schrieb:
Dieses blinde Argumentieren Pro-GC ist genauso dumm wie strikt dagegen zu reden. Einen GC einzusetzen bedeutet eben auch, entsprechende, teils starke, Nachteile hinzunehmen. Damit ist ein GC je nach Einsatzzweck "besser" oder "schlechter". Das wissen auch Shade und hustbaer, die müssen aber nunmal PI widersprechen.
Ich argumentiere nicht "blind für GC".
Wenn du wirklich meinst dass die Beiträge von Pi in dieser Diskussion irgend einen Wert haben, oder dass er nicht "strikt dagegen redet"...