如果我编写这段代码:
void loop1(int N, double* R, double* A, double* B) {
for (int i = 0; i < N; i += 1) {
R[i] = A[i] + B[i];
}
}
Clang(-O3
)生成以下x64 ASM作为循环(Compiler Explorer)展开版本的一部分:
.LBB0_14:
movupd xmm0, xmmword ptr [rdx + 8*rax]
movupd xmm1, xmmword ptr [rdx + 8*rax + 16]
movupd xmm2, xmmword ptr [rcx + 8*rax]
addpd xmm2, xmm0
movupd xmm0, xmmword ptr [rcx + 8*rax + 16]
addpd xmm0, xmm1
movupd xmmword ptr [rsi + 8*rax], xmm2
movupd xmmword ptr [rsi + 8*rax + 16], xmm0
rdx
和rcx
握住我的输入指针(A
/ B
),rsi
是输出(R
),并且rax
是一个偏移量计数器。因此,它一次加载两对输入/输出,使用SIMD指令添加它们,然后将它们写入输出-到目前为止,一切都很好。
如果我改写以下内容:
void loop2(int N, double* R, double* A, double* B) {
for (int i = 0; i < N; i += 2) {
R[i] = A[i] + B[i];
R[i + 1] = A[i + 1] + B[i + 1];
}
}
LLVM生成以下(Compiler Explorer):
.LBB0_13:
movupd xmm0, xmmword ptr [rdx + 8*rdi]
movupd xmm1, xmmword ptr [rdx + 8*rdi + 16]
movupd xmm2, xmmword ptr [rcx + 8*rdi]
addpd xmm2, xmm0
movupd xmm0, xmmword ptr [rcx + 8*rdi + 16]
addpd xmm0, xmm1
movapd xmm1, xmm2
unpckhpd xmm1, xmm0 # xmm1 = xmm1[1],xmm0[1]
unpcklpd xmm2, xmm0 # xmm2 = xmm2[0],xmm0[0]
movapd xmm0, xmm2
unpcklpd xmm0, xmm1 # xmm0 = xmm0[0],xmm1[0]
unpckhpd xmm2, xmm1 # xmm2 = xmm2[1],xmm1[1]
movupd xmmword ptr [rsi + 8*rdi + 16], xmm2
movupd xmmword ptr [rsi + 8*rdi], xmm0
为清楚起见添加了空格,因为中间的部分带有unpckhpd
等,这使我感到困惑。据我所知,这6条指令的整体效果只是交换xmm0
和xmm2
,这似乎是在浪费时间。
知道为什么要这么做吗?有办法阻止它吗? :p
EDIT :我为loop2()
编辑了ASM,以删除所有相似的块(并在后续的写操作中在寄存器之间交换),并且它似乎可以正常运行并且与loop1()
(快40%)