C++ zu hohe Last bei vielen Objekten

SoDaTierchen

Captain
Dabei seit
Feb. 2011
Beiträge
3.152
Hallo CB-Community,

da ich mich mit der Spielentwicklung vertraut machen möchte, habe ich mir gedacht, ich programmiere mal selbst ein kleines Spiel. Es geht hier um den Lerneffekt, nicht um die Qualität, also wird das ein rein privates Projekt.

Plattform ist C++, Grafikschnittstelle OpenGL. Programmiert wird von Pike, jedoch unter Windows.

So, mein eigentliches Problem:
Ich wollte als ersten Anfang ein sichtbares Bild erzeugen. Das ist schon ein gutes Stück Code, da OpenGL ja nativ keine Windows-Fenster ansprechen kann. Das klappt aber, dank Tutorials im Netz.
Zweiter Schritt war das Anzeigen eines Würfels. Würfel, weil ich den aus jeder Perspektive sehen könnte, selbst wenn ich mich mit x,y und z mal vermache.

Dritter Schritt war das Anzeigen von 80x80x80 Würfeln. Immerhin soll das mal eine große Welt werden. Doch hier tritt ein Problem auf: Es ruckelt wie Schwein. Die CPU geht hoch an die 25% Auslastung (keine Mehrkernoptimierung, ist also Volllast) und die Grafikkarte gurkt bei 20% rum -> Definitiv CPU-Limitiert. Dabei lasse ich die Würfel lediglich pro Frame in drei geschachtelten Schleifen erzeugen, also nichtmal wirklich berechnungen drumherum. Diese Schleife wird jedes Frame ausgeführt, ist also schon ein Batzen Arbeit, aber es sollte doch trotzdem nicht so ruckeln, oder?

Einfache Rechnung: Moderne Spiele schaffen es, 2Mrd Polygone gleichzeitig auf dem Bildschirm darzustellen und das bei 60 fps. Von mir aus auch nur Benchmark-Suiten, aber das Szenario ist nicht unrealistisch. Meine Würfel sind jetzt grob gerundet 500.000 Stück mit 6 Polygonen pro Würfel -> Also nur ein Zweitausendstel von dieser riesigen Welt. Und ich habe keine Berechnung wie Physik, bewegliche Objekte oder ähnliches drin. Nur diese Würfel anzeigen lassen und etwas entlang den x- und z- Koordinaten (horizontal) laufen.

Wie kriege ich es also hin, dass die Würfel alle angezeigt werden können, ohne dass es ruckelt wie die Pest? Wenn die Erklärung etwas aufwendiger wird, gebe ich mich auch mit Links zufrieden, aber ich bin scheinbar zu blöd, um im Netz darauf eine Antwort zu finden :/

liebe Grüße und schoneinmal Danke für die Antworten

SoDaTierchen
 

Matthias247

Cadet 3rd Year
Dabei seit
Okt. 2012
Beiträge
61
Das Problem wird sein, dass du die Würfel in jedem Frame neu erzeugst und malst. Eigentlich solltest du den/die Würfel einmal zum Ladezeitpunkt erzeugen und dann in jedem Frame einfach nur neu zeichnen. Der würfel sollte dazu dann einmal in einem Vertex Buffer Object gespeichert sein, so dass er schon in der Grafikkarte liegt und nicht immer hingeschoben werden muss.
Evtl. ist es sogar noch besser nur einen einzigen Würfel zu laden und in einem solchen Objekt zu speichern, und der Graka dann immer nur zu sagen wo er hingezeichnet werden soll um mehrere Würfel zu erhalten.
 

SoDaTierchen

Captain
Ersteller dieses Themas
Dabei seit
Feb. 2011
Beiträge
3.152
Hier werden die Würfel erzeugt:

Code:
if (draw<2)
  for(int x=-80;x++;x<80)
    for(int z=-80;z++;z<80)
      for(int y=-80;y++;y<1)
        DrawCube(x,y,z);
Und das ist die DrawCube-Methode:

Code:
GLvoid DrawCube(GLfloat x, GLfloat y, GLfloat z)
{
    float xh = x + 0.5f; float xl = x - 0.5f;
    float yh = y + 0.5f; float yl = y - 0.5f;
    float zh = z + 0.5f; float zl = z - 0.5f;

    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(xh, yh, zl); // oben rechts (OBEN)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(xl, yh, zl); // oben links (OBEN)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(xl, yh, zh); // unten links (OBEN)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(xh, yh, zh); // unten rechts  (OBEN)

        glTexCoord2f(0.0f, 0.0f); glVertex3f(xh, yl, zh); // oben rechts (UNTEN)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(xl, yl, zh); // oben links (UNTEN)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(xl, yl, zl); // unten links (UNTEN)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(xh, yl, zl); // unten rechts  (UNTEN)

        glTexCoord2f(0.0f, 0.0f); glVertex3f(xh, yh, zh); // oben rechts (VORNE)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(xl, yh, zh); // oben links (VORNE)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(xl, yl, zh); // unten links (VORNE)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(xh, yl, zh); // unten rechts  (VORNE)

        glTexCoord2f(0.0f, 0.0f); glVertex3f(xh, yl, zl); // oben rechts (HINTEN)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(xl, yl, zl); // oben links (HINTEN)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(xl, yh, zl); // unten links (HINTEN)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(xh, yh, zl); // unten rechts  (HINTEN)

        glTexCoord2f(0.0f, 0.0f); glVertex3f(xl, yh, zh); // oben rechts (LINKS)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(xl, yh, zl); // oben links (LINKS)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(xl, yl, zl); // unten links (LINKS)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(xl, yl, zh); // unten rechts  (LINKS)

        glTexCoord2f(0.0f, 0.0f); glVertex3f(xh, yh, zl); // oben rechts (RECHTS)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(xh, yh, zh); // oben links (RECHTS)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(xh, yl, zh); // unten links (RECHTS)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(xh, yl, zl); // unten rechts (RECHTS)
    glEnd();
}

Ich seh auch nen kleinen Denkfehler, sind 4x so viele Würfel. Die Anzahl selbst sollte aber trotzdem noch möglich sein. Nur halt nicht so, wie ich das mache.


Nachtrag: Oh, da kamen ja noch mehr Antworten. Ja, sowas habe ich mir auch gedacht, nur habe ich keine Ahnung, wie man sowas macht. Meine Versuche sind fehlgeschlagen, sowas zu bewerkstelligen und ich wusste auch nicht, wonach ich suchen soll. "Vertex Buffer Object" klingt vielversprechend. Ich bemühe mal Google, schaue aber trotzdem noch hier rein.
 
Zuletzt bearbeitet:

Nai

Lt. Commander
Dabei seit
Aug. 2012
Beiträge
1.531
Also einfachste Möglichkeit: Displaylists
http://www.songho.ca/opengl/gl_displaylist.html

Gibt zwar einen Speedup ist aber nicht sehr gross.

Nächste Möglichkeit: Alle Würfel in ein VBO abspeichern und dieses Zeichnen
http://www.ozone3d.net/tutorials/opengl_vbo_p2.php

Problem: Würfel können nur ALLE auf einmal gezeichnet werden. Die Position der Würfel verändern ist umständlich. Speichert man nur einen Würfel in ein VBO ab, so ist der Overhead des Zeichenaufrufs so gross, dass man kaum Performancegewinn hat.


Nächste Möglichkeit: Instancing auf das VBO eines Würfels verwenden
http://ogldev.atspace.co.uk/www/tutorial33/tutorial33.html

Theoretisch am Performantesten für soetwas, allerdings auch am umständlichsten zu implementieren

schau mal hier z.B. http://sol.gfxile.net/instancing.html
Und hier: http://sol.gfxile.net/cubes.html

Ich kann aber nicht sagen wie gut das Beispiel ist denn ich bin nicht wirklich ein Experte auf dem Gebiet
Beide Weblinks sind etwas veraltet, da man dort veraltete Shaderextensions verwendet.
 
Zuletzt bearbeitet:

SoDaTierchen

Captain
Ersteller dieses Themas
Dabei seit
Feb. 2011
Beiträge
3.152
Hey, danke für die vielen hilfreichen Antworten. Ich setze mich mal ans Lesen, Verstehen und nachimplementieren und berichte dann hinterher, was bei rausgekommen ist :)
 

Nai

Lt. Commander
Dabei seit
Aug. 2012
Beiträge
1.531
Ein Tipp als Nachtrag: Du solltest dir eventuell im vorneherein überlegen, ob du auf die veralteten Funktionalitäten von OpenGL, wie glBegin glEnd, Displaylists, Matrizenfunktionalitäten etc, setzen willst oder auf ein Reines OpenGL der Version 4.X.

Die alten Funktionalitäten helfen dir zwar zu Beginn etwas Programmieraufwand zu sparen, jedoch umso mehr du machen willst, desto mehr werden sie dich limitieren. Insbesondere kann man die eingebauten Funktionalitäten nicht Programmieren, was man jedoch zB bei den Matrizen oft gerne tun würde. Auch sind die veralteten Funktionalitäten nur eingeschränkt mit den neueren OpenGL Funktionalitäten kompatibel. Das führt dann dazu, dass du zT zwei unterschiedliche Code-Pfade hast, einmal für neu und einmal für veraltet, oder dass du den gesamten veralteten Code rausreissen musst.

Will man nur OpenGL 4.x Funktionalitäten benutzen, so benötigt man allerdings wesentlich mehr Codezeilen bis man überhaupt das erste Dreieck sieht.
 

SoDaTierchen

Captain
Ersteller dieses Themas
Dabei seit
Feb. 2011
Beiträge
3.152
Nachdem ich mich ein wenig eingelesen und herumprobiert habe mein Fazit zu dem Thema:

Ja, es löst die Problematik! Die Leistung steigt damit signifikant :)

Aber die ganzen Zusatzbibliotheken die man dafür braucht und von "irgendwo" bezieht, stören mich dann doch sehr. Mir wäre da eine Gesamtheitliche Lösung (build-in oder EIN Paket für ALLES) lieber. Da ich noch sehr am Anfang bin, was Spielprogrammierung angeht, werde ich mir mal DirectX als Gegenstück ansehen, und ob die Lösungen dort mir vielleicht sogar besser gefallen. Das Projekt ist ja noch nicht so groß, dass der Portier-Aufwand enorm wäre :D

Aber auf jeden Fall Danke an alle, die mir geantwortet haben, meine Frage wurde ja mehr als zufriedenstellend beantwortet :)
 

daemon777

Lt. Commander
Dabei seit
Dez. 2003
Beiträge
1.371
Falls es dich noch interessiert: das Problem war hier nicht, dass du die Würfel in jedem Frame neu "erzeugt" hast. Schließlich hast du ja gar keine Objekte angelegt sondern immer wieder einfach direkt alle nötigen Darstellungsoperationen durchgeführt. Das geht natürlich nur so lange, wie sich keine Objekte in der Szene verändern aber es geht ohne Probleme.

Was hier viel mehr Probleme macht ist, dass du 80*80*80 mal pro Frame glBegin(..) und glEnd(..) aufrufst. Das wäre so wesentlich besser gegangen:

Code:
glBegin(GL_QUADS)
 for ...
   for ...
      for ...
          Draw(...)
glEnd()

void Draw(...)
{
   glTexCoord2f(....);
   glVertex3f(....); 
   ....
}
Ansonsten ist es zwar eine nette Idee, dass du in der DrawCube(...)-Methode am Anfang alle 6 nötigen Werte berechnest, um die 24 Rechnungen später zu sparen aber hierbei allokierst du auch 80*80*80*6 Floats pro Frame also bei 50fps etwa 50*80*80*80*6*4 Bytes = 600 MB. Das ist schon eine ganze Menge und es kann schnell passieren, dass die CPU zwar voll ausgelastet ist aber dabei eigentlich nur auf den Arbeitsspeicher wartet.
Lösung: entweder die entsprechenden Variablen außerhalb anlegen und im Funktionsaufruf übergeben oder direkt jedes Mal einzelnd berechnen.
 

maxwell-cs

Lt. Junior Grade
Dabei seit
Juli 2007
Beiträge
465
ich habe zwar keine ahnung von grafikprogrammierung, allerdings sehe ich nicht das problem mit den 6 floats, schließlich werden nicht gleichzeitig 6*80*80*80 floats angelegt, sondern nacheinander 80*80*80 mal 6 floats, die denselben speicher nutzen können.
 

Nai

Lt. Commander
Dabei seit
Aug. 2012
Beiträge
1.531
Was hier viel mehr Probleme macht ist, dass du 80*80*80 mal pro Frame glBegin(..) und glEnd(..) aufrufst. Das wäre so wesentlich besser gegangen:
Jeder OpenGL Call hat einen nicht zu vernachlässigenden CPU-Overhead und wahrscheinlich auch einen GPU-Overhead (je nachdem wie das GLBegin und GLEND im Treiber und in der GPU intern so geregelt ist) . Würde man das glBegin herausziehen hätte man zwar wirklich weniger Calls und damit eine bessere Performance, aber man umgeht immer noch nicht das Problem, dass man durch die verschiedenen Overheads von glVertex3f und glTexCoord2f nur einen Bruchteil der maximalen GPU-Performance ausnutzen kann.

Ansonsten ist es zwar eine nette Idee, dass du in der DrawCube(...)-Methode am Anfang alle 6 nötigen Werte berechnest, um die 24 Rechnungen später zu sparen aber hierbei allokierst du auch 80*80*80*6 Floats pro Frame also bei 50fps etwa 50*80*80*80*6*4 Bytes = 600 MB. Das ist schon eine ganze Menge und es kann schnell passieren, dass die CPU zwar voll ausgelastet ist aber dabei eigentlich nur auf den Arbeitsspeicher wartet.
Lösung: entweder die entsprechenden Variablen außerhalb anlegen und im Funktionsaufruf übergeben oder direkt jedes Mal einzelnd berechnen.
Die Werte werden auf den Stack oder in den Registern allokiert, weshalb ihre Allokation billig ist. Dadurch dass die entsprechenden Cache-lines des Stacks sehr sehr häufig verwendet werden, haben die Cache-lines des Stacks eine sehr hohe Hit-Rate, weshalb wegen ihnen so gut wie nie der Hauptspeicher abgefragt wird. Im Vergleich zu dem CPU-Overhead, den die ganzen OpenGL-Calls verursachen, sollten die Kosten dieser Speicherallokation und Speicherabfragen sogar zu vernachlässigen sein.

Aber die ganzen Zusatzbibliotheken die man dafür braucht und von "irgendwo" bezieht, stören mich dann doch sehr.
Welche Zusatzbibliotheken meinst du denn ?
 
Zuletzt bearbeitet:

Blublah

Lt. Commander
Dabei seit
März 2009
Beiträge
1.592
aber hierbei allokierst du auch 80*80*80*6 Floats pro Frame also bei 50fps etwa 50*80*80*80*6*4 Bytes = 600 MB.
Nein, bei jedem Funktionseintritt werden lediglich 6 Floats auf dem Stack angelegt und beim Funktionsaustritt wieder abgeräumt. Speicher wird also fast keiner verbraucht und eine Allokation auf dem Stack ist auch sehr schnell (schneller als eine vom Heap). Trotzdem wäre es natürlich effizienter die Koordinaten nur in ihren jeweiligen Schleifen (x,y,z) hochzuzählen, da kann man z.B. 2*160*80=25600 Berechnungen von xh/xl pro Frame sparen.
 
Zuletzt bearbeitet:

SoDaTierchen

Captain
Ersteller dieses Themas
Dabei seit
Feb. 2011
Beiträge
3.152
Welche Zusatzbibliotheken meinst du denn ?

Als Beispiel GLFW und GLEW. Alle refenzierten Seiten als auch die, die ich gefunden habe, greifen stets darauf zurück. Und das sind externe Bibliotheken, die man irgendwo im Internet suchen und herunterladen muss. Dass es kein Problem darstellt, ist mir klar, aber ich habe da eine persönliche Abneigung gegen - also keine objektiven Widerworte^^
 

mental.dIseASe

Lt. Junior Grade
Dabei seit
Dez. 2008
Beiträge
466
Kann sein, dass ich mich zu weit aus dem Fenster lehne, aber ich denke mal, dass du im richtigen Leben sogut wie nie plain OpenGL programmieren wirst. Also freunde dich lieber damit an, dass du massig Bibliotheken benutzen musst. Ich wollte früher in der Hinsicht auch immer wissen, "was die Welt im Innersten zusammenhält", aber das ist oftmals einfach nicht praktikabel.

Aber einmal so richtig durch die plain-Wüste zu gehen, kann dennoch bestimmt nicht schaden. :)
 

daemon777

Lt. Commander
Dabei seit
Dez. 2003
Beiträge
1.371
ich habe zwar keine ahnung von grafikprogrammierung, allerdings sehe ich nicht das problem mit den 6 floats, schließlich werden nicht gleichzeitig 6*80*80*80 floats angelegt, sondern nacheinander 80*80*80 mal 6 floats, die denselben speicher nutzen können.
Das Problem ist ja auch nicht, dass 600MB gleichzeitig belegt werden (was ja nicht der Fall ist), sondern eine Datenrate von 600MB/s entsteht. Das ist nicht so ganz optimal.


Zitat von Blublah:
Nein, bei jedem Funktionseintritt werden lediglich 6 Floats auf dem Stack angelegt und beim Funktionsaustritt wieder abgeräumt. Speicher wird also fast keiner verbraucht und eine Allokation auf dem Stack ist auch sehr schnell (schneller als eine vom Heap). Trotzdem wäre es natürlich effizienter die Koordinaten nur in ihren jeweiligen Schleifen (x,y,z) hochzuzählen, da kann man z.B. 2*160*80=25600 Berechnungen von xh/xl pro Frame sparen.
Ich habe ja auch gar nicht behauptet, dass Speicher verbraucht wird. Lediglich, dass ständig Speicher allokiert wird. Eine Allokation auf dem Stack mag ja schneller sein als eine auf dem Heap aber sie ist immernoch langsamer als eine eingesparte Allokation. Bei 50fps * 80 * 80 * 80 * 6 Floats pro Sekunde kann so eine Kleinigkeit schonmal ins Gewicht fallen.

Zitat von Nai:
Die Werte werden auf den Stack oder in den Registern allokiert, weshalb ihre Allokation billig ist. Dadurch dass die entsprechenden Cache-lines des Stacks sehr sehr häufig verwendet werden, haben die Cache-lines des Stacks eine sehr hohe Hit-Rate, weshalb wegen ihnen so gut wie nie der Hauptspeicher abgefragt wird. Im Vergleich zu dem CPU-Overhead, den die ganzen OpenGL-Calls verursachen, sollten die Kosten dieser Speicherallokation und Speicherabfragen sogar zu vernachlässigen sein.
Die Antwort hat mich jetzt doch etwas mehr ins Grübeln gebracht. In den Registern wäre die Allokation ja wirklich billig. Mir war nicht bewusst, dass so etwas überhaupt möglich ist. Auf dem Stack wäre hier verhältnismäßig doch ziemlich teuer. Im Durchschnitt sollten die Adressen für die Floats auch immer die Gleichen sein, weshalb wohl auch die selbe Cacheline hierfür genutzt werden wird. Allerdings wird der Wert jedesmal geändert. Jetzt kommt es natürlich an, ob der Wert noch ausgerollt wird oder, ob hier darauf verzichtet wird, da der Wert eh wieder verworfen wird. Rein theoretisch könnte er natürlich drinne bleiben und die Cache-Line auch noch als gültig deklariert bleiben, solange keine anderen Prozesse dazwischenpfuschen. Wenn das tatsächlich passiert, dann hatte ich unrecht und habe mal wieder etwas gelernt.
Aber ganz allgemein gesprochen ist es dennoch nicht die beste Idee Millionen von Variablen ständig neu zu erstellen, auch wenn es vielleicht hier in dem Fall tatsächlich nicht ins Gewicht fallen sollte.

Hast du denn noch irgendwelche Infos darüber, wodurch der Overhead bei den OGL-Funktionen entsteht? Was hier intern abläuft weiß ich nämlich leider überhaupt nicht.
 
G

geisterfahrer

Gast
Punkt 1: https://www.computerbase.de/2012-09/idf-2012-die-cpu-architektur-haswell-im-detail/
Laut Tabelle hat Sandybridge eine L1 Cache Performance von 32/16 Byte pro Takt! Bei 3GHz macht das schlappe ~91GB/s - nicht vergessen, jeder Core eines Multicores hat eigenen L1-Cache. Da es ja nur sechs Variablen sind, passen diese komplett in den L1 Cache rein (32kByte Platz!). 600MB/s schafft sogar jeder Arbeitsspeicher.

Punkt 2: Speicher wird nicht auf dem Stack allokiert. Beim Start des Programms bekommt das Programm großzügig Stackspeicher zugewiesen. Bei Aufruf einer Funktion, bekommt diese einen Stackpointer, sämtliche Stackvariablen sind dann relativ zum Stackpointer fest im Quellcode codiert. Da wird nichts zugewiesen und nichts gelöscht, es wird einfach eine Speicheradresse beschrieben. Es ist auch irrelevant wieviel (solange der Stackspeicher reicht) man belegt, da beim Aufrufen einer Funktion der Stackpointer einfach nur um eine größere Zahl aufaddiert wird.
Code:
int test(int var1, int var2)
{
  int stackvar1 = var1 + 2;
  int stackvar2 = var2 + 3;
  return stackvar1 + stackvar2;
}
Hier der Assembler Beweis:
_Z4testii:
pushl %ebp //Stackframe vorbereitungen (Stichwort Empfehlung für Google: "Stackframe". Dann dürften Ergebnisse kommen die den Sachverhalt mit Stackvariablen erläutern
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %eax //erster Parameter wird in das Rechenregister kopiert
addl $2, %eax //2 wird addiert (Ergebnis liegt in Register eax)
movl %eax, -8(%ebp) //Ergebnis wird in Stackvariable1 (relativ zu der Adresse im Basepointer (ebp)) gespeichert
movl 12(%ebp), %eax //zweiter Paramerter wird in das Rechenregister kopiert
addl $3, %eax //3 wird addiert
movl %eax, -4(%ebp) //Ergebnis wird in Stackvariable 2 gespeichert
movl -4(%ebp), %eax //Stackvariable wieder in Rechenregister kopieren
movl -8(%ebp), %edx //Stackvariable wieder in Rechenregister kopieren
addl %edx, %eax //Addition der Variablen von Stackvariable 1 + Stackvariable 2
leave
ret

Der Code ist zugegebenermaßen etwas dämlich, da sehr optimierbar, deswegen mit -O0 kompiliert. Mit -O3 bleibt da nicht viel übrig, weil dieses unnötige rumkopiere in Register/Stack vermieden wird:
_Z4testii:
movl 8(%esp), %eax
movl 4(%esp), %edx
leal 5(%edx,%eax), %eax //die fünf resultiert aus dem +2 und +3, hier zusammenfasst.
ret

Man sieht aber jedenfalls sehr schön, da wird nichts allokiert, sondern einfach nur Speicherplätze benutzt, die einmalig beim Programmstart bereits allokiert wurden. Es macht also keinerlei Unterschied, ob man Stackvariablen wiederverwendet, oder nicht.
Sprich:
int test;
for(...) {
//mach was mit test
}
oder:
for(...) {
int test;
//mach was mit test
}
sind Geschwindigkeitstechnisch identisch, weil eben test nicht jede Schleifeniteration neu allokiert wird, man braucht also nicht zwecks Wiederverwendung irgendwelche Variablen außerhalb des eigentlichen Scopes zu verschieben und so seinen Code verschandeln - ohne Compileroptimierung wird wahrscheinlich sogar mehr Stackspeicher belegt, weil der Scope somit noch weiter hinter der for-Schleife reicht, bis zum Ende der Funktion.

Ansonsten: Wenn man nicht weiß, wo es performancetechnisch hakt: Unter Linux valgrind benutzen. Wer sich auskennt, kann sich zusätzlich natürlich auch den Assemblercode ausgeben lassen (gcc mit Parameter -S aufrufen)

Quintessenz: Debatte um die float Variablen sofort vergessen - die spielen keinerlei, auch wirklick gar keine Rolle aus Performancesicht.
 

Nai

Lt. Commander
Dabei seit
Aug. 2012
Beiträge
1.531
Als Beispiel GLFW und GLEW. Alle refenzierten Seiten als auch die, die ich gefunden habe, greifen stets darauf zurück. Und das sind externe Bibliotheken, die man irgendwo im Internet suchen und herunterladen muss. Dass es kein Problem darstellt, ist mir klar, aber ich habe da eine persönliche Abneigung gegen - also keine objektiven Widerworte^^
GLFW kenne ich nicht. GLEWspart dir im wesentlichen nur bei OpenGL 2.X oder höher, dass du dir für jede einzelne Funktion, welche du verwenden willst, jedes mal beim Initialisieren des Kontexts einen Funktionszeiger aus der OpenGL DLL(?) holen darfst. Soetwas kann man auch ohne Probleme selbst machen, wobei das ganze abfragen eher lästig ist.

Punkt 2: Speicher wird nicht auf dem Stack allokiert. Beim Start des Programms bekommt das Programm großzügig Stackspeicher zugewiesen. Bei Aufruf einer Funktion, bekommt diese einen Stackpointer, sämtliche Stackvariablen sind dann relativ zum Stackpointer fest im Quellcode codiert. Da wird nichts zugewiesen und nichts gelöscht, es wird einfach eine Speicheradresse beschrieben. Es ist auch irrelevant wieviel (solange der Stackspeicher reicht) man belegt, da beim Aufrufen einer Funktion der Stackpointer einfach nur um eine größere Zahl aufaddiert wird
Hier muss ich leider widersprechen. Eben diese Art von Speicherallokation, also wenn man sich Stackspeicher durch inkrementieren/dekrementieren des Stackpointers belegt, wird Speicherallokation auf dem Stack genannt; wie zB. hier nachzulesen:
http://en.wikipedia.org/wiki/Stack-based_memory_allocation
Dadurch läuft sie in konstanter Zeit ab, wodurch sie billig ist. Allerdings ist im Ausgleich dafür auch nur eine statische Allokation möglich.


Man sieht aber jedenfalls sehr schön, da wird nichts allokiert, sondern einfach nur Speicherplätze benutzt, die einmalig beim Programmstart bereits allokiert wurden. Es macht also keinerlei Unterschied, ob man Stackvariablen wiederverwendet, oder nicht.
Sprich:
int test;
for(...) {
//mach was mit test
}
oder:
for(...) {
int test;
//mach was mit test
}
sind Geschwindigkeitstechnisch identisch, weil eben test nicht jede Schleifeniteration neu allokiert wird, man braucht also nicht zwecks Wiederverwendung irgendwelche Variablen außerhalb des eigentlichen Scopes zu verschieben und so seinen Code verschandeln - ohne Compileroptimierung wird wahrscheinlich sogar mehr Stackspeicher belegt, weil der Scope somit noch weiter hinter der for-Schleife reicht, bis zum Ende der Funktion.
Wenn der Compiler nicht optimieren würde, so würde er sich bei jedem for-Durchlauf 4 Byte für test auf dem Stack allokieren; er müsste also jedes mal den Stackpointer inkrementieren, wodurch er eine Integeroperation benötigen würde. Würde man das test aus dem for rausziehen, so bräuchte er dies nicht zu tun. Dadurch wäre erste Version, wenn der Compiler nahe am C++ Code bleiben würde *minimal* schneller. Allerdings würde ich so spontan darauf setzen, dass der Compiler dank cleveren Optimierungen schon das performanteste machen würde und solche minmalen Optimierungen in 99.9 % aller Fälle eh egal sind.


Hast du denn noch irgendwelche Infos darüber, wodurch der Overhead bei den OGL-Funktionen entsteht? Was hier intern abläuft weiß ich nämlich leider überhaupt nicht.
Leider nicht ausser das übliche Registersichern etc.. Ich kann hier vielleicht nur noch einmal zur Verdeutlichung grob Performanceabschätzungen für den maximalen Dreiecksdurchsatz bei flüssigen 30 FPS geben aus eigener Erfahrung:
glBegin//glEnd: 20 000 Dreiecke
Displaylists: 100 000 bis 200 000 Dreiecke
Bei grossen VBOs: 50 000 000 Dreiecke
Allerdings läuft das ganze nur bis so ~5 000 - 10 000 VBOs Zeichenaufrufen flüssig. Will man mehr Objekte zeichnen, so muss man Instancing verwenden. Dadurch wird deutlich, dass einem der Overhead bei den Zeichenaufrufen unter Umständen einen grossen Strich durch die Rechnung macht.
 

daemon777

Lt. Commander
Dabei seit
Dez. 2003
Beiträge
1.371
@geisterfahrer

Danke für die wirklich ausführliche Erklärung. Da habe ich mich in diesem Fall wohl wirklich etwas verrannt. Ich kann nur trotzdem davor warnen davon auszugehen, dass der Compiler schon alles irgendwie für einen richten wird. Das geht in manchen Fällen (wie man hier ja auch sehen kann) noch gut, aber eben nicht immer.

@Nai

Die Vergleichswerte finde ich auch durchaus interessant. Da werde ich wohl mal ein wenig in Richtung VBOs experimentieren müssen. Immer gibt es so viele interessante Projekte, die man in Angriff nehmen könnte.... :D
 
Top