你看过集会吗?我放
double foo(std::vector<double> &X, std::vector<double> &Y) {
size_t N = X.size();
double sum = 0.0;
for (size_t i = 0; i <N; ++i) sum += X[i] * Y[i];
return sum;
}
进入http://gcc.godbolt.org/并查看 GCC 4.9.2 中的程序集,-O3 -mfma我看到了
.L3:
vmovsd (%rcx,%rax,8), %xmm1
vfmadd231sd (%rsi,%rax,8), %xmm1, %xmm0
addq $1, %rax
cmpq %rdx, %rax
jne .L3
所以它使用fma。但是,它不会对循环进行矢量化(sinsd表示单(即未打包),d表示双浮点)。
要矢量化循环,您需要启用关联数学,例如使用-Ofast. 使用-Ofast -mavx2 -mfma给
.L8:
vmovupd (%rax,%rsi), %xmm2
addq $1, %r10
vinsertf128 $0x1, 16(%rax,%rsi), %ymm2, %ymm2
vfmadd231pd (%r12,%rsi), %ymm2, %ymm1
addq $32, %rsi
cmpq %r10, %rdi
ja .L8
所以现在它是矢量化的(pd意味着打包双打)。但是,它没有展开。这是当前 GCC 的一个限制。由于依赖链,您需要展开多次。如果您想让编译器为您执行此操作,请考虑使用 Clang 展开四次,否则使用内部函数手动展开。
请注意,与 GCC 不同,Clang 默认情况下不使用 fma 和-mfma. 为了将 fma 与 Clang 一起使用-ffp-contract=fast(例如-O3 -mfma -ffp-contract=fast)或#pragma STDC FP_CONTRACT ON启用关联数学-Ofast,如果您想使用 Clang 对循环进行矢量化,无论如何您都将想要启用关联数学。
有关使用不同编译器启用 fma 的更多信息,请参阅融合乘加和默认舍入模式和https://stackoverflow.com/a/34461738/2542702 。
GCC 创建了许多额外的代码来处理未对齐而N不是 8 的倍数。您可以告诉编译器假设数组使用对齐__builtin_assume_aligned并且 N 是 8 的倍数使用N & -8
以下代码与-Ofast -mavx2 -mfma
double foo2(double * __restrict X, double * __restrict Y, int N) {
X = (double*)__builtin_assume_aligned(X,32);
Y = (double*)__builtin_assume_aligned(Y,32);
double sum = 0.0;
for (int i = 0; i < (N &-8); ++i) sum += X[i] * Y[i];
return sum;
}
产生以下简单的组装
andl $-8, %edx
jle .L4
subl $4, %edx
vxorpd %xmm0, %xmm0, %xmm0
shrl $2, %edx
xorl %ecx, %ecx
leal 1(%rdx), %eax
xorl %edx, %edx
.L3:
vmovapd (%rsi,%rdx), %ymm2
addl $1, %ecx
vfmadd231pd (%rdi,%rdx), %ymm2, %ymm0
addq $32, %rdx
cmpl %eax, %ecx
jb .L3
vhaddpd %ymm0, %ymm0, %ymm0
vperm2f128 $1, %ymm0, %ymm0, %ymm1
vaddpd %ymm1, %ymm0, %ymm0
vzeroupper
ret
.L4:
vxorpd %xmm0, %xmm0, %xmm0
ret