Dérouler la boucle et faire une sum indépendante avec vectorisation

Pour la boucle suivante, GCC ne vectorisera la boucle que si je lui dis d’utiliser des mathématiques associatives, par exemple avec -Ofast .

 float sumf(float *x) { x = (float*)__builtin_assume_aligned(x, 64); float sum = 0; for(int i=0; i<2048; i++) sum += x[i]; return sum; } 

Voici l’assemblage avec -Ofast -mavx

 sumf(float*): vxorps %xmm0, %xmm0, %xmm0 leaq 8192(%rdi), %rax .L2: vaddps (%rdi), %ymm0, %ymm0 addq $32, %rdi cmpq %rdi, %rax jne .L2 vhaddps %ymm0, %ymm0, %ymm0 vhaddps %ymm0, %ymm0, %ymm1 vperm2f128 $1, %ymm1, %ymm1, %ymm0 vaddps %ymm1, %ymm0, %ymm0 vzeroupper ret 

Cela montre clairement que la boucle a été vectorisée.

Mais cette boucle a aussi une chaîne de dépendance. Afin de surmonter la latence de l’addition, je dois dérouler et faire des sums partielles au moins trois fois sur x86_64 (à l’exception de Skylake qui doit se dérouler huit fois et faire l’ajout des instructions FMA qui doivent se dérouler dix fois sur Haswell et Broadwell). . Autant que je -funroll-loops je peux dérouler la boucle avec -funroll-loops .

Voici l’assemblage avec -Ofast -mavx -funroll-loops .

 sumf(float*): vxorps %xmm7, %xmm7, %xmm7 leaq 8192(%rdi), %rax .L2: vaddps (%rdi), %ymm7, %ymm0 addq $256, %rdi vaddps -224(%rdi), %ymm0, %ymm1 vaddps -192(%rdi), %ymm1, %ymm2 vaddps -160(%rdi), %ymm2, %ymm3 vaddps -128(%rdi), %ymm3, %ymm4 vaddps -96(%rdi), %ymm4, %ymm5 vaddps -64(%rdi), %ymm5, %ymm6 vaddps -32(%rdi), %ymm6, %ymm7 cmpq %rdi, %rax jne .L2 vhaddps %ymm7, %ymm7, %ymm8 vhaddps %ymm8, %ymm8, %ymm9 vperm2f128 $1, %ymm9, %ymm9, %ymm10 vaddps %ymm9, %ymm10, %ymm0 vzeroupper ret 

GCC déroule la boucle huit fois. Cependant, il ne fait pas de sums indépendantes. Il fait huit sums dépendantes. C’est inutile et pas mieux que sans dérouler.

Comment faire en sorte que GCC déroule la boucle et effectue des sums partielles indépendantes?


Modifier:

Clang se déroule à quatre sums partielles indépendantes même sans -funroll-loops pour SSE mais je ne suis pas sûr que son code AVX soit aussi efficace. Le compilateur ne devrait de toute façon pas avoir besoin de -funroll-loops avec -Ofast , il est donc bon de voir que Clang le fait correctement, du moins pour SSE.

Clang 3.5.1 avec -Ofast .

 sumf(float*): # @sumf(float*) xorps %xmm0, %xmm0 xorl %eax, %eax xorps %xmm1, %xmm1 .LBB0_1: # %vector.body movups (%rdi,%rax,4), %xmm2 movups 16(%rdi,%rax,4), %xmm3 addps %xmm0, %xmm2 addps %xmm1, %xmm3 movups 32(%rdi,%rax,4), %xmm0 movups 48(%rdi,%rax,4), %xmm1 addps %xmm2, %xmm0 addps %xmm3, %xmm1 addq $16, %rax cmpq $2048, %rax # imm = 0x800 jne .LBB0_1 addps %xmm0, %xmm1 movaps %xmm1, %xmm2 movhlps %xmm2, %xmm2 # xmm2 = xmm2[1,1] addps %xmm1, %xmm2 pshufd $1, %xmm2, %xmm0 # xmm0 = xmm2[1,0,0,0] addps %xmm2, %xmm0 retq 

ICC 13.0.1 avec -O3 déroule en deux sums partielles indépendantes. ICC suppose apparemment des mathématiques associatives avec seulement -O3 .

 .B1.8: vaddps (%rdi,%rdx,4), %ymm1, %ymm1 #5.29 vaddps 32(%rdi,%rdx,4), %ymm0, %ymm0 #5.29 vaddps 64(%rdi,%rdx,4), %ymm1, %ymm1 #5.29 vaddps 96(%rdi,%rdx,4), %ymm0, %ymm0 #5.29 addq $32, %rdx #5.3 cmpq %rax, %rdx #5.3 jb ..B1.8 # Prob 99% #5.3 

Certaines utilisations des __builtin_ insortingnsèques de gcc et de __builtin_ produisent ceci:

 typedef float v8sf __atsortingbute__((vector_size(32))); typedef uint32_t v8u32 __atsortingbute__((vector_size(32))); static v8sf sumfvhelper1(v8sf arr[4]) { v8sf retval = {0}; for (size_t i = 0; i < 4; i++) retval += arr[i]; return retval; } static float sumfvhelper2(v8sf x) { v8sf t = __builtin_shuffle(x, (v8u32){4,5,6,7,0,1,2,3}); x += t; t = __builtin_shuffle(x, (v8u32){2,3,0,1,6,7,4,5}); x += t; t = __builtin_shuffle(x, (v8u32){1,0,3,2,5,4,7,6}); x += t; return x[0]; } float sumfv(float *x) { //x = __builtin_assume_aligned(x, 64); v8sf *vx = (v8sf*)x; v8sf sumvv[4] = {{0}}; for (size_t i = 0; i < 2048/8; i+=4) { sumvv[0] += vx[i+0]; sumvv[1] += vx[i+1]; sumvv[2] += vx[i+2]; sumvv[3] += vx[i+3]; } v8sf sumv = sumfvhelper1(sumvv); return sumfvhelper2(sumv); } 

Quel gcc 4.8.4 gcc -Wall -Wextra -Wpedantic -std=gnu11 -march=native -O3 -fno-signed-zeros -fno-trapping-math -freciprocal-math -ffinite-math-only -fassociative-math -S se transforme en:

 sumfv: vxorps %xmm2, %xmm2, %xmm2 xorl %eax, %eax vmovaps %ymm2, %ymm3 vmovaps %ymm2, %ymm0 vmovaps %ymm2, %ymm1 .L7: addq $4, %rax vaddps (%rdi), %ymm1, %ymm1 subq $-128, %rdi vaddps -96(%rdi), %ymm0, %ymm0 vaddps -64(%rdi), %ymm3, %ymm3 vaddps -32(%rdi), %ymm2, %ymm2 cmpq $256, %rax jne .L7 vaddps %ymm2, %ymm3, %ymm2 vaddps %ymm0, %ymm1, %ymm0 vaddps %ymm0, %ymm2, %ymm0 vperm2f128 $1, %ymm0, %ymm0, %ymm1 vaddps %ymm0, %ymm1, %ymm0 vpermilps $78, %ymm0, %ymm1 vaddps %ymm0, %ymm1, %ymm0 vpermilps $177, %ymm0, %ymm1 vaddps %ymm0, %ymm1, %ymm0 vzeroupper ret 

La deuxième fonction d'aide n'est pas ssortingctement nécessaire, mais la sum des éléments d'un vecteur a tendance à produire un code terrible en gcc. Si vous êtes prêt à utiliser des __builtin_ia32_hadps256() insortingnsèques dépendants de la plate-forme, vous pouvez probablement les remplacer en grande partie par __builtin_ia32_hadps256() .