PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Code Optimierung


Einfachkrank
2002-12-05, 16:27:52
Hi,

mich würde mal interessieren, auf was die Profis der Programmierung bei ihren Programmen bzw. Spielen alles beachten. Wenn es geht, dann speziell zu C/C++ und zu deren Funktionen :)
Nutzt ihr viele Zeiger oder auch Pointer? Was ist besser, mehrere kurze Funktionen oder weniger längere? Sind globale Variablen sinnvoller im Gegensatz zu Zeigern? Was sollte man möglichst dynamisch programmierung und was statisch? Wo kann man besonders Speed sparen, da ich hauptsächlich mit OpenGL und C++ werke :D ?

EDIT: Ach, und wie sieht`s speziell mit der Aufteilung in Dateien, also ob DLL, oder nicht DLL, viele Header Dateien etc. ??? ?

MFG Einfachkrank

Exxtreme
2002-12-05, 17:44:55
Hehe, das ist eine gute Frage. Man sollte folgende Sachen beachten:

grössere Datenmengen ständig hin und her zu kopieren ist nicht so gut. Und man sollte Schleifen so coden, daß diese möglichst wenige Durchläufe brauchen.

Ansonsten kann man sich in den meisten Fällen auf den Compiler verlassen.

Edit: Sinn.

Demirug
2002-12-05, 20:16:09
In erster Linie versuche ich mal so zu programmieren das ich den Code auch noch 1 Jahr später verstehe. Optimiert wird dann an den Flaschenhälsen.

Aber wie Exxtreme schon sagte erzuegen die Compiler inzwischen recht guten Code aber wenn man den falschen Ansatz zur Lösung eines Problems benutzt kann der Compiler auch nichts mehr retten.

Wenn du jetzt noch die Grafikkarte ins spiel bringst gelten wieder ganz andere Regeln. Aber dazu gibt es zum Beispiel bei NVIDIA eine ganze menge gute Dokumente.

Einfachkrank
2002-12-06, 16:43:51
Originally posted by Demirug
Wenn du jetzt noch die Grafikkarte ins spiel bringst gelten wieder ganz andere Regeln. Aber dazu gibt es zum Beispiel bei NVIDIA eine ganze menge gute Dokumente.

Also ich hab zurzeit ne Voodoo5 5500 AGP mit 64MB. Aber macht das im privaten Programmiererbereich solche Unterschiede, zwischen ner Voodoo und ner Geforce ???

Demirug
2002-12-06, 17:01:41
Originally posted by Einfachkrank


Also ich hab zurzeit ne Voodoo5 5500 AGP mit 64MB. Aber macht das im privaten Programmiererbereich solche Unterschiede, zwischen ner Voodoo und ner Geforce ???

Ja das macht einen Unterschied. Bei der Voodoo must du dir um viel weniger Sache sorgen machen.

zeckensack
2002-12-06, 18:13:50
Originally posted by Exxtreme
Und man sollte Schleifen so coden, daß diese möglichst wenige Durchläufe brauchen.Öööhm :|

Eigentlich nicht. Kurze Schleifen, die nur ein, zweimal durchlaufen werden haben einen prozenutal viel zu hohen Overhead für die Schleifenlogik. Daher sind oft wiederholte Schleifen besser (es sei denn natürlich sie machen unnützen Kram, aber dann ist's eh egal ob oft oder nicht).

Auch zu kleine Schleifenkörper haben dieses Problem, das löst man durch sogenanntes 'loop unrolling'. Dh daß man pro Durchlauf der Schleife mehrere logische Durchläufe ausführt. Wobei man dann wieder beachten muß, daß wenn man innerhalb der Schleife zB vier logische Iterationen ausführt, man die übriggebliebenen Iterationen zusätzlich außerhalb der Hauptschleife ausführen muß.
*edit* Man kann halt nicht jede mögliche Anzahl an Schleifeniterationen glatt durch vier teilen.

Clevere Compiler sollten eigentlich in der Lage sein das loop unrolling selbst zu erledigen.

AMD gibt als vernünftigen Wert für Schleifenkörper ca 100 Maschineninstruktionen an. Größer ist tendenziell besser, verschwendet aber Platz im Code-Cache.

Bei Streaming-Geschichten soll man pro Iteration 64Bytes an Daten verarbeiten, aber das lohnt nur zu wissen, wenn man Prefetch-Instruktionen (ergo Assembler) einfügt, außerdem ist das schwer Athlon-spezifisch.

Einfachkrank
2002-12-07, 20:20:43
Hi,

kann mir jemand mal die folgenden Schlüsselwörter wirklich im Detail erklären:
1. Virtual
2. Union
3. Inline
4. Const
5. Void
6. Operator
7. und wenns geht, dann die wichtigsten von diesen #ifndef - Dingern.

Ich möchte diese Schlüsselwörter mal wirklich im Detail kennen und deren sinnvollste Anwendung. Besonders weiß ich nichts über virtual :D

MFG Einfachkrank

GloomY
2002-12-07, 20:55:47
Originally posted by Demirug
In erster Linie versuche ich mal so zu programmieren das ich den Code auch noch 1 Jahr später verstehe. Optimiert wird dann an den Flaschenhälsen.

Aber wie Exxtreme schon sagte erzuegen die Compiler inzwischen recht guten Code aber wenn man den falschen Ansatz zur Lösung eines Problems benutzt kann der Compiler auch nichts mehr retten.Seh' ich auch so. Also an sich braucht man gute Algorithmen, sonst nützt die ganze Optimiererei nichts.

Ich habe ein paar frühe Programmiererfahrungen mit einem Primzahlenprogramm gemacht. Der erste Ansatz war reines Brute Force und es lief furchtbar langsam (war übrigends ein 8086er, deshalb sowieso elends lahm). Nach und nach habe ich das Programm immer weiter optimiert und dabei einen riesen Aufwand betrieben.

Also ich es dann später mit dem "Sieb des Eratosteles" (eigentlich ganz billiger Algorithmus) verglichen habe, war mein optimiertes Programm aber immer noch langsamer :|

Also merken: Die richtigen Algorithmen sind gaaaaanz wichtig!
Originally posted by Einfachkrank
Hi,

kann mir jemand mal die folgenden Schlüsselwörter wirklich im Detail erklären:
1. Virtual
2. Union
3. Inline
4. Const
5. Void
6. Operator
7. und wenns geht, dann die wichtigsten von diesen #ifndef - Dingern.

Ich möchte diese Schlüsselwörter mal wirklich im Detail kennen und deren sinnvollste Anwendung. Besonders weiß ich nichts über virtual :D

MFG Einfachkrank
zu 2.) Ein Union ist praktisch ein struct, welches aber nicht alle Variablen beinhaltet, sondern genau nur eine davon.

Bsp:

union test_typ {
char c;
int i;
double x;
} test;

test.i = 10;
// mach' irgendwas
test.x = 3.14;
// jetzt ist test.i nicht mehr verfügbar
Die Speicherung für die einzelnen Variablen findet an genau der selben Adresse statt, daher ist test.i nicht mehr verfügbar, wenn test.x zugewiesen wurde.
Die Größe der Union ist die Größe des größten Elements (hier: double, also wahrscheinlich 8 Bytes)

Die anderen Sachen kann jemand anders sicher besser als ich erklären...

Xmas
2002-12-07, 21:09:45
Die meisten findest du hier: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_pluslang_Specifiers.asp?frame=true

Union ist eine struct, bei der sämtliche Member an derselben Adresse anfangen. Das ist sinnvoll, wenn man von einer Gruppe von Variablen immer nur eine braucht, oder z.B. auf die einzelnen Bytes eines Integer direkt zugreifen will.

Was für "#ifndef Dinger"?

zeckensack
2002-12-07, 21:13:03
Ich hol mir mal die Rosinen :)
Originally posted by Einfachkrank
1. Virtual
2. Union
3. Inline
4. Const
5. Void
6. Operator
7. und wenns geht, dann die wichtigsten von diesen #ifndef - Dingern.

3. als inline deklarierte Funktionen werden nicht aufgerufen, sondern direkt vor Ort in den Code eingefügt. Das lohnt bei sehr kleinen Funktionen, bei denen der Overhead für die Parameterübergabe oft größer ist, als der eigentliche Funktionscode.

4. const ist ein 'Versprechen' des Programms an den Compiler, die so deklarierte Variable nicht zu verändern. Das erlaubt
a)aggressivere Optimierung des Compilers
b)eigene Fehler zu vermeiden
Bsp:const double pi=3.141...; //wieso sollte ich das je wieder ändern wollen?

<...>

void funkschn(const int cheese)
{
if (cheese=3) //hamwer uns vertippt, woll? sollte wahrscheinlich cheese==3 heißen
{
rhabarber();
}
}

5. Funktionen in C/C++ müssen einen Rückgabetyp haben. Wenn sie nichts zurückgeben, dann muß man auch das deklarieren. In diesem Fall als void. void ist garnichts und belegt keinen Speicher (englisch void->(die) Leere). Würde man das weglassen, könnte der Compiler eine Deklaration nicht von einem Funktionsaufruf unterscheiden.

6. operator + zB. 'Eingebaute' Operatoren wie eben +, -, +=, *= und andere können überladen werden, damit man so lustige Sachen wievector3 a,b,c;
a=b+c;machen kann, anstatt jedesmala.x=b.x+c.x;
a.y=b.y+c.y;
a.z=b.z+c.z;schreiben zu müssen.

MeLLe
2002-12-08, 17:07:57
zu 1)
Nach meinem Verständnis müssen (sollten?) als virtual ausgewiesene Methoden einer Basisklasse beim Ableiten eigener Klassen aus dieser Basisklasse eigens implementiert werden.
Damit soll AFAIK sichergestellt werden, dass immer die zur Klasse passende Methode (v.a. beim Aufruf über Zeiger) gefunden wird. Würde man virtual weglassen, würde der Compiler beim Aufruf einer Methode einer abgeleiteten Klasse über einen Zeiger vom Typ der Basisklasse die Methode der Basisklasse aufrufen. Und dass kann Probleme machen.
Beispiel gefällig?
class cFahrzeug
{
public:
virtual void Fahren() { BewegDich(); }
[...]
}

[...]

class cAuto : public cFahrzeug
{
public:
virtual void Fahren() { DrehDieRäder(); }
}

class cSchiff : public cFahrzeug
{
public:
virtual void Fahren() { SetzDieSegel(); }
}

[...]

void main(void]
{
cAuto passat;
cSchiff einmaster;
cFahrzeug *vehikel = new cFahrzeug;

vehikel->Fahren(); // ruft cFahrzeug::Fahren() auf
passat.Fahren(); // ruft cAuto::Fahren() auf
einmaster.Fahren(); // ruft cSchiff::Fahren() auf

vehikel = &passat;
vehikel->Fahren(); // ruft cAuto:Fahren() auf
// hätten wir oben in der ClassDef die
// virtual-Angaben weggelassen, wäre hier
// jetzt cFahrzeug:Fahren() aufgerufen worden

vehikel = &einmaster;
vehikel->Fahren(); // ruft cSchiff:Fahren() auf
// hätten wir oben in der ClassDef die
// virtual-Angaben weggelassen, wäre hier
// jetzt cFahrzeug:Fahren() aufgerufen worden
}

hth

Edit: Verdammtes Inline-Methoden-Implementiering :|

Einfachkrank
2002-12-09, 17:00:37
Hi,

mir ist gerade noch was eingefallen -> Wo ist der Unterschied zwischen
Varialbe++ // bzw. Varialbe--und++Variable // bzw. --Variable ?

Pitchfork
2002-12-09, 17:24:09
Rein technisch ist der Unterschied, daß

Var ++

zu einem erhöhen des Wertes nach der Auswertung des gesamten Ausdrucks führt, während

++ Var

zu einem erhöhen des Wertes vor Beginn des Ausdrucks führt.

Beispiel:

int i = 5;
print(i ++); ===> Ausgabe: 5

int j = 5;
print(++ j); ===> Ausgabe: 6


Wenn nun der inkrement nicht in einem Ausdruck verwendet wird, sondern alleine in der Gegend rumsteht, gilt folgendes zu beachten:

- bei einfachern Integern und so gibt es keinen Unterschied.
- bei Klassen mit überladenem Increment Operator (operator ++) ist die Version mit dem ++ vor der Var deutlich schneller, da dort keine temp. Kopie erzeugt werden muß.

Nasenbaer
2002-12-09, 18:39:28
7.) http://www.forum-3dcenter.net/vbulletin/showthread.php?s=&threadid=36241

Da wird alles erklärt. :)

Mfg Nasenbaer

Einfachkrank
2002-12-09, 21:33:26
Hi,

ich hab noch mal ne kleine Frage zu den #ifdef Befehlen. Das sind ja quasi Anweisungen an den Präprozessor oder so ähnlich, der dann weiß ob dieser Teil des Codes mit compiled werden soll oder nicht, je nach dem ob das Ding definiert ist oder nicht. So weit richtig?

Jetzt meine Frage, falls das so weit stimmt -> Wie kann ich im Code Sachen definieren, die abhängig sind von der Programmeinstellung. Beispiel: In nem Game soll die eine Option zur Verfügung stehen, mit der man Schatteneffekte ein und ausschalten kann. Jetzt möchte ich, falls sie ausgeschaltet sind, einen State nicht definieren, der dann die Codeteil auslässt, welche für die Projektion von Schatten zuständig wären.(und anders herum solls natürlich auch funzen :D)

Ich hoffe das war klar und deutlich :)

MFG Einfachkrank

Demirug
2002-12-09, 21:47:42
Mit "if else" Anweisungen zum Beispiel.

Also in etwas so:



void Render ()
{
if (SchattenActiv)
{
Code der nur bei Aktiven Schatten ausgeführt werden soll
}
else
{
Code der nur bei Inaktiven Schatten ausgeführt werden soll
}

Code der auf jeden Fall ausgeführt werden soll.
}



Das ist der klassiche C Ansatz.

Es gibt auch noch eine C++ Lösung die mit virtuellen Methoden arbeitet. Diese hat den Vorteil das man damit die ifs eliminieren kann. Allerdings ist sowas nur für jemanden zu empfehlen der sich gut mit OOP auskennt. Wenn du willst kann ich ein auch dafür ein Code Beispiel posten.

Nasenbaer
2002-12-09, 22:26:42
JA mach mal bitte. :)
Auch wenns mich warscheinlich anfangs überfordert. Aber perfekte OOP ist schon mein Ziel.

Mfg Nasenbaer

Demirug
2002-12-09, 22:46:59
So bitte:


class ShadowSetup
{
public:
virtual void SetShadows () = 0;
};

class ActiveShadowSetup : public ShadowSetup
{
public:
virtual void SetShadows ()
{
// Schatten Code
}
}

class InactiveShadowSetup : public ShadowSetup
{
public:
virtual void SetShadows ()
{
// Keine Schatten Code
}
}

class Render
{
public:
Render ()
: m_ShadowSetup (NULL)
{
SetShadows (true);
}

void SetShadows (bool active)
{
delete m_ShadowSetup;

if (active)
m_ShadowSetup = new ActiveShadowSetup ();
else
m_ShadowSetup = new InactiveShadowSetup ();
}

void Render ()
{
m_ShadowSetup->SetShadows ();

// Weitere Code
}

protected:
ShadowSetup* m_ShadowSetup;
}

Nasenbaer
2002-12-10, 00:01:01
Also im groben hab ichs verstanden aber ein paar Fragen hab ich noch. :-)

Ich muss doch nun trotzdem noch ne if-Anweisung reinnehmen um
Render.SetShadows(false) auszuführen, wenn ichs nich haben will oder hab ich da was falsch verstanden?

Vielleicht kannst du ja mal erklären wie man mit diesen Code anschließend umgeht.

Mfg Nasenbaer

Demirug
2002-12-10, 07:44:55
In deinem Renderloop wird immer nur Render.Render () aufgerufen. Die Render.SetShadows () Methode wird nur dann aufgerufen wenn man über das Menü oder die Konsole die einstellung für die Schatten ändert. Der Vorteil dabei ist das man im normalen Renderloop die if Anweisungen so beseitigen kann (nicht alle) und if Anweisungen bzw jede Art von bedingten Sprüngen mögen moderne CPUs nicht so sehr.

zeckensack
2002-12-10, 13:38:03
Die Aufrufe von virtual functions erfolgen - im Ggs zu bedingten Sprüngen - durch Sprüngen in eine Tabelle von Funktionszeigern (= vtable).
Zu Fuß würde man das so definieren
typedef void (*vertex_converter_fp)(const Vertex*const,internal_vertex*const);

const vertex_converter_fp AMD_VCs[19]={
//no texture
AMD_cv_simple,AMD_cv_col,AMD_cv_z,AMD_cv_col_z,AMD_cv_w,AMD_cv_col_w,
//texture
AMD_cv_tex,AMD_cv_col_tex,AMD_cv_tex_z,AMD_cv_col_tex_z,AMD_cv_tex_w,AMD_cv_col_ tex_w,
//texture with own w
AMD_cv_wtex,AMD_cv_col_wtex,AMD_cv_wtex_z,AMD_cv_col_wtex_z,AMD_cv_wtex_w,AMD_cv _col_wtex_w,

AMD_cv_stuff_with_table_fog};

const vertex_converter_fp x86_VCs[19]={
//no texture
x86_cv_simple,x86_cv_col,x86_cv_z,x86_cv_col_z,x86_cv_w,x86_cv_col_w,
//texture
x86_cv_tex,x86_cv_col_tex,x86_cv_tex_z,x86_cv_col_tex_z,x86_cv_tex_w,x86_cv_col_ tex_w,
//texture with own w
x86_cv_wtex,x86_cv_col_wtex,x86_cv_wtex_z,x86_cv_col_wtex_z,x86_cv_wtex_w,x86_cv _col_wtex_w,

x86_cv_stuff_with_table_fog};
Diese 'expliziten vtables' kann man dann so oder ähnlich aufrufen:void
convert_vertex(const Vertex*const src,internal_vertex*const dest)
{
uint config_number=blablabla;
if (cpu.got_3dnow()) AMD_VCs[config_number](src,dest);
else x86_VCs[config_number](src,dest);
}
(das letzte if kann man natürlich rausschmeißen, habe ich auch nicht drin, aber das hätte jetzt zuviele Änderungen erfordert)

____________________

Das was jetzt kommt ist sogar noch weniger elegant, aber es macht im Prinzip das gleiche. Der Unterschied besteht darin, daß die 'vtable' (die es in diesem Fall nicht wirklich gibt) explizit einmal pro Objekt ausgefüllt wird. Auch dies ist Polymorph, allerdings sehr sehr altmodisch. Nur zu Demonstrationszwecken.

//pixpipe.h

class
PixelPipe
{
public:
void init();
uint updates; //bitfield that indicates new states
void (*reconfigure)(void); //apply new states

void (*kill)(void); //free all associated resources (memory, display lists, shaders)
<...>
private:
friend class Lfb;

static void (*suspend)(void); //suspend pixel pipe for lfb writes
static void (*resume)(void); //restore pixel pipe to previous state (use after finishing lfb writes)
};reconfigure, kill, suspend und resume sind in diesem Fall Zeiger auf Funktionen. Die Methode init() füllt diese (nach Abfrage der Konfiguration) passend aus.
//pixpipe.cpp
void
PixelPipe::init()
{

if (gl_caps.ati_fragment_shader)
{
LOG("PixelPipe: Configuring for R200 fragment shaders\n");
pp_ati_fs::init();
reconfigure=pp_ati_fs::reconfigure;
kill=pp_ati_fs::kill;
suspend=pp_ati_fs::disable_fragment_ops;
resume=pp_ati_fs::restore_fragment_ops;
}
else

if (gl_caps.nv_register_combiners>=2)
{
LOG("PixelPipe: Configuring for NV10 register combiners\n");
pp_nv_rc::init();
reconfigure=pp_nv_rc::reconfigure;
kill=pp_nv_rc::kill;
suspend=pp_nv_rc::disable_fragment_ops;
resume=pp_nv_rc::restore_fragment_ops;
}
else
if (gl_caps.atix_tec3)
{
LOG("PixelPipe: Configuring for R100 combiners\n");
pp_atix_tecombine3::init();
reconfigure=pp_atix_tecombine3::reconfigure;
kill=pp_atix_tecombine3::kill;
suspend=pp_atix_tecombine3::disable_fragment_ops;
resume=pp_atix_tecombine3::restore_fragment_ops;
}
//initially mark all states as changed (everything needs to be set up)
updates=~0;
}

Einfachkrank
2002-12-10, 15:46:47
Originally posted by Einfachkrank
Hi,

ich hab noch mal ne kleine Frage zu den #ifdef Befehlen. Das sind ja quasi Anweisungen an den Präprozessor oder so ähnlich, der dann weiß ob dieser Teil des Codes mit compiled werden soll oder nicht, je nach dem ob das Ding definiert ist oder nicht. So weit richtig?

Jetzt meine Frage, falls das so weit stimmt -> Wie kann ich im Code Sachen definieren, die abhängig sind von der Programmeinstellung. Beispiel: In nem Game soll die eine Option zur Verfügung stehen, mit der man Schatteneffekte ein und ausschalten kann. Jetzt möchte ich, falls sie ausgeschaltet sind, einen State nicht definieren, der dann die Codeteil auslässt, welche für die Projektion von Schatten zuständig wären.(und anders herum solls natürlich auch funzen :D)

Ich hoffe das war klar und deutlich :)

MFG Einfachkrank
Ach, sorry! Mir ist es gerade selbst bewusst geworden, dass diese Logik nicht möglich ist, da ja der Code nicht im Programm enthalten ist, wenn das entsprechende State nicht definiert ist. Ich müsste bei jeder Einstellungsänderung das Programm neu compilen :D :D :D
Also komme ich um if() Abfragen nicht herum, na ja!

MFG Einfachkrank