New und Delete



  • Danke schon mal das klärt schon mal gut auf.

    Das Argument was hier gebracht wird ist einfach das es schneller ist. Für mich klingt das aber hier in diesem, ich sag mal großem Projekt, das da echt hart Fehler drin sein können. Vor allem das ich mir das alles selber bei bringen muss. So richtig "gelehrt" werde ich nicht.
    Aber gut es ist ein großes Projekt und da muss alles schnell und fix sein.

    Edit:
    @SeppJ

    Das ist ganz einfach: Nutze es nicht!

    Ja in privaten Projekten mach ich das auch. 😉 Aber hier muss ich ja ... 😕


  • Mod

    Es ist langsamer oder gleich schnell, niemals schneller. Besonders gegen automatische Speicherverwaltung (in meiner Übersetzungstabelle "Kleine, statische Arrays" und "Nicht-Arrays") ist der Geschwindigkeitsverlust nicht mehr lustig.

    Ja in privaten Projekten mach ich das auch. 😉 Aber hier muss ich ja ... 😕

    Aber was möchtest du dann wissen? Die richtige Benutzung ist, es nicht zu benutzen. Wenn du es benutzen musst, dann kannst du es nicht richtig benutzen.



  • SeppJ schrieb:

    Es ist langsamer oder gleich schnell, niemals schneller. Besonders gegen automatische Speicherverwaltung (in meiner Übersetzungstabelle "Kleine, statische Arrays" und "Nicht-Arrays") ist der Geschwindigkeitsverlust nicht mehr lustig.

    D.h. der erzählt mir hier scheiße?? 😮
    Oh man ...
    Also noch mal für mich. New und delete ist sogar langsamer? Ahhhhhh 😡



  • Fuchs aus dem Wald schrieb:

    Das Argument was hier gebracht wird ist einfach das es schneller ist.

    Wäre ein Argument, wenns stimmen würde. Tut es aber nicht.

    {
      // ... zum ersten
      std::unique_ptr<irgendwas> u(new irgendwas);
      // ... zum zweiten
    }
    

    wird von jedem Compiler geinlined zu

    {
      // ... zum ersten
      irgendwas *u;
      try {
        u = new irgendwas;
        // ... zum zweiten
      } catch (...) {
        delete u;
        throw;
      }
      delete u;
    }
    

    Nein warte, es ist sogar schneller weil Stack Unwinding und Exceptions speziell optimiert werden kann.

    Das einzige, was bei SeppJ eventuell langsamer ist, ist std::vector . Aber auch hier gibt es Abhilfe:

    irgendwas *foo = new irgendwas[variable_zahl]; // 3 Pointer Verwaltungsdaten
    

    wird zu

    std::unique_ptr<irgendwas[]> foo(new irgendwas[variable_zahl]); // 1 Pointer Verwaltungsdaten
    

    Es gab bei mir aber noch einen Grund, nicht vector zu nehmen.



  • Ahhhhhh schrieb:

    Das einzige, was bei SeppJ eventuell langsamer ist, ist std::vector . Aber auch hier gibt es Abhilfe:

    irgendwas *foo = new irgendwas[variable_zahl]; // 3 Pointer Verwaltungsdaten
    

    wird zu

    std::unique_ptr<irgendwas[]> foo(new irgendwas[variable_zahl]); // 1 Pointer Verwaltungsdaten
    

    Es gab bei mir aber noch einen Grund, nicht vector zu nehmen.

    Das versteh ich nicht. Da ist ja immer noch ein new drin?


  • Mod

    Das einzige, was bei SeppJ eventuell langsamer ist, ist std::vector

    Nun komm, 3 Pointer vs. 1 Pointer + Größe: Das ist so was von Pillepalle.

    Dafür kann man den vector in O(1) wachsen lassen, was bei new nur in O(N) geht. Wenn man es natürlich nicht benötigt, dann kann der vector vereinzelt(!) im Programm eine Subtraktion benötigen, wo new+size nur einen Zugriff benötigt. Also braucht das Gesamtprogramm eventuell 10-20 Prozessortakte mehr.

    Dein Vorschlag ist zwar technisch richtig, würde ich aber trotzdem nicht machen. Auf diesem Pfad stößt man nämlich bald auf die Performancevoodookulte, deren Anhänger Enno in seiner Firma angetroffen hat.



  • Vor allem das ich mir das alles selber bei bringen muss. So richtig "gelehrt" werde ich nicht.

    Und den meisten hier hats auch niemand beigebracht. Das Wissen musst du dir selbst aneignen, das Internet hilft und Artikel gibt es genug.



  • Die Geschwindigkeit von den Speicherallokationen ist überhaupt nicht maßgeblich. Das, was da langsam wäre, wäre die Speicherallokation selbst. Das ist aber in beiden Fällen gegeben, somit spielt das keine Rolle. Wenn new wirklich performancekritisch eingesetzt wird, sollte man ohnehin einen eigenen Allocator schreiben und der ist mit unique_ptr wieder gut kompatibel.



  • SeppJ schrieb:

    Nun komm, 3 Pointer vs. 1 Pointer + Größe: Das ist so was von Pillepalle.

    Ist so. Aber man kann Ennos Kollegen ohne schlechtes Gewissen sagen, dass RAII nie langsamer ist.

    SeppJ schrieb:

    Dafür kann man den vector in O(1) wachsen lassen, was bei new nur in O(N) geht.

    Viel kann natürlich auf genau auf das Problem zugeschnittene Container herausgeholt werden (ein reserve kann den Speicher bis um Faktor 2 schrumpfen). Oder durch eigene Allocators.



  • @SeppJ:
    Bitte neuen Namen nehmen. 😉
    Ja DEN hab ich hier gefunden. Ist eine Person. 😃

    knivil schrieb:

    Vor allem das ich mir das alles selber bei bringen muss. So richtig "gelehrt" werde ich nicht.

    Und den meisten hier hats auch niemand beigebracht. Das Wissen musst du dir selbst aneignen, das Internet hilft und Artikel gibt es genug.

    Da hast aber wunderbar nur das gelesen was du wolltest. Ich mach das auch. UUHH! Aber ich mache eine Ausbildung da könnte man meinen man wird in gewissermaßen "gelehrt" und nicht nur gesagt: geht so mach so!

    Eisflamme schrieb:

    Die Geschwindigkeit von den Speicherallokationen ist überhaupt nicht maßgeblich. Das, was da langsam wäre, wäre die Speicherallokation selbst. Das ist aber in beiden Fällen gegeben, somit spielt das keine Rolle. Wenn new wirklich performancekritisch eingesetzt wird, sollte man ohnehin einen eigenen Allocator schreiben und der ist mit unique_ptr wieder gut kompatibel.

    Danke das klärt mich weiter auf.

    Allgemein danke schon mal das ihr mich noch mal genau aufklärt. Muss ich nun hier zwar durch aber ich hab kein bock mich für mein späteres Berufsleben zu versauen.



  • Fuchs aus dem Wald schrieb:

    SeppJ schrieb:

    Es ist langsamer oder gleich schnell, niemals schneller. Besonders gegen automatische Speicherverwaltung (in meiner Übersetzungstabelle "Kleine, statische Arrays" und "Nicht-Arrays") ist der Geschwindigkeitsverlust nicht mehr lustig.

    D.h. der erzählt mir hier scheiße?? 😮
    Oh man ...
    Also noch mal für mich. New und delete ist sogar langsamer? Ahhhhhh 😡

    Schreibe eine eigene einfach(!) verkettet Liste mit eigenem new und delete und eigener Speicherverwaltung (lässt sich auch C-like hübsch machen)). Und dann lasse sie gegen die doppelt(!) verkette std::list<> aus dem C++-Standard antreten. Letztere ist im Nachteil, da sie bei jeder Element-Operation mehr Zeiger aktualisieren muss, als die einfach verkettete Liste.

    Mache bitte einen fairen Benschmark mit vielen (möglichst kleinen) Elementen rein und raus aus der Liste. Und lege auch viele solcher Listen an und lösche sie wieder ... alles was in 'großen' Programmen so passiert.

    Dann wirst Du Dich wundern, um wie viel schneller die std::list<> das gegenüber Deiner selbst gebastelten erledigt. Und ich rede nicht von 5 oder 10% - die std::list<> ist richtig schneller - und die Ursache liegt in der besseren Speicherverwaltung!
    Probiere es aus - nachdem ich das gemacht hatte, musste ich mich auch erst mal setzen!
    (Ausprobiert mit MS Visual C++ und Dinkumware STL)


  • Mod

    Werner Salomon schrieb:

    und die Ursache liegt in der besseren Speicherverwaltung!

    Nanu? Kannst du konkreter werden? Ich hätte eigentlich nicht erwartet, dass man bei einer list viel durch die Implementierung rausholen kann. Arbeitet die deque-artig?



  • Werner Salomon schrieb:

    Dann wirst Du Dich wundern, um wie viel schneller die std::list<> das gegenüber Deiner selbst gebastelten erledigt. Und ich rede nicht von 5 oder 10% - die std::list<> ist richtig schneller - und die Ursache liegt in der besseren Speicherverwaltung!

    Kann ich nicht bestätigen (GCC/libstdc++).

    Meine List: 0.75s auf ideone
    std::list: 0.88s auf ideone



  • Kein unique_ptr? Ich muss doch bitten! 🤡



  • cooky451 schrieb:

    Kein unique_ptr? Ich muss doch bitten! 🤡

    Ich gebe zu, die PODs vom Betriebssystem aufräumen zu lassen war nicht so freundlich.

    Meine List ohne Leaks und UB: 0.75s auf ideone

    Den unique_ptr innerhalb von node einzusetzen kostet leider ein paar Prozessortakte mehr.



  • wtf, ne, so war das jetzt nicht gemeint. Eine node sollte aus T + unique_ptr<node> bestehen, die liste sollte nur nen unique_ptr<node> halten. Damit werden alle Einfüge/Löschoperationen auch nur noch zu Zuweisungen zwischen unique_ptr. Ist die Frage, ob das langsamer ist, denke aber nicht.



  • cooky451 schrieb:

    wtf, ne, so war das jetzt nicht gemeint. Eine node sollte aus T + unique_ptr<node> bestehen, die liste sollte nur nen unique_ptr<node> halten. Damit werden alle Einfüge/Löschoperationen auch nur noch zu Zuweisungen zwischen unique_ptr. Ist die Frage, ob das langsamer ist, denke aber nicht.

    Versuchs doch mal und zeig es mir.

    Mit meiner Implementation geht es nicht, die ist dafür zu trickreich. Die Liste ist als Zyklus implementiert, um die ganzen if-Abfragen zu sparen, die man hätte, wenn das letzte Element auf NULL zeigen würde. Bevor man den Destruktor von unique_ptr ans Werk lassen könnte, müsste man zuerst den Zyklus aufbrechen. Das kostet. Alternativ könnte man was mit speziellen Deletern machen. Aber auch das kostet und wäre umständlicher.


  • Mod

    cooky451 schrieb:

    wtf, ne, so war das jetzt nicht gemeint. Eine node sollte aus T + unique_ptr<node> bestehen, die liste sollte nur nen unique_ptr<node> halten. Damit werden alle Einfüge/Löschoperationen auch nur noch zu Zuweisungen zwischen unique_ptr. Ist die Frage, ob das langsamer ist, denke aber nicht.

    Das halte ich für eine schlechte Idee.
    Abgesehen davon, dass es Abstraktionsebenen durcheinander bringt, wäre ich nicht überrascht, wenn das Löschen einer großen Liste zum Stacküberlauf führen würde.

    node* pointer_to_first;
    
      // das geht gut, solange nie auf data()->data zugegriffen wird.
      node* data() { return reinterpret_cast<node*>(&pointer_to_first); }
    

    Das ist leider (im Allgemeinen) undefiniert (es ist zum Glück nicht schwierig, das standardkonform aufzuschreiben).



  • camper schrieb:

    node* pointer_to_first;
     
      // das geht gut, solange nie auf data()->data zugegriffen wird.
      node* data() { return reinterpret_cast<node*>(&pointer_to_first); }
    

    Das ist leider (im Allgemeinen) undefiniert (es ist zum Glück nicht schwierig, das standardkonform aufzuschreiben).

    Im darauf folgenden Post hatte ich das geändert. Allerdings war noch ein Fehler drin.

    Meine List hoffentlich ohne UB



  • camper schrieb:

    wäre ich nicht überrascht, wenn das Löschen einer großen Liste zum Stacküberlauf führen würde.

    Ich war überrascht, dem GCC hätte ich Tail Rekursion zugetraut.


Anmelden zum Antworten