[C++] Swizzling implementatie operators

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Een tijdje geleden was ik geintereseerd geraakt om swizzling te implementeren in C++.

Ik weet dat er methoden zijn met templates die prima werken, maar die naar mijn mening niet zo'n mooie syntax hebben.

in GLSL kun je het volgende doen:
C++:
1
2
vec3 a, b;
a.xyz = b.xxy;

Dat wil ik dus nabootsen in C++. Na wat zoeken kwam ik uit bij ene Dwight en die had een implementatie die mij wel aansprak.

Met dat als basis ben ik verder gegaan en heb inmiddels een redelijk werkende opzet. Momenteel genereer ik de klasse met een klein programma, dit omdat er voor elke optie (xxx, xxy, xxz etc ) een member gemaakt moet worden. Op zich misschien niet zo elegant, maar dit is eenmalig en het stelt de gebruiker van de klasse vervolgens instaat om het GLSL voorbeeld van boven 1-op-1 over te nemen naar C++, terwijl het ook nog zeer efficiënt is.
Daarnaast is de klasse nu gemaakt op integers, maar dit kan natuurlijk relatief eenvoudig omgezet worden naar een template.

De basis van de klasse is als volgt:
C++:
1
2
3
4
5
6
7
8
union Point
{
    int x, y, z;
    struct ZYX
    {
        int z,y,x;
    } zyx;
}

Voor een punt met de waarden x=1, y=2, z=3 zijn de x en z members precies omgedraait in de member zyx.
De members z,y,x in de struct ZYX zou je ook kunnen lezen als 'eerste-, tweede- en derde member'.
Hierdoor kun je de volgende operator definiëren:
C++:
1
2
3
4
template < class A, class B > A operator * ( const A &a, const B &b )
{
    return A( a.x * b.x, a.y * b.y, a.z * b.z );
}

Waardoor het volgende mogelijk wordt*:
C++:
1
2
Point a( 1, 2, 3 );
Point b = a * a.zyx;

* constructor en assignment operator ontbreken, gaat om het idee

Zie hier voor een meer volledige versie van deze klasse

Nu gaat het mij om het implementeren van de benodigde operators. De huidige vermenigvuldiging operator is erg generiek, maar weer niet zo generiek dat deze geschikt is om
C++:
1
a.xy * b.xy

te ondersteunen.

Graag zou ik met jullie brainstormen hoe deze operators het beste geïmplementeerd kunnen worden :)

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
In een poging een mogelijke discussie aan te zwengelen:

Ik heb een methode bedacht die praktisch gezien werkt, deze maakt gebruik van een typedef en sfinae om te checken of de members een 'z' member hebben. Zo ja, dan wordt de operatie ook uitgevoerd voor de 'z' member, zo nee, dan worden slechts de 'x' en de 'y' members bewerkt.

Dit laat echter wel een hele serie generieke operators bestaan, namelijk
C++:
1
template < class A, class B > operator [*/+- etc..] ( const A&, const B& );


Hoewel deze operator alleen maar werkt voor klassen die een x, y en optioneel een z member hebben, zal de compiler deze optie natuurlijk wel altijd overwegen.

Is er een methode om ervoor te zorgen dat de compiler alleen maar mijn eigen types accepteert voor deze operator? Of weet iemand een beter alternatief?

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Om de discussie met mezelf niet uit de weg te gaan;

Ik heb een nieuwe versie van de klasse gemaakt, deze is hier te vinden: http://houbenweb.nl/Point_v2.h

Op deze pagina heb ik een manier gevonden om operators te implementeren die alleen voor mijn eigen gegenereerde klassen werken: http://www.mpi-inf.mpg.de...design_03/notes/meta.html

Ik moet nog operators toevoegen om vermenigvuldiging met built-in types te faciliteren, dan is de klasse functioneel wel af.

Ik zit echter nog met een probleem, om een of andere reden is deze nieuwe klasse niet zo snel als hij eerder was, ik heb de boel geprofiled en er komt niks geks uit... Als iemand daar nog een idee over heeft dan hoor ik dat ook graag!

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 20-09 11:26

Bob

Interressant.

Ik heb heel snel een testje gedaan:

C++:
1
2
3
    Point test(1,2,3);

    std::cerr << test.xxy << std::endl;


Geeft als output 1,1,3 ...
Niet de bedoeling zou ik denken? Heb nog niet hard gezocht, ik moet eerst m'n union kennis opfrissen, ik gebruik dat nooit. :)

edit:
er zal een foutje in je class generator zitten? De fix is logisch en eenvoudig voor dit specifiek geval:
C++:
1
2
3
4
5
    struct XXY
    {
        union { int x, y; };
        int padding, z;
    } xxy;

moet zijn:
C++:
1
2
3
4
5
    struct XXY
    {
        union { int x, y; };
        int z, padding;
    } xxy;

[ Voor 34% gewijzigd door Bob op 14-12-2010 23:02 ]


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
arg, ja dat was wel stom. Voor de classes waarbij de eerste twee waarden gelijk zijn werd de derde niet goed gezet.. gefixt en geupload :)

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Arjan schreef op maandag 13 december 2010 @ 09:39:
Is er een methode om ervoor te zorgen dat de compiler alleen maar mijn eigen types accepteert voor deze operator? Of weet iemand een beter alternatief?
Je kunt ze categorizeren met behulp van superclasses.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class T> struct MySpecialCategory
{
    typedef T type;
};
template<class T> T & Get(MySpecialCategory<T> & c) { return static_cast<T&>(c); }
template<class T> const T & Get(const MySpecialCategory<T> & c) { return static_cast<const T&>(c); }

struct Foo : MySpecialCategory<Foo> { int bla; };
struct Bar : MySpecialCategory<Bar> { int bla; };

template<class T> T operator+(const MySpecialCategory<T> & a, const MySpecialCategory<T>  & b)
{
    T t;
    t.bla = Get(a).bla + Get(b).bla;
    return t;
}


die operator+() werkt nu op zowel Foo als Bar, ookal zijn de twee types compleet ongerelateerd.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
Die methode heeft wel een performance penalty lijkt mij, omdat er inheritance gebruikt wordt.

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ga maar eens uitleggen waarom dan precies ;)

Trouwens, als je performance belangrijk vind dan moet je deze hele methode overboord gooien, want dan ga je SSE intrinsics gebruiken voor je vectoren, en ga je swizzlen met _mm_shuffle_ps(). In mijn eigen (SSE optimized) math lib heb ik de identifiers _Z, _Y, _Z en _W, en een overloaded operator,(). Uit het resultaat komt een compile-time constant wat als mask dient voor de _mm_shuffle_ps() functie. De uiteindelijke syntax ziet er dan zo uit:

C++:
1
2
3
4
Vector3 a(1,2,3);

Vector3 b = a[_Z,_Z,_X];  // (3,3,1)
Vector2 c = a[_Y,_Z];  // (2,3)

[ Voor 111% gewijzigd door .oisyn op 15-12-2010 15:52 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
.oisyn schreef op woensdag 15 december 2010 @ 15:47:
Ga maar eens uitleggen waarom dan precies ;)
Het lijkt mij dat er de Foo en Bar klasse informatie over hun base klasse opgeslagen moet zijn, maar wellicht is de compiler slim genoeg om te zien dat dit runtime niet nodig is.
Trouwens, als je performance belangrijk vind dan moet je deze hele methode overboord gooien, want dan ga je SSE intrinsics gebruiken voor je vectoren, en ga je swizzlen met _mm_shuffle_ps(). In mijn eigen (SSE optimized) math lib heb ik de identifiers _Z, _Y, _Z en _W, en een overloaded operator,(). Uit het resultaat komt een compile-time constant wat als mask dient voor de _mm_shuffle_ps() functie. De uiteindelijke syntax ziet er dan zo uit:

C++:
1
2
3
4
Vector3 a(1,2,3);

Vector3 b = a[_Z,_Z,_X];  // (3,3,1)
Vector2 c = a[_Y,_Z];  // (2,3)
Ik vind performance belangrijk, maar het is niet de heilige graal. Als ik wat performance moet inleveren voor een meer gebruiksvriendelijke interface dan doe ik dat, maar de verhouding moet kloppen :)

oprecht vertrouwen wordt nooit geschaad


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Arjan schreef op woensdag 15 december 2010 @ 18:38:
Het lijkt mij dat er de Foo en Bar klasse informatie over hun base klasse opgeslagen moet zijn, maar wellicht is de compiler slim genoeg om te zien dat dit runtime niet nodig is.
Empy base class optimization is toegestaan hier.
.oisyn schreef op woensdag 15 december 2010 @ 15:47:
Uit het resultaat komt een compile-time constant wat als mask dient voor de _mm_shuffle_ps() functie. De uiteindelijke syntax ziet er dan zo uit:
Zeg maar zoiets: http://www.devmaster.net/...t.php?p=59323&postcount=5 ? ;)

[ Voor 33% gewijzigd door Zoijar op 15-12-2010 19:38 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Arjan schreef op woensdag 15 december 2010 @ 18:38:
[...]

Het lijkt mij dat er de Foo en Bar klasse informatie over hun base klasse opgeslagen moet zijn, maar wellicht is de compiler slim genoeg om te zien dat dit runtime niet nodig is.
Wanneer heb je die informatie nodig dan? RTTI werkt alleen op types met minstens 1 virtual functie. De type info volgt dan ook in de praktijk meestal uit de vtable, en de klasse zoals ik die voorstelde heeft geen vtable. Kan ook niet, want dan mag je 'm niet eens in een union stoppen.

Maar misschien moet je gewoon eens naar de gegenereerde code kijken voordat je dergelijke uitspraken doet ;). Pas dan kun je iets zeggen over de performance impact van een stuk code. Anders blijft het alleen maar giswerk op basis van wellicht foute aannames.

[ Voor 9% gewijzigd door .oisyn op 16-12-2010 10:06 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Arjan
  • Registratie: Juni 2001
  • Niet online

Arjan

copyright is wrong

Topicstarter
.oisyn schreef op donderdag 16 december 2010 @ 10:05:
[...]

Wanneer heb je die informatie nodig dan? RTTI werkt alleen op types met minstens 1 virtual functie. De type info volgt dan ook in de praktijk meestal uit de vtable, en de klasse zoals ik die voorstelde heeft geen vtable. Kan ook niet, want dan mag je 'm niet eens in een union stoppen.

Maar misschien moet je gewoon eens naar de gegenereerde code kijken voordat je dergelijke uitspraken doet ;). Pas dan kun je iets zeggen over de performance impact van een stuk code. Anders blijft het alleen maar giswerk op basis van wellicht foute aannames.
Voordat ik keuzes maak over een daadwerkelijke implementatie profile ik altijd eerst een testopzet, maar in mijn voorselectie was een dergelijke opzet inderdaad gesneuveld op basis van aannames.

Ik moet zeggen dat ik redelijk tevreden ben over het resultaat van versie 3

De performance moet ik nog controleren, al leken eerste tests uit te wijzen dat het niet extreem snel was..

Wat inheritance betreft, zou het volgende stukje:
C++:
1
2
3
4
5
6
7
8
9
10
11
struct p2
{
    p2( int a, int b ) : x( a ), y( b ) { };
    int x, y;
};

struct p3 : public p2
{
    p3( int a, int b, int c ) : p2( a, b ), z( c ) { }
    int z;
};

Dan ook net zo snel kunnen zijn als een struct zonder base klasse en een x,y en z member :?
[edit] voor een simpele instantie leveren ze dezelfde asm op, maar ik kan me haast niet voorstellen dat beide gevallen hetzelfde kunnen worden geoptimaliseerd..

[ Voor 18% gewijzigd door Arjan op 16-12-2010 22:50 ]

oprecht vertrouwen wordt nooit geschaad


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Yup :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1