Programm läuft einfach nicht weiter?



  • Also die genaue Zahl kenn ich ja. Da der Algorithmus beim Referenzdatensatz deterministisch ist, ist der Aussprungpunkt immer derselbe. Ich werde jetzt gleich (sobald mein Rechner wieder hier ist) mal einen Langzeitdebug machen.

    Ich habe nämlich auch noch den Verdacht, dass sich der Rechner einfach tot macht, wenn er versucht, ein Element in eine Liste einzuhängen, das 13,6 Mio. Komponenten unter sich trägt.

    Da müsste ich doch deutlich schneller sein, wenn ich nur eine

    list<Area*>
    

    anstelle einer

    list<Area>
    

    verwalte, weil er ja dann nur die Speicheradresse anstatt des ganzen Elements hängen muss, oder?



  • Soo, ich hab heut Nachmittag mal gut 2h den Debugger laufen lassen. In der Tat war es wirklich so, dass er irgendwann mal fertig wurde.. 🙂

    Nun hab ich list<Area> in list<Area*> umgewandelt, was - wie ich mir bereits dachte - einen erheblichen Performanceschub brachte. Allerdings muss ich jetzt dauernd selbst aufräumen, da ich ja stets mit

    Area* a = new Area();
    

    neue Subsets erzeuge. Ich habe schon ein wenig gegoogelt, aber ein paar offene Punkte wären da noch (für mich als Java-GC-Verwöhnter :)):

    • wenn ich eine Area* a löschen will, dann rufe ich einfach
    delete a;
    

    auf?

    • wenn der Destruktor aufgerufen wird, muss dieser dann selbst noch etwas beinhalten? Muss ich in der Area zum Beispiel
    delete voxels;
    delete voxels2;
    

    aufrufen? So wie ich das mitbekommen habe, müsste ja sonst nichts weiter hin, da er das ja für simple Datentypen selbstständig macht.


  • Mod

    freddixx schrieb:

    Ich habe schon ein wenig gegoogelt, aber ein paar offene Punkte wären da noch (für mich als Java-GC-Verwöhnter :)):

    Als Java-GC verwöhnter solltest du dir schnellstens alles abgewöhnen, was du aus Java kennst. new und delete brauchst du in C++ so gut wie nie. Da arbeitet man normalerweise mit den Gültigkeitsbereichen um die Lebenszeit von Variablen zu steuern. Für den außergewöhnlichen Fall, dass man doch mal die Lebenszeit eines Objektes über den aktuellen Gültigkeitsbereich verlängern muss, schaut man ob die Standardbibliothek nichts dafür anbietet (es gibt da sehr viele Probleme die schon gelöst sind) und findet vermutlich einen der Smartpointer. Und falls selbst dieser nicht das sein sollte was man sucht, dann macht man die Mechanismen aus der Standardbibliothek zum sauberen und automatischen Löschen nach. Stichwort dazu: RAII.



  • Hm, also muss ich new nicht verwenden, wenn ich Speicherplatz für einen neuen Objektinstanzzeiger möchte?

    Ich meine, dass das hier geht, ist mir klar:

    Area a = Area(5);
    

    Aber ich dachte, wenn ich einen Zeiger will, brauche ich new, à la:

    Area* a = new Area(5);
    

    , da ich mir ja Speicherplatz für diesen Zeiger holen muss. Oder soll ich es aufwändiger machen und so umschreiben:

    Area a = Area(5);
    Area* aPtr = &a;
    

    Ich geb mir ja echt schon Mühe, ordentlich umzudenken. Aber natürlich vergleicht man im Kopf immer alles mit Java und wendet bekannte Muster an 🙂

    Dein Stichwort hab ich grad gegoogelt und ziehe mir den englischen Wikipedia-Artikel rein.



  • Für so große Datenmengen könnte man auch den Container std::deque verwenden, je nachdem welche Operationen du auf dem Container wie oft nutzt. Manuelle Speicherverwaltung solltest du wirklich meiden (siehe SeppJ).

    Und welchen Compiler nutzt du? Beim neuen Visual Studio 2010 ist in den Komponenten der Standardbibliothek afaik schon moving implementiert, da geht das Umkopieren um Welten schneller. In einer list<Area> muss er z.B. bei jedem push_back das komplette Objekt umkopieren, inklusive der zwei darin enthaltenen Listen. Das R-Value- und std::move-Konzept erspart das Kopieren praktisch, kommt aber erst mit dem C++0x-Standard, der aber in den neueren Compilern schon teilweise implementiert ist.


  • Mod

    Du verstehst nicht: Die Frage die du dir stellen solltest ist, ob du überhaupt einen Zeiger braucht.



  • SeppJ schrieb:

    Du verstehst nicht: Die Frage die du dir stellen solltest ist, ob du überhaupt einen Zeiger braucht.

    Konzeptionell nicht, aber wenn es einen wichtigen Geschwindigkeitsschub bringt, überwiegt das natürlich.



  • fdfdg schrieb:

    Für so große Datenmengen könnte man auch den Container std::deque verwenden, je nachdem welche Operationen du auf dem Container wie oft nutzt. Manuelle Speicherverwaltung solltest du wirklich meiden (siehe SeppJ).

    Und welchen Compiler nutzt du? Beim neuen Visual Studio 2010 ist in den Komponenten der Standardbibliothek afaik schon moving implementiert, da geht das Umkopieren um Welten schneller. In einer list<Area> muss er z.B. bei jedem push_back das komplette Objekt umkopieren, inklusive der zwei darin enthaltenen Listen. Das R-Value- und std::move-Konzept erspart das Kopieren praktisch, kommt aber erst mit dem C++0x-Standard, der aber in den neueren Compilern schon teilweise implementiert ist.

    Ich benutze derzeit VisualStudio 2005 - ich habe auch 2010 hier, muss aber projektgebunden 2005 verwenden.

    Ich hab mird die anderen Typen mal angesehen. Ich denke, ich bin mit einer Liste gut dabei, da ich ja nie irgendwo innen zugreifen muss. Allerdings weiß ich nicht, wie viel schneller eine deque ist - meinst du, das bringt was? Dann würde ich das mal ausprobieren.

    SeppJ schrieb:

    Du verstehst nicht: Die Frage die du dir stellen solltest ist, ob du überhaupt einen Zeiger braucht.

    Es kann gut sein, dass ich es nicht verstehe. Ich versuche mal zu erklären, wie ich das sehe, dann kommen wir vielleicht auf einen Nenner: Ich habe eine Area mit ~14Mio Elementen. Und eben diese Area an das Ende einer list anzuhängen dauert ewig (er erstellt doch ne Kopie und schmeißt die rauf, oder?). Klar ist es in meinen Augen performanter, mir einfach nur den Pointer zu holen und den raufzuschmeißen (der ist ja auch viel kleiner..).

    Ich habe mir bereits das Referenzwerk von Stroustrup bestellt, das morgen hoffentlich in meinem Briefkasten liegt. Nichtsdestotrotz bin ich euch für eure Erklärungen sehr dankbar.



  • Okay, habe mich über RAII belesen - erscheint mir auch sinnvoll. In Zukunft sollte ich also das Speicherbereitstellen über new unterlassen.

    Wie kann ich dennoch das Kopieren großer Objekte vermeiden? Soll ich immer erst das Objekt erstellen und mir dann mit & den Pointer holen? Oder ließe sich (besonders in diesem Szenrario) vollständig auf Pointer verzichten, ohne allzu krasse Geschwindigkeitseinbußen zu verzeichnen?

    Und verstehe ich jetzt richtig: Wenn ich new nicht verwende, wird der Destruktor auch automatisch aufgerufen, wenn der Scope der Variable verlassen wird. Ich muss also nur dafür sorgen, dass der Destruktor ordentlich ist (Dateien schließen, usw)?



  • Habt ihr die Fragen überlesen? 🙂

    Wäre nett, wenn ihr die Fragen aus den voran gegangenen Posts noch beantworten könnt (leider auch, weil mein Stroustrup immer noch nicht da ist und ich gern Köpfe mit Nägeln machen möchte..).



  • freddixx schrieb:

    Okay, habe mich über RAII belesen - erscheint mir auch sinnvoll. In Zukunft sollte ich also das Speicherbereitstellen über new unterlassen.

    Wie kann ich dennoch das Kopieren großer Objekte vermeiden? Soll ich immer erst das Objekt erstellen und mir dann mit & den Pointer holen? Oder ließe sich (besonders in diesem Szenrario) vollständig auf Pointer verzichten, ohne allzu krasse Geschwindigkeitseinbußen zu verzeichnen?

    Du könntest das Objekt auch direkt in deinem Container anlegen und dann mit Werten füllen.

    Und verstehe ich jetzt richtig: Wenn ich new nicht verwende, wird der Destruktor auch automatisch aufgerufen, wenn der Scope der Variable verlassen wird. Ich muss also nur dafür sorgen, dass der Destruktor ordentlich ist (Dateien schließen, usw)?

    Ja, der Destruktor wird aufgerufen, wenn das Objekt aus dem Scope fällt. Und der ruft auch automatisch die Destruktoren der enthaltenen Elemente auf - so daß du bei vernünftigen Klassen (Stichwort: RAII) auch die Aufräumarbeiten gratis bekommst.
    Wenn deine Klasse Speicher per new angefordert hat oder mit extern verwalteten Handles (z.B. FILE* aus der C <stdio.h>) arbeitet, mußt du die aber freigeben.



  • Und alle Klassen der STD enthalten ordentliche Destruktoren. Da brauchst du dir keine Sorgen zu machen.



  • SeppJ schrieb:

    new und delete brauchst du in C++ so gut wie nie.

    Wird dann nicht alles auf dem Stack angelegt wenn man so konsequent auf new verzichtet? Ich bin schon einige Jahre aus C++ raus und blicke nicht so ganz wie man heutzutage umfangreiche Datenmengen auf den Heap bekommt.



  • Die Alternative zu new ist es ja nicht, alles auf den Stack zu schieben, sondern Container der Standardbibliothek und eigene RAII-Klassen zu nutzen, die die nötigen Freigaben selbständig im Destruktor vornehmen.
    Die fehleranfällige manuelle Speicherverwaltung entfällt somit komplett.



  • Ok damit verlagere ich die new/delete Aufrufe in Konstruktor/Destruktor. Das ist ja überaus sinnvoll.
    Aber damit kann man doch nicht sagen, dass new/delete nicht mehr notwendig sind.

    Oder verwendet man eine Art Factory, in der new/delete nochmals weggekapselt sind? Mit Templates sollte sowas ja möglich sein.

    Gehen wir mal weg von Filehandles und dergleichen.
    Wie würde man, z.B. für ein Spiel, eine große Anzahl an Triangles und Vertices mit Hilfe von RAII anlegen und verwalten? Wo findet letztendlich die Speicherallokation statt?



  • Ein stl-Container kann nicht nur eingebaute Typen verwalten. Man könnte sich bspw. eine Klasse Triangle schreiben:

    class Triangle
    {
      float pos[3];
    public:
      Triangle(float x=0, float y=0, float z=0);
      ...
    };
    

    Die Verwendung könnte so aussehen:

    // 1
    Triangle* pTriangles;
    
    // 2
    std::vector<Triangle> triangles;
    
    ..
    // 1
    pTriangles = new Triangle[300];
    Triangle def;
    for(size_t i=0;i<300;++i)
      pTriangles[i] = def;
    // 2
    triangles.resize(300);
    

    Ein ganz entscheidender Vorteil bietet sich, wenn man die Anzahl der Dreiecke erhöhen möchte

    // 1, C++ bietet kein realloc
    Triangle* pCpy = new Triangle[301];
    memcpy(pCpy, pTriangle, 300*sizeof(Triangle);
    delete [] pTriangle;
    pTriangle = pCpy;
    pTriangle[300] = Triangle(30,30,30);
    
    // 2
    triangles.push_back(Triangle(30,30,30));
    

    Wichtig ist vor allem, dass bei der Verwendung eines vectors keine Geschwindigkeitsnachteile entstehen. Auch bisherige Schnittstellen kann man problemlos bedienen

    void f(float* data, size_t size);
    
    // 1
    f(pTriangles, 300);
    // 2
    f(&triangles[0], triangles.size());
    

    Wenn stattdessen Zeiger verwendet werden sollen (bspw. um Polymorphie zu betreiben), kann auch ein Pointercontainer verwendet werden, der nicht nur den eigenen Speicherbereich freigibt, sondern darüberhinaus jedes einzelne Element zerstört.



  • Darauf wollte ich nicht hinaus.

    Du hantierst wieder mit new und delete.
    Meine frage war, wie man sich dessen entledigt.



  • Ich habe doch immer die Möglichkeit mit einem nackten Zeiger und darunter jeweils die Alternative mit einem vector gezeigt. Ich dachte, dies wäre ersichtlich.



  • yahendrik schrieb:

    Ich dachte, dies wäre ersichtlich.

    Ist es auch.

    Nutze Container und RAII erledigt den Rest.



  • Ooookay, dann bearbeite ich mal meinen Quelltext. Ich werde iterativ vorgehen und schauen, wann es wieviel langsamer wird. Im Moment habe ich:

    Voxel* v = new Voxel(d1,d2,d3, greyValue);
    vgraph[d1][d2][d3] = v;
    

    Daraus mache ich:

    voxelGraph.vgraph[d1][d2][d3] = &Voxel(d1,d2,d3, greyValue);
    

    Dann wäre erstmal das new weg. Das ist grad nur kosmetisch, weil ich sonst zuviel ändern müsste. Im nächsten Schritt würde ich dann den Typedef ändern:

    typedef vector<Voxel*> voxVec1D;
    typedef vector<voxVec1D> voxVec2D;
    typedef vector<voxVec2D> voxVec3D;
    

    zu:

    typedef vector<Voxel> voxVec1D;
    typedef vector<voxVec1D> voxVec2D;
    typedef vector<voxVec2D> voxVec3D;
    

    Aber dann müsste ich richtig im Code wursteln 🙂 Aber bevor ich das jetzt mache: Im Moment hätte ich ja noch den 3D-Vector mit Adressen - da geht eine "Kopie" auf einen anderen Vector recht schnell, bzw, sich einen Zeiger auf ein Objekt dieses Vectors zu holen.

    Aber wenn ich auf dem Vector nur noch die ganzen Objekte habe, dann würde ja jede Kopie einen Haufen Rechenzeit in Anspruch nehmen.

    Ich glaube, ich muss mir auch Gedanken machen, WO ich im Speicher die Daten zu liegen haben möchte. Im Moment liegen sie ja irgendwo und der Vector hat nur die Referenzen. Aber wahrscheinlich ist es besser, wenn sie ausschließlich im Vector liegen und ich mir dann dort ne Referenz hole.

    *grübel*. C++ ist schon nicht so trivial. Aber ich will's ja richtig machen. Zum Glück hat der Bote vorhin meinen Stroustrup gebracht 😃


Anmelden zum Antworten