Разверните цикл и сделайте независимую сумму с векторизацией
Для следующего цикла GCC будет векторизовать цикл, только если я скажу ему использовать ассоциативную математику, например. с-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;
}
Вот сборка с-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
Это ясно показывает, что цикл был векторизован.
Но этот цикл также имеет цепочку зависимостей. Чтобы преодолеть задержку сложения, мне нужно развернуть и сделать частичные суммы как минимум три раза на x86_64 (исключая Skylake, который нужно развернуть восемь раз, и выполнить сложение с инструкциями FMA, которые нужно развернуть 10 раз на Haswell и Broadwell) , Насколько я понимаю, я могу развернуть цикл с-funroll-loops
.
Вот сборка с-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 разматывает цикл восемь раз. Тем не менее, он не делает независимых сумм. Это делает восемь зависимых сумм. Это бессмысленно и не лучше, чем без развертывания.
Как я могу заставить GCC развернуть цикл и сделать независимые частичные суммы?
Редактировать:
Clang раскатывается до четырех независимых частичных сумм даже без-funroll-loops
для SSE, но я не уверен, что его код AVX столь же эффективен. Компилятор не должен нуждаться-funroll-loops
с-Ofast
в любом случае, так приятно видеть, что Clang делает это правильно, по крайней мере, для SSE.
Лязг 3.5.1 с-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 с-O3
раскатывается до двух независимых частичных сумм. ICC, по-видимому, предполагает ассоциативную математику только с-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