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 verschieben 😉

    Ich 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+i
    32)

    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 0x1f

    extern __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) %kA

    sehe? 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) %kA

    sehe? 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) %kA

    sehe? 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.


Anmelden zum Antworten