Wann new verwenden und wann nicht?



  • So, hatte eigentlich selbst bei fast 3 GB am Swap kein Problem, weiterzuarbeiten. Musik lief auch weiter. OS X ist halt einfach super 😃

    Edit: Läuft sogar besser, als vorher 😃


  • Mod

    314159265358979 schrieb:

    😃

    Sorry, das war wirklich nicht meine Absicht. Ich hätte damit gerechnet, dass der Stack eher eingeht, als der Heap. Naja, muss ich auch mal eben testen, welche Auswirkung das so auf mein System hat. 😃

    Ich habe es wie gesagt nicht bis zu bitteren Ende getrieben, sondern sobald ich es merkte abgebrochen. Ich kann ja mal abschätzen:
    sizeof(string) ist 8 bei mir. Stackgröße ist 163840 kB. Ich mag jetzt nicht in den Assemblercode gucken, was der int macht, aber sagen wir mal nochmal 4 Byte pro Call. Das sind knapp 14 Millionen mögliche Calls. Für jeden Call bekommen wir einen String, der 1 Zeichen länger ist als der vorherige, je nachdem wie genau es läuft bekommen wir auch 2 Strings dieser Länge, aber dafür auch 2 Strings auf dem Stack, das hebt sich ungefähr weg. Jedenfalls sind das nach Gauß rund Calltiefe^2/2 Bytes auf dem Heap. Plus noch ein paar Byte Verwaltungsdaten, die ich mal vernachlässige. Mein Heimrechner ist nicht so üppig ausgestattet, insgesamt gut 13 GB stehen zur Verfügung. Das heißt, nach ein paar hundertausend Calls, geht mir der Heap aus und das Programm würde vermutlich mit einer passenden Exception enden, anstatt mit Stackoverflow. Und es ist auch noch gewaltig Platz nach oben, man bräuchte also wirklich ungewöhnlich viel Arbeitsspeicher um den Stack mit diesem Programm zu knacken.

    314159265358979 schrieb:

    So, hatte eigentlich selbst bei fast 3 GB am Swap kein Problem, weiterzuarbeiten. Musik lief auch weiter. OS X ist halt einfach super 😃

    Bei mir lief auch alles weiter, außer einen kleinen Videoaussetzer als er anfing auszulagern, wodurch ich überhaupt erst das Problem bemerkte. Ärgerlich war, dass das Programm selbst nur sehr träge auf das CTRL+C reagierte (beziehungsweise: Es hat sicherlich sehr prompt reagiert, aber die Millionen Anfragen nach mehr RAM waren schon abgeschickt und wurden noch verarbeitet). Und der Scheduler hat mir dann anscheinend den Flashplayer abgeschossen, als der RAM am Ende war 😡 .



  • Pi: Alle von dir geposteten Programme haben Undefined Behaviour:

    N3290 §1.10.24 schrieb:

    The implementation may assume that any thread will eventually do one of the following:
    — terminate,
    — make a call to a library I/O function,
    — access or modify a volatile object, or
    — perform a synchronization operation or an atomic operation.
    [Note: This is intended to allow compiler transformations such as removal of empty loops, even when termination cannot be proven. — end note]



  • Die Sache mit dem Stack und Heap interessiert mich mehr.

    In .NET (C#) beispielsweise ist es so, dass alle Referenztypen (somit auch Instanzen von Klassen) auf dem Heap angelegt werden und Wertetypen werden auf dem Stack angelegt werden (aus Performancegründen). Die klare Trennung existiert in C++ nicht; ich bin also in der Lage frei zu entscheiden, ob meine Instanz auf dem Stack oder Heap angelegt werden soll. Mal angesehen von der "Schönheit" des Codes – wann weiß ich wann ich Möglichkeit 1 oder Möglichkeit 2 (s. Codebeispiel auf der ersten Seite) bevorzugen soll?



  • inc7 schrieb:

    wann weiß ich wann ich Möglichkeit 1 oder Möglichkeit 2 (s. Codebeispiel auf der ersten Seite) bevorzugen soll?

    Generell ist der Stack vorzuziehen. Grund: die Allokation auf dem Heap kostet Performance, das Speichermanagement muss außerdem selbst gehandelt werden (auch wenn Smartpointer das erleichtern), weil C++ keinen GC hat. Zugriffe über (smart-)Pointer auf Heap-Objekte dauern auch länger als auf Stackobjekte.
    ABER: ein Stack-Objekt hat nur Scope-Lebenszeit, soll heißen, wenn die Funktion oder der Block verlassen wird, wird das Objekt zerstört. Solltest du also Objekte erzeugen, die länger leben sollen als die aktuelle Funktion, kommst du am Heap nicht vorbei.



  • In C# können auch Wertetypen auf dem Heap sein, aber das ist ein Detail. Um die Lage in C++ beurteilen zu können, musst die Frage nach der Lifetime der Objekte stellen. Wenn du mit auto-Semantik leben kannst, dann solltest du sie verwenden. Wenn nicht, kannst du anfangen an new zu denken 😉



  • pumuckl schrieb:

    Zugriffe über (smart-)Pointer auf Heap-Objekte dauern auch länger als auf Stackobjekte.

    Hmmm... Ob man das so als generelle Aussage stehen lassen kann? Was bremst denn da? Cache misses? Wenn es da jetzt nichts anderes gibt, würde ich diese Aussage wieder zurück ziehen und getrennt auf den Cache hinweisen (und dass Cache misses teuer sind).



  • Ich würd sagen: In erster Linie die zusätzliche Indirektion 😉



  • dot schrieb:

    Ich würd sagen: In erster Linie die zusätzliche Indirektion 😉

    Kannst du da näher beschreiben und was genau meinst du mit Indirektion (ich kenne das Wort nicht)?



  • Auf ein Objekt am Heap greifst du über einen Zeiger zu. D.h. du musst zuerst den Wert des Zeigers auslesen um die Adresse für den eigentlichen Zugriff auf das Objekt zu bekommen.
    Auf ein Objekt am Stack kannst du dagegen direkt zugreifen.



  • dot schrieb:

    Auf ein Objekt am Stack kannst du dagegen direkt zugreifen.

    Wie soll das denn gehen? Der Stack liegt doch nicht in der CPU? Ok, wenn man sich erst den Pointer aus dem Stack holen muss um ihn zu dereferenzieren ist das eine Indirektion mehr, aber man kann ja schon öfter davon ausgehen, dass der Zeiger schon in einem Register liegt.



  • Eine x86 CPU hat ein eigenes Register für den Base Pointer auf den aktuellen Stackframe und unterstützt entsprechende Adressberechnungen direkt in Hardware...



  • cooky451 schrieb:

    Ok, wenn man sich erst den Pointer aus dem Stack holen muss um ihn zu dereferenzieren ist das eine Indirektion mehr, aber man kann ja schon öfter davon ausgehen, dass der Zeiger schon in einem Register liegt.

    Jepp. Aber erstens muss er da mal irgendwann reingeladen worden sein, und zweitens gibt es zumindest auf der x86-Architektur nicht so wahnsinnig viele allgemein verwendbare Register.



  • dot schrieb:

    Eine x86 CPU hat ein eigenes Register für den Base Pointer auf den aktuellen Stackframe und unterstützt entsprechende Adressberechnungen direkt in Hardware...

    Ja, aber es ist eben trotzdem eine Dereferenzierung. Die Frage ist (@bashar), ob der andere Zeiger auch schon in einem Register liegt. Ja, er muss mal reingeladen sein, aber wenn man ihn sehr oft hintereinander dereferenziert (-> das was Zeit braucht), liegt er genau so in einem Register. Insofern würde ich sagen, es gibt keine Optimierung die weniger erfolgsversprechend ist, als etwas vom Heap auf den Stack zu legen*. 🤡

    Das was am Stack halt so toll ist, ist, dass Speicheranforderungen nur eine Addition sind.



  • cooky451 schrieb:

    dot schrieb:

    Eine x86 CPU hat ein eigenes Register für den Base Pointer auf den aktuellen Stackframe und unterstützt entsprechende Adressberechnungen direkt in Hardware...

    Ja, aber es ist eben trotzdem eine Dereferenzierung.

    Und wenn du über einen Zeiger zugreifst hast du zwei Dereferenzierungen, außer der Wert des Zeigers liegt schon in einem Register. Und das wird eher nur der Fall sein, wenn du auf den Zeiger ständig (z.B. in einer kleinen Schleife) zugreifst. Ansonsten liegt der Zeiger bestenfalls im Cache...



  • Ich habe heute einige Objekte, die ich vorher mit Komposition benutzt habe, auf dem heap angelegt. So brauche ich in der Klasse nur die Vorwärtsdeklarationen, was zu weniger Abhängigkeiten beim kompilieren führt und die compilezeiten drückt.

    Qt hat durch das parent-Konzept auch das deleten für mich übernommen 🙂

    Es hat mich einfach genervt, dass ich nach einer kleinen Änderung in einem header mehr als 2 Minuten kompilieren muss..

    Natürlich geht das auch mit pimpl, aber das erhöht den implementierungsaufwand mehr und 1 mal new brauch man normalerweise trotzdem.


Anmelden zum Antworten