アセンブリを見ましたか?置いた
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を使う。ただし、ループはベクトル化されません ( s
insd
は単一 (つまり、パックされていない) をd
意味し、double 浮動小数点を意味します)。
ループをベクトル化するには、連想演算を有効にする必要があります-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
はパックされた double を意味します)。ただし、展開されていません。これは現在、GCC の制限です。依存チェーンのため、数回展開する必要があります。コンパイラにこれを行わせたい場合は、4 回展開する Clang を使用することを検討してください。それ以外の場合は、組み込み関数を使用して手動で展開します。
GCC とは異なり、Clang はデフォルトで fma を使用しないことに注意してください-mfma
。Clang で fma を使用するには-ffp-contract=fast
(eg -O3 -mfma -ffp-contract=fast
) を使用するか#pragma STDC FP_CONTRACT ON
、eg で連想演算-Ofast
を有効にします。
異なるコンパイラで 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