garbage collection - Referenzen finden und updaten



  • Hallo,
    wie genau funktioniert denn bei den üblichen VMs (JVM, .Net, ...) das finden und updaten der Referenzen auf ein Objekt? Wenn eine garbage collection ansteht, wie findet man die Startreferenzen? Registrieren sich die Referenzen irgendwo solange sie leben, oder scannt man den stack? (wie genau das? nimmt man einfach an, dass alles auf dem stack eine Referenz sein könnte?)
    Beim compacten werden ja Objekte im Speicher verschoben. Wie kann man alle Referenzen, die auf ein Objekt zeigen, finden und updaten? Registriert sich auch hier jede Referenz bei dem Objekt? Was ist denn dann so der typische memory overhead pro Objekt und Referenz?
    Ich wäre mit einem link schon zufrieden, ist ja ein sehr komplexes Thema.
    EDIT: falsches Forum, bitter verschieben





  • Wie GCs funktionieren? Gar nicht. Die Technologie an sich ist bescheuert und kaputt.



  • 314159265358979 schrieb:

    Wie GCs funktionieren? Gar nicht. Die Technologie an sich ist bescheuert und kaputt.

    Dem entgegen Tausende von echten Anwendungen xD



  • 314159265358979 schrieb:

    Wie GCs funktionieren? Gar nicht. Die Technologie an sich ist bescheuert und kaputt.

    GCs sind in einigen Situationen deutlich schneller und Leistungsstärker als herkömmliche Speicherverwaltungen.

    In einigen Situationen natürlich auch deutlich langsamer und schwächer - und in vielen Situationen gleich gut.

    Das Problem dass du mit GCs hast, ist vermutlich dass Java kein Resourcen Management hat - das ist aber kein GC Problem sondern ein Java Problem 😉



  • 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.



  • 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

    Ne, da liegst du komplett falsch. shared_ptr sind verdammt langsam. Die müssen jedesmal locken (bzw. meistens kann man es mit atomic Operationen machen - dennoch kann es ein Cache Miss/Page Fault sein) und wenn man Pech hat gibts sogar ne extra Allokation. Ein GC allokiert deutlich schneller als zB operator new es kann. 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.

    Deallokationen sind natürlich um Dimensionen schneller als operator delete.

    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.

    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.



  • 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


Anmelden zum Antworten