[C++] Tipps für Schleifenmakros

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

[C++] Tipps für Schleifenmakros

Beitrag von Krishty »

Hi,

ich habe in meinem Quelltext ca. 200 do-while-Schleifen der Form:

    auto toObject = toBeginningOf(tile.myObjects);
    auto toEndOfObjects = toEndOf(tile.myObjects);
    do {
        result = maximumOf(result, toObject->boundingSphere.radius);
    } while(++toObject < toEndOfObjects);


Da sind, wie man sieht, vier der fünf Zeilen für nichts anderes als die Iteration zuständig. Darum wollte ich den Quelltext nun radikal reduzieren indem ich ein FOR_EACH-Makro baue.

Im Augenblick sieht das so aus:

    #define FOR_EACH_NONEMPTY(CONTAINERS_NAME, ITERATORS_NAME, STATEMENT) \
        { \
            auto ITERATORS_NAME = toBeginningOf(CONTAINERS_NAME); \
            auto ITERATORS_NAME##End = toEndOf(CONTAINERS_NAME); \
            do \
                STATEMENT \
            while(++ITERATORS_NAME < ITERATORS_NAME##End); \
        }


und damit reduziert sich obige Schleife zu:

    FOR_EACH_NONEMPTY(tile.myObjects, toObject, {
        result = maximumOf(result, toObject->boundingSphere.radius);
    })


So weit, so gut. Nun habe ich aber Schleifen mit nicht nur einem, sondern zwei oder drei oder vier Iteratoren. Außerdem welche, wo Iteratoren nicht in jedem Schleifendurchlauf inkrementiert werden. Zusätzlich manchmal break, continue, und goto.

Bevor ich das anpacke, und zweihundert Schleifen kaputtmache weil ich mittendrin merke, dass es nicht geht, wollte ich euch fragen, was eure Erfahrungen damit sind und was ich beachten sollte. Also im Bezug auf Scopes, Variable Parameter, usw.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: [C++] Tipps für Schleifenmakros

Beitrag von Sternmull »

Könnte man das vielleicht mit "range based for loop" statt Makro machen? Es dürfte doch nur eine Funkion erfordern die ein temporäres Objekt zurück gibt das deine toBeginningOf und toEndOf hinter begin- und end-Memberfunktionen versteckt. Dann müsste ja eigentlich so was gehen:

Code: Alles auswählen

template <class T>
class Iterable
{
	T & x;
public:
	Iterable(T & x) : x(x){}
	T::iterator begin() { return toBeginningOf(x); }
	T::iterator end() { return toEndOf(x); }
};


template <class T>
Iterable<T> WrapBeginEnd(T & c)
{
	return Iterable<T>(c);
}

Foo()
{
	auto result = 0;
	for (auto & x : WrapBeginEnd(tile.myObjects))
	{
		result = maximumOf(result, x.boundingSphere.radius);
	}
}
Ich war jetzt zu faul das wirklich mal auszuprobieren, aber eigentlich sollte das doch im Groben so funktionieren. Natürlich müsste man dann auch mal gucken was der Compiler daraus macht. Aber mir perönlich würde das besser gefallen als so ein Makro.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Tipps für Schleifenmakros

Beitrag von Krishty »

Dafür würde sogar einfach reichen, meine Funktionen in begin() und end() umzubenennen. Eine for-Schleife produziert aber leider suboptimalen Maschinentext, darum brauche ich do-while. Auch erlaubt eine range-based for nur einen Iterator; die anderen müsste ich von Hand im Befehlsrumpf inkrementieren :(

Nachtrag: Ja; for produziert gegenüber do-while glatt ein Drittel mehr Befehle. Sogar wenn ich explizit nur auf den Variablen operiere und mit Compiler-Intrinsics auf den Compiler einhacke, dass das Array nicht leer sein kann, ändert sich nicht ein Mnemonic.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: [C++] Tipps für Schleifenmakros

Beitrag von Sternmull »

Die mehreren Iteratoren könnte man bequem in WrapBeginEnd() unterbringen indem man Überladungen für mehr als ein Argument anbietet. Der Wrapper könnte dann N-Tupel generieren wobei, N die Anzahl der übergebenen Container ist. Damit würde man recht elegant über mehrere Container iterieren können.
Aber wenn es unbedingt darum geht einen Weg zu wählen bei dem mit deiner aktuellen Compilerversion der beste Code generiert wird, dann kann das natürlich ungeeignet sein.

Du könntest auch mal versuchen die while-Schleife in eine Funktion zu stecken und den Schleifenkörper per Lambda zu erzeugen und an diese Funktion zu übergeben. Es ist zwar zu befürchten das dort wieder nicht richtig optimiert wird, aber zumindest bleibt erst mal die while-Schleife erhalten :)
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Tipps für Schleifenmakros

Beitrag von Krishty »

Für Lambdas kann man kein Inlining erzwingen – jede Schleife, die über ein paar Zeilen hinausgeht, würde also deutlich degradiert.

Trotzdem hat die Überladung den Vorteil, dass … man überladen kann. Mit Makros ist das ja quasi unmöglich, und ich müsste mit FOR_EACH_1 und FOR_EACH_2 anfangen … mal probieren, wie weit ich komme.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: [C++] Tipps für Schleifenmakros

Beitrag von Sternmull »

Du könntest auch einen Mix machen: Mit "Wrapper-Objekt" und Makro, aber ohne Lambda. Dann könnte der Wrapper die Überladung übernehmen und es würde nur ein Makro geben was nichts weiter macht als die while-Schleife zu erstellen. Bleibt natürlich noch offen wie gut der Wrapper weg optimiert wird.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Tipps für Schleifenmakros

Beitrag von Krishty »

Cat hatte mir gerade vorgeschlagen:

    for(bool continueLooping = true; continueLooping; ++toObject, continueLooping = toObject < toEndOfObjects) {

Das bleibt ähnlich einem do-while, aber Visual Studio baut dann zusätzlich zum Iterator noch einen Zähler ein, mit dem die Schleifenbedingung abgearbeitet wird. Es ist ein Trauerspiel.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

Re: [C++] Tipps für Schleifenmakros

Beitrag von kaiserludi »

Mir stellen sich langsam diese beiden Fragen bei deinen ganzen Optimierungen: Brauchst du den Performanceunterschied wirklich? Wenn ja, warum nutzt du dann nicht einen Compiler, der besser optimiert?
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Tipps für Schleifenmakros

Beitrag von Krishty »

kaiserludi hat geschrieben:Brauchst du den Performanceunterschied wirklich?
Abgesehen davon, dass ich dabei massig lerne und auch gerne flüssig auf meinem Netbook zocke, nein.
kaiserludi hat geschrieben:Wenn ja, warum nutzt du dann nicht einen Compiler, der besser optimiert?
Der Maschinentext ist nur so gut wie man ihn artikuliert. do-while wird auf fast allen Compilern schneller sein als for. Außerdem kann man den erzeugten Maschinentext besser in eine bestimmte Richtung lenken.

Ich würde ja auch gern in LLVM IR programmieren, aber so weit bin ich nunmal noch nicht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten