Programm läuft einfach nicht weiter?



  • [Rewind] schrieb:

    Lies dir bitte zuerst den in diesem Forum oben stehenden Beitrag "Du brauchst Hilfe?" durch (ALLES aber vor allem den Abschnitt Stell deine Fragen präzise.), bevor du nach Hilfe fragst.

    Ob du's glaubst oder nicht: Den Thread habe ich auch gelesen 😉 Das Projekt ist mittlerweile recht komplex geworden. Daher wollte ich es erstmal mit einer Grobeinschätzung versuchen.

    SeppJ schrieb:

    Ohne Code ist das zu vage. Das einzige was man vermuten könnte ist ein Überlauf irgendeiner Art, aber es könnte auch leicht irgendein anderer Fehler sein.

    Alles klar, ich poste hier mal ein paar Ausschnitte. Vielleicht stelle ich mich auch nur zu blöd an. Ich programmiere erst seit 8 Wochen C++ und entlehne mein Wissen vornehmlich Büchern und Beispielen der C++-Referenz. Ursprünglich programmiere ich eher Java 🙂

    Erstmal der Code, bei dem es kracht:

    list<Area> VoxelGraph::initSearch() {
    	list<Area> areas;
    	while (this->hasNext()) {
    		Voxel* currentVoxel = this->getNext();		
    		Area a;
     		a.addVoxel(currentVoxel);
    		//If there are valid neighbours, enqueue them
    		this->enqueueNeighbours(currentVoxel, &a);
    		vgraph[currentVoxel->getX()][currentVoxel->getY()][currentVoxel->getZ()] = 0;
    		while (!this->queue.empty()) {
    			Voxel* v = this->queuePop();
    			this->enqueueNeighbours(v, &a);
    			a.addVoxel(v);
    			vgraph[v->getX()][v->getY()][v->getZ()] = 0;
    		}
    		areas.push_back(a);
    	} // DIES ist die Stelle, an der er bei großen Datensätzen rausspringt,
    	  // er bearbeitet die While-Schleife einfach nicht mehr weiter. Er prüft
    	  // nicht einmal die Bedingung im Kopf.
    	return areas;
    }
    

    Dann noch die essentiellen Entities:

    class Voxel {
    
    public:
    	Voxel(int x1, int y1, int z1, short grey1);
    	Voxel();
    	~Voxel(void);
    	int		getX() { return x; };
    	void	setX(int x1);
    	int		getY() { return y; };
    	void	setY(int y1);
    	int		getZ() { return z; };
    	void	setZ(int z1);
    	short	getGrey() { return grey; };
    	void	setGrey(short grey1);
    	bool	getEnqueued() { return enqueued; };
    	void	setEnqueued(bool e) { enqueued = e; };
    private:
    	int		x;
    	int		y;
    	int		z;
    	short	grey;
    	bool	enqueued;
    	bool    isEdge;
    
    };
    
    class Area {
    
    public:
    	Area(void);
    	Area(short avgGrey);
    	~Area(void);
    	void addVoxel(Voxel* v);
    	short getAvgGrey() { return avgGrey; }
    	list<Voxel*> voxels;
    	list<Voxel> voxels2;
    
    private:
    	short avgGrey;
    	long  totalGrey;
    };
    

    Hinter dem vgraph verbirgt sich (derzeit) nur dieses Konstrukt:

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

    Noch eine Frage: Es kam die Vermutung, dass es sich um einen Variablenüberlauf handeln könnte. Kommt da nicht immer irgendein Runtime-Fehler? Kann er im Debugmode auch stillschweigend hängen? Wenn ja, dann prüfe ich erstmal alle Variablen noch einmal zusätzlich durch, während ich auf eure Antworten warte.



  • Ich hoffe doch, daß du deinem vgraph vor dieser Funktion genug Elemente spendiert hast, daß die Zugriffe nicht über die vector-Größen hinausschreiben.

    freddixx schrieb:

    Noch eine Frage: Es kam die Vermutung, dass es sich um einen Variablenüberlauf handeln könnte. Kommt da nicht immer irgendein Runtime-Fehler? Kann er im Debugmode auch stillschweigend hängen? Wenn ja, dann prüfe ich erstmal alle Variablen noch einmal zusätzlich durch, während ich auf eure Antworten warte.

    Wenn du Pech hast, bewirkt so ein Überlauf auch eine Endlosschleife, weil der maximal mögliche Wert der Zählvariable kleiner ist als das Ziel und sie plötzlich wieder bei Null anfängt.



  • Hm, genug Elemente hat er. Da er praktisch das Koordinatensystem repräsentiert, wird da nichts überschritten.

    Das mit der Zählschleife klingt sinnvoll. Vielleicht wird unsigned int ja irgendwo geknackt. Was mich nur verwirrt - das könnte maximal in den hasNext()-Funktionen passieren - und die werden ja nach der als Kommentar markierten Stelle lt. Debugger nicht noch einmal aufgerufen.

    Bietet es sich an, "a" manuell zu zerstören, nachdem es in "areas" eingehängt wurde? Eigentlich nicht, oder? Da die Variable zum erneuten Schleifendurchlauf ja neu initialisiert wird. Ich denke bloß, dass ihn das vielleicht Zeit kosten könnte, bis er alle 13 Mio. Voxel in diesem Areal gelöscht und den Speicher wieder freigegeben hat.



  • Setz an den Schleifenbeginn mal einen Breakpoint. Da du sagst, dass er die Probleme bei großen Datensätzen hat, könntest du dem breakpoint einen hitcount spendieren und nur alle ~100 Durchläufe stoppen. So kannst du nach und nach abgrenzen, wieviele Durchläufe er baucht, bis er steht. Wenn das ne konstante Zahl ist, kannst du die fragliche Schleife dann schrittweise debuggen um rauszufinden wann und wo das genau passiert..



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


Anmelden zum Antworten