GCC SSE Inline assembler funktioniert nicht
-
Hallo, ich habe ein problem mit dem GCC Inline Assembler und hoffe, daß Ihr mir helfen könnt. Bin gerade dabei mir einige Vektor-Klassen zu schreiben, welche über inline assembler die volle Geschwindigkeit der SSE-Instruktionen nutzen sollen. Leider gibt es bereits bei der ersten Klase und ersten Methode Probleme...gcc compiliert zwar einwandtfrei, jedoch ist das Resultat fehlerhaft.
Definition:
class Vec2 { protected: float v[2] __attribute__((aligned(16))); public: float& x; float& y; Vec2(); Vec2(const Vec2& vec); Vec2(float X, float Y); ... Vec2& operator+=(float s); };
Implementation:
inline Vec2& Vec2::operator+=(float s) { float t[2] = {s, s}; asm ( "movlps xmm0, [%1]\n" "movlps xmm1, [%2]\n" "addps xmm0, xmm1\n" "movlps [%0], xmm0\n" : "=r" (v) : "r" (v), "r" (t) ); return *this; }
Test-code:
Vec2 c(1.0f, 2.0f); c += 3.0f; printf("C: [ %f %f ]\n", c.x, c.y);
Resultat:
C: [ 0.000000 0.000000 ]
Aus der SSE-Doku geht hervor, das "movlps" KEIN 16 Byte Alignment benötigt, daher dürfte es mit dem Array "t" auch keine Probleme geben. Endianess is Little-Endian, also auch von den Adressen her kein Fehler. Auch das Ändern des "Clobbings" in "=rm" bzw. "rm" oder "g" bringt kein korrektes Resultat. Auch bei Compilierung OHNE Code-Optimierung läuft das Programm nicht korrekt.
Hoffe, daß mir hier jemand weiter helfen kann.Gruss
Marc
-
Ich glaube, da solltest du lieber im Assembler-Forum fragen.
Der GCC erstellt übrigens auch selber Code für SSE, wenn man ihm denn erlaubt Code zu erstellen der hinterher nicht auf einem 386er laufen würde:
http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.htmlWenn du nicht gerade ganz pfiffige SSE-Algorithmen hast, würde ich mein Geld bei dieser Art der Optimierung eindeutig auf den Compiler setzen. Wahrscheinlich würde der Buchmacher solch eine Wette noch nicht einmal annehmen, weil niemand auf den Menschen setzen würde.
-
Wär schön, wenn es so wäre, aber für die Funktion:
Vec2& Vec2::operator+=(float s) { x += s; y += s; return *this; }
...geneneriert mir GCC:
004013B0 push %ebp 004013B1 mov %esp,%ebp 004013B3 mov 0x8(%ebp),%eax 004013B6 movss 0xc(%ebp),%xmm0 004013BB mov 0x8(%eax),%edx 004013BE movss (%edx),%xmm1 004013C2 addss %xmm0,%xmm1 004013C6 movss %xmm1,(%edx) 004013CA mov 0xc(%eax),%edx 004013CD addss (%edx),%xmm0 004013D1 movss %xmm0,(%edx) 004013D5 pop %ebp 004013D6 ret
...und das sind 2x "addss"...ich würde das ganze allerdings gerne über die SIMD-Funktionen von GCC lösen, also "addps". Den Code generiert mir GCC 4.5 übrigens bei "-msse -msse2 -mfpmath=sse"...dachte ehrlich, daß GCC etwas besser optimieren würde.
Trotzdem danke für die schnelle Antwort.
-
Nimm SSE-Intrinsics, Inlineassembly ist meist nur für graue Haare gut.
#include <xmmintrin.h> class Vec2 { protected: float v[2]; public: Vec2() {v[0]=0;v[1]=0;} Vec2(float X, float Y) {v[0]=X;v[1]=Y;} Vec2& operator+=(float s) { __m128 ps=_mm_set1_ps(s); __m128 pv=_mm_loadl_pi(_mm_setzero_ps(),(__m64*)v); pv=_mm_add_ps(ps,pv); _mm_storel_pi((__m64*)v,pv); return *this; } };
Test:
const int vc=1000000; Vec2* vecs=new Vec2[vc+1]; if ((int)vecs%16!=0)vecs++; assert((int)vecs%16==0); for (int i=0;i<vc;i++)vecs[i]=Vec2(i,i+2); uint start=mclock(); for (int x=0;x<1000;x++)for (int i=0;i<vc;i++)vecs[i]+=3.5; uint end=mclock(); cout << end-start << " ms" << endl;
Ergebnis: 2516 ms.
Generierter Code für die innere Schleife:
.L13: leal (%ebx,%eax), %edx movaps %xmm1, %xmm0 movlps (%edx), %xmm0 addps %xmm2, %xmm0 movlps %xmm0, (%edx) addl $8, %eax cmpl $8000000, %eax jne .L13
Nun ein Test mit:
Vec2& operator+=(float s) { v[0] += s; v[1] += s; return *this; }
Ergebnis: 2260 ms.
Generierter Code:
.L15: movaps %xmm1, %xmm0 movlps (%eax), %xmm0 movhps 8(%eax), %xmm0 addps %xmm2, %xmm0 movaps %xmm0, (%eax) addl $16, %eax cmpl %ebx, %eax jne .L15
Man beachte: zwei Vec2 werden pro Durchgang bearbeitet. Statt der ersten drei Instruktionen hätte es leider auch ein movaps getan, aber gcc kann sich nicht sicher sein, dass die Adresse 16 byte aligned ist.
Edit: obwohl... beim zweiten movaps schämt er sich dann doch nicht... naja.Wenn man da noch manuell nachhilft, werden es 2085 ms.
Wenn man vecs auf dem Stack anlegt, geht folgendes:
Vec2 __attribute__((aligned(16))) vecs[vc];
und daraufhin wird auch perfekter Code produziert:
.L7: movaps (%eax), %xmm0 addps %xmm1, %xmm0 movaps %xmm0, (%eax) addl $16, %eax cmpl %ebx, %eax jne .L7
aber ob und wie man dem gcc verklickern kann, dass ein Zeiger auf aligned memory zeigt, weiß ich nicht.
-
Erstens: Vielen vielen Dank für diese überaus detaillierte Antwort. Werde mich jetzt mit den XMM-Intrinsics anfreunden, auch, wenn diese laut GNU schon deprecated sind (Man soll die neuen vector-attribute verwenden, mit denen jedoch der GCC 3.5 als auch der GCC 4.5 nur Fehler machen (movaps mit unaligned memory etc.) und es kommt bei etwas grösseren Projekten fast immer zum Laufzeitfehler).
Zweitens: Dem compiler sagen, daß ein Zeiger 16 Byte aligned ist, ist eigentlich sehr einfach...machst es schon selbst..beispiel:
float v[4] __attribute__((aligned(16)));
Damit wird fest gelegt, daß das Array 16 Byte aligned angelegt wird und jeder Zeiger auf das Array ebenfalls auf 16 Byte aligned memory zeigt.
Gruss und nochmal vielen Dank
-
Damit wird fest gelegt, daß das Array 16 Byte aligned angelegt wird und jeder Zeiger auf das Array ebenfalls auf 16 Byte aligned memory zeigt.
Ja, aber das gilt nur, wenn man nicht mit new arbeitet.
Deswegen ist ein Weg nötig, selbst "Zeiger auf 16 byte-aligned Vec2" deklarieren zu können.
-
Ja, auf diese Problem bin ich nun auch gestossen, jedoch liess es sich sehr elegant über das Überladen der operatoren "new" und "new[]", bzw. "delete" und "delete[]" lösen...zur Reservierung des Speichers muss man dann lediglich einen Puffer mit genügend Platz für die Vektoren + 16 Byte anlegen und die eigentliche Anfangs-Adresse auf die nächst-beste "aligned"-Adresse legen.
MFG
Marc
-
Dieser Thread wurde von Moderator/in pumuckl aus dem Forum C++ in das Forum Compiler- und IDE-Forum verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
Als Antwort auf die ursprüngliche Frage schlage ich ein Zitat aus
http://tldp.org/HOWTO/Assembly-HOWTO/gas.html vor:The order of operands is source(s) first, and destination last...
Zu deutsch: die Operanden-Reihenfolge ist Quelle zuerst, Ziel am Ende
Schon der Anfang des ASM-codes erscheint damit etwas "fragwürdig":
asm ( "movlps xmm0, [%1]\n" "movlps xmm1, [%2]\n"
-
Zu deutsch: die Operanden-Reihenfolge ist Quelle zuerst, Ziel am Ende
Das ist korrekt für AT&T-Syntax, aber wie Du vielleicht übersehen hast, benutze ich die Intel-Syntax, welche man bei dem GCC-Compiler einstellen kann, da ich mit der Intel-Syntax praktisch "gross geworden" bin und mich mit der AT&T-Syntax überhaupt nicht anfreunden kann.