AVX - Intrinsics GCC
-
Ich bin mir nicht ganz sicher, in welches Forum dieser Beitrag passt.
Ist eine Mischung aus Assembler und C++. Im Notfall bitte verschiebenIch versuche eine einfache Vektoraddition in C/C++ mit AVX durchzuführen.
Ich lege dazu 3 Arrays a b und c and, die ich mit memalign(, 32) allokiere.Hier ist schonmal die erste Frage:
Ich habe gelesen, dass das Alignment für AVX nicht so wichtig ist (besser gesagt: alignment-restrictions = relaxed)Macht es also einen Unterschied ob ich normal new/malloc verwende ?
Nun zur Addition, die ich mit folgender Funktion verwirklichen will:
void add_vector(double *a, double *b, double *c, const unsigned int& N) { // N is supposed to be multiple of 4 by now __m256d *av, *bv, *cv; av = (__m256d *) a; // assume 32-byte aligned bv = (__m256d *) b; // assume 32-byte aligned cv = (__m256d *) c; // assume 32-byte aligned for (unsigned int i = 0; i < N / 4; i++) { av[i] = _mm256_add_pd(bv[i], cv[i]); } }
Der Compiler gcc macht mit -mavx und -O0 das daraus:
-Ausgabe von objdump-av[i] = _mm256_add_pd(bv[i], cv[i]); 400e64: 8b 44 24 04 mov 0x4(%rsp),%eax 400e68: 48 89 c2 mov %rax,%rdx 400e6b: 48 c1 e2 05 shl $0x5,%rdx 400e6f: 48 8b 44 24 f8 mov -0x8(%rsp),%rax 400e74: 48 01 d0 add %rdx,%rax 400e77: 8b 54 24 04 mov 0x4(%rsp),%edx 400e7b: 48 89 d1 mov %rdx,%rcx 400e7e: 48 c1 e1 05 shl $0x5,%rcx 400e82: 48 8b 54 24 e8 mov -0x18(%rsp),%rdx 400e87: 48 01 ca add %rcx,%rdx 400e8a: c5 fd 28 02 vmovapd (%rdx),%ymm0 400e8e: 8b 54 24 04 mov 0x4(%rsp),%edx 400e92: 48 89 d1 mov %rdx,%rcx 400e95: 48 c1 e1 05 shl $0x5,%rcx 400e99: 48 8b 54 24 f0 mov -0x10(%rsp),%rdx 400e9e: 48 01 ca add %rcx,%rdx 400ea1: c5 fd 28 0a vmovapd (%rdx),%ymm1 400ea5: c5 fd 29 4c 24 c8 vmovapd %ymm1,-0x38(%rsp) 400eab: c5 fd 29 44 24 a8 vmovapd %ymm0,-0x58(%rsp)
Meine Assemblerkenntnisse sind zwar nicht überragend, aber wenn ich das richtig
deute, wird die Addition auf den 64-Bit Registern %r_x ausgeführt und nicht auf %ymm mittels vaddpd, was eigentlich mein Ziel war.Aber bitte erstmal korrigieren, falls ich da falsch liege. WIe gesagt: Assembler ist nicht meine Stärke.
Falls meine Vermutung stimmt, würde das doch bedeuten, dass der Code <bei weitem> nicht optimal ist.
Wieso wird __mm256_addpd nicht mit vaddpd übersetzt?WIe kann ich das ändern (ohne Compileroptimierungen zu aktivieren)
-
Das kann nicht der ganze Code sein, da dort nur zwei 256Bit Vektoren aus dem Array in eine lokale Variable kopiert werden.
Pseudocode:
(__m256d )(%rsp-0x38) = (__m256d )(%rsp-0x10+i32)
(__m256d )(%rsp-0x58) = (__m256d )(%rsp-0x18+i32)VEX codierten befehle brauchen an sich kein alignment, aber dein Compiler verwendend vmovapd, welches ein alignment von 32 verlangt.
Allgemeine ist es besser eine solche Sache in einem externen Assembler anzugehen.
-
Ja der ganze Code ist das nicht ^^
Hier mal alles was objdump mir zu der Funktion ausgegeben hat:
void add_vector(double *a, double *b, double *c, const unsigned int& N) { 400e1c: 55 push %rbp 400e1d: 48 89 e5 mov %rsp,%rbp 400e20: 48 83 e4 e0 and $0xffffffffffffffe0,%rsp 400e24: 48 83 ec 08 sub $0x8,%rsp 400e28: 48 89 7c 24 a0 mov %rdi,-0x60(%rsp) 400e2d: 48 89 74 24 98 mov %rsi,-0x68(%rsp) 400e32: 48 89 54 24 90 mov %rdx,-0x70(%rsp) 400e37: 48 89 4c 24 88 mov %rcx,-0x78(%rsp) __m256d *av, *bv, *cv; av = (__m256d *) a; // assume 32-byte aligned 400e3c: 48 8b 44 24 a0 mov -0x60(%rsp),%rax 400e41: 48 89 44 24 f8 mov %rax,-0x8(%rsp) bv = (__m256d *) b; // assume 32-byte aligned 400e46: 48 8b 44 24 98 mov -0x68(%rsp),%rax 400e4b: 48 89 44 24 f0 mov %rax,-0x10(%rsp) cv = (__m256d *) c; // assume 32-byte aligned 400e50: 48 8b 44 24 90 mov -0x70(%rsp),%rax 400e55: 48 89 44 24 e8 mov %rax,-0x18(%rsp) for (unsigned int i = 0; i < N / 4; i++) 400e5a: c7 44 24 04 00 00 00 movl $0x0,0x4(%rsp) 400e61: 00 400e62: eb 66 jmp 400eca <_Z10add_vectorPdS_S_RKj+0xae> { av[i] = _mm256_add_pd(bv[i], cv[i]); 400e64: 8b 44 24 04 mov 0x4(%rsp),%eax 400e68: 48 89 c2 mov %rax,%rdx 400e6b: 48 c1 e2 05 shl $0x5,%rdx 400e6f: 48 8b 44 24 f8 mov -0x8(%rsp),%rax 400e74: 48 01 d0 add %rdx,%rax 400e77: 8b 54 24 04 mov 0x4(%rsp),%edx 400e7b: 48 89 d1 mov %rdx,%rcx 400e7e: 48 c1 e1 05 shl $0x5,%rcx 400e82: 48 8b 54 24 e8 mov -0x18(%rsp),%rdx 400e87: 48 01 ca add %rcx,%rdx 400e8a: c5 fd 28 02 vmovapd (%rdx),%ymm0 400e8e: 8b 54 24 04 mov 0x4(%rsp),%edx 400e92: 48 89 d1 mov %rdx,%rcx 400e95: 48 c1 e1 05 shl $0x5,%rcx 400e99: 48 8b 54 24 f0 mov -0x10(%rsp),%rdx 400e9e: 48 01 ca add %rcx,%rdx 400ea1: c5 fd 28 0a vmovapd (%rdx),%ymm1 400ea5: c5 fd 29 4c 24 c8 vmovapd %ymm1,-0x38(%rsp) 400eab: c5 fd 29 44 24 a8 vmovapd %ymm0,-0x58(%rsp) #define _CMP_TRUE_US 0x1f extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__, __artificial__)) _mm256_add_pd (__m256d __A, __m256d __B) { return (__m256d) __builtin_ia32_addpd256 ((__v4df)__A, (__v4df)__B); 400eb1: c5 fd 28 44 24 a8 vmovapd -0x58(%rsp),%ymm0 400eb7: c5 fd 28 4c 24 c8 vmovapd -0x38(%rsp),%ymm1 400ebd: c5 f5 58 c0 vaddpd %ymm0,%ymm1,%ymm0 400ec1: c5 fd 29 00 vmovapd %ymm0,(%rax) __m256d *av, *bv, *cv; av = (__m256d *) a; // assume 32-byte aligned bv = (__m256d *) b; // assume 32-byte aligned cv = (__m256d *) c; // assume 32-byte aligned for (unsigned int i = 0; i < N / 4; i++) 400ec5: 83 44 24 04 01 addl $0x1,0x4(%rsp) 400eca: 48 8b 44 24 88 mov -0x78(%rsp),%rax 400ecf: 8b 00 mov (%rax),%eax 400ed1: c1 e8 02 shr $0x2,%eax 400ed4: 3b 44 24 04 cmp 0x4(%rsp),%eax 400ed8: 0f 97 c0 seta %al 400edb: 84 c0 test %al,%al 400edd: 75 85 jne 400e64 <_Z10add_vectorPdS_S_RKj+0x48> { av[i] = _mm256_add_pd(bv[i], cv[i]); } } 400edf: c9 leaveq 400ee0: c3 retq
Wenn mir jemand das Wichtigste davon erläutern könnte, wäre das schon sehr hilfreich.
Mich verwirrt vor allem
#define _CMP_TRUE_US 0x1fextern __inline __m256d __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm256_add_pd (__m256d __A, __m256d __B)
...wird das tatsächlich ausgeführt? Oder ist das nur eine Definition?
Noch eines:
Was hat es zu bedeuten wenn ich Befehle wie
(%kA, %kA, 1) %kAsehe? ALso speziell jetzt die Klammerung im ersten AUsdruck zusammen mit der 1.
Finde leider kein Beispiel grad dazu.Aber erstmal danke an masm
-
Der Disassembler gibt dir den Quelltext und den draus resultierend Code aus - #define, extern, ... sind nicht Bestandteils des fertigen Codes. Die Bedeutung der macros solltest du in der Dokumentation von GCC Nachschalgen oder in die Kommentare der entsprechend Headern schauen (sofern Kommentiert).
Der Code der Operation VADDPD dest,a,b ist ineffizient, da die Operanden a und b in eine lokale Variable zwischen gespeichert werden:400ea5: c5 fd 29 4c 24 c8 vmovapd %ymm1,-0x38(%rsp) 400eab: c5 fd 29 44 24 a8 vmovapd %ymm0,-0x58(%rsp) 400eb1: c5 fd 28 44 24 a8 vmovapd -0x58(%rsp),%ymm0 400eb7: c5 fd 28 4c 24 c8 vmovapd -0x38(%rsp),%ymm1 400ebd: c5 f5 58 c0 vaddpd %ymm0,%ymm1,%ymm0
Aktivier die Code Optimierung !
shisha schrieb:
Wenn mir jemand das Wichtigste davon erläutern könnte, wäre das schon sehr hilfreich.
400e5a: c7 44 24 04 00 00 00 movl $0x0,0x4(%rsp) ; initialisiere Zählervariable i 400e61: 00 400e62: eb 66 jmp 400eca ; springe zu Prüfung der Schleifenbedingung ; berechne Adresse zu aktuellen Element in Array a 400e64: 8b 44 24 04 mov 0x4(%rsp),%eax ; rax = i 400e68: 48 89 c2 mov %rax,%rdx 400e6b: 48 c1 e2 05 shl $0x5,%rdx ; i*32 400e6f: 48 8b 44 24 f8 mov -0x8(%rsp),%rax ; a+i*32 ; berechen Adresse zu aktuellen Element in Array b 400e74: 48 01 d0 add %rdx,%rax 400e77: 8b 54 24 04 mov 0x4(%rsp),%edx ; rdx = i 400e7b: 48 89 d1 mov %rdx,%rcx 400e7e: 48 c1 e1 05 shl $0x5,%rcx ; i*32 400e82: 48 8b 54 24 e8 mov -0x18(%rsp),%rdx 400e87: 48 01 ca add %rcx,%rdx ; b+i*32 ; ymm0 = bv[i] 400e8a: c5 fd 28 02 vmovapd (%rdx),%ymm0 ; berechne Adresse zu aktuellen Element in Array c 400e8e: 8b 54 24 04 mov 0x4(%rsp),%edx ; rdx = i 400e92: 48 89 d1 mov %rdx,%rcx 400e95: 48 c1 e1 05 shl $0x5,%rcx ; i*32 400e99: 48 8b 54 24 f0 mov -0x10(%rsp),%rdx 400e9e: 48 01 ca add %rcx,%rdx ; c+i*32 400ea1: c5 fd 28 0a vmovapd (%rdx),%ymm1 ; ymm1 = cv[i] ; sinnfreies Kopieren (ymm1/0 ändern sich nicht) 400ea5: c5 fd 29 4c 24 c8 vmovapd %ymm1,-0x38(%rsp) 400eab: c5 fd 29 44 24 a8 vmovapd %ymm0,-0x58(%rsp) 400eb1: c5 fd 28 44 24 a8 vmovapd -0x58(%rsp),%ymm0 400eb7: c5 fd 28 4c 24 c8 vmovapd -0x38(%rsp),%ymm1 ; ymm0 = bv[i] + cv[i] 400ebd: c5 f5 58 c0 vaddpd %ymm0,%ymm1,%ymm0 ; av[i] = ymm0 400ec1: c5 fd 29 00 vmovapd %ymm0,(%rax) ; i++ 400ec5: 83 44 24 04 01 addl $0x1,0x4(%rsp) ; Schleifenkopf 400eca: 48 8b 44 24 88 mov -0x78(%rsp),%rax ; - lade N 400ecf: 8b 00 mov (%rax),%eax ; / 400ed1: c1 e8 02 shr $0x2,%eax ; N/4 400ed4: 3b 44 24 04 cmp 0x4(%rsp),%eax ; vergleich i mit N/4 ; ab hier wird Sinnlos: CMP liefert bereits die nötige Information für den bedingten Sprung 400ed8: 0f 97 c0 seta %al ; al = (N/4>i)?TRUE:FALSE 400edb: 84 c0 test %al,%al ; 400edd: 75 85 jne 400e64 ; Schleife wiederholen wenn i < N/4
shisha schrieb:
Was hat es zu bedeuten wenn ich Befehle wie
(%kA, %kA, 1) %kAsehe? ALso speziell jetzt die Klammerung im ersten AUsdruck zusammen mit der 1.
Das kann ich dir auch nicht sagen (Befehle?) -> Doku.
-
Nachtrag:
masm schrieb:
shisha schrieb:
Was hat es zu bedeuten wenn ich Befehle wie
(%kA, %kA, 1) %kAsehe? ALso speziell jetzt die Klammerung im ersten AUsdruck zusammen mit der 1.
Das kann ich dir auch nicht sagen (Befehle?) -> Doku.
Das könnte zur Adressierung eines Speicheroperanden dienen - AT&T Syntax ist ziemlich vermurkst. Es wäre dann glaub ich: (index register,base register,scale={1,2,4,8}) offset.