我正在尝试使用 LLVM 自动矢量化缩减循环。但是即使我 运行 在他们的文档中给出了相同的示例代码,它也会失败
I am trying to auto vectorize a reduction loop with LLVM. But it fails even when I run the same example code given in their documentation
static void iadd(int &R, Vector &A) {
unsigned sum = 0;
int a;
for (int i=0; i<A.vector_elements_16; i++) {
a = static_cast<int>(A.data_16[i]);
sum += a ;
}
R=static_cast<int>(sum);
}
Vector class:具有宽度为 32 且类型为 uint16_t 的静态数组。所以迭代次数是 32.
问题中没有足够的信息来自信地回答它。但我向你保证,LLVM 和 Clang 至少会在树的顶部对缩减循环进行矢量化处理(我还没有检查旧版本的功能)。
第一个问题是矢量化确实取决于架构。我将在示例中使用 x86-64 和 Haswell 微体系结构(支持 AVX2),因为您没有列出特定的体系结构。如果您指定,我很乐意更新我对其他架构的回答。
下一个问题是您的描述听起来不像是真正的缩减循环。首先,如果数组是 static,那么我真的不知道这是关于什么的——那是一个全局数组。但是假设你的意思是一个长度为 32 的成员数组,那应该等同于以下(稍微简化的)完整并编译的代码:
struct V {
static constexpr int length = 32;
unsigned short data[32];
};
int reduce(V &v) {
int sum = 0;
for (int i = 0; i < v.length; ++i)
sum += static_cast<int>(v.data[i]);
return sum;
}
尽管给出这段代码,因为长度是一个常量,LLVM 完全展开了循环,这意味着没有循环矢量化将发挥作用。可悲的是,我们实际上为展开的循环生成了非常糟糕的代码——32 次加载和 32 次添加。我们可以做得更好,更好。我已提交 http://llvm.org/PR28090 以跟踪修复此问题。
但这不再是真正的缩减 loop 因为它具有恒定的 rip 计数并展开。如果你真的有一个循环,比如下面的代码:
struct V {
int length;
unsigned short *data;
};
int reduce(V &v) {
int sum = 0;
for (int i = 0; i < v.length; ++i)
sum += static_cast<int>(v.data[i]);
return sum;
}
然后 LLVM 实际上会为 Haswell 很好地矢量化它。它将 8 个元素加载到一个向量中,将它们扩展为 32 位值,然后对它们求和。它还一次处理其中的 4 个向量,以充分利用架构的带宽。在此处查看代码:
_Z6reduceR1V: # @_Z6reduceR1V
.cfi_startproc
# BB#0: # %entry
movslq (%rdi), %rcx
xorl %eax, %eax
testq %rcx, %rcx
jle .LBB0_11
# BB#1: # %for.body.lr.ph
movq 8(%rdi), %rdx
xorl %edi, %edi
movl [=12=], %eax
cmpl , %ecx
jbe .LBB0_10
# BB#2: # %min.iters.checked
xorl %edi, %edi
movq %rcx, %r9
movl [=12=], %eax
andq $-32, %r9
je .LBB0_10
# BB#3: # %vector.body.preheader
leaq -32(%r9), %rsi
shrq , %rsi
leal 1(%rsi), %r8d
andl , %r8d
xorl %eax, %eax
testq %rsi, %rsi
je .LBB0_4
# BB#5: # %vector.body.preheader.new
leaq -1(%r8), %rdi
subq %rsi, %rdi
vpxor %ymm0, %ymm0, %ymm0
xorl %eax, %eax
vpxor %ymm1, %ymm1, %ymm1
vpxor %ymm2, %ymm2, %ymm2
vpxor %ymm3, %ymm3, %ymm3
.p2align 4, 0x90
.LBB0_6: # %vector.body
# =>This Inner Loop Header: Depth=1
vpmovzxwd (%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 16(%rdx,%rax,2), %ymm5 # ymm5 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 32(%rdx,%rax,2), %ymm6 # ymm6 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 48(%rdx,%rax,2), %ymm7 # ymm7 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm0, %ymm4, %ymm0
vpaddd %ymm1, %ymm5, %ymm1
vpaddd %ymm2, %ymm6, %ymm2
vpaddd %ymm3, %ymm7, %ymm3
vpmovzxwd 64(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 80(%rdx,%rax,2), %ymm5 # ymm5 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 96(%rdx,%rax,2), %ymm6 # ymm6 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 112(%rdx,%rax,2), %ymm7 # ymm7 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
leaq 64(%rax), %rax
vpaddd %ymm0, %ymm4, %ymm0
vpaddd %ymm1, %ymm5, %ymm1
vpaddd %ymm2, %ymm6, %ymm2
vpaddd %ymm3, %ymm7, %ymm3
addq , %rdi
jne .LBB0_6
jmp .LBB0_7
.LBB0_4:
vpxor %ymm0, %ymm0, %ymm0
vpxor %ymm1, %ymm1, %ymm1
vpxor %ymm2, %ymm2, %ymm2
vpxor %ymm3, %ymm3, %ymm3
.LBB0_7: # %middle.block.unr-lcssa
testq %r8, %r8
je .LBB0_9
# BB#8: # %middle.block.epilog-lcssa
vpmovzxwd 48(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm3, %ymm4, %ymm3
vpmovzxwd 32(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm2, %ymm4, %ymm2
vpmovzxwd 16(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm1, %ymm4, %ymm1
vpmovzxwd (%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm0, %ymm4, %ymm0
.LBB0_9: # %middle.block
vpaddd %ymm0, %ymm1, %ymm0
vpaddd %ymm0, %ymm2, %ymm0
vpaddd %ymm0, %ymm3, %ymm0
vextracti128 , %ymm0, %xmm1
vpaddd %ymm1, %ymm0, %ymm0
vpshufd , %xmm0, %xmm1 # xmm1 = xmm0[2,3,0,1]
vpaddd %ymm1, %ymm0, %ymm0
vphaddd %ymm0, %ymm0, %ymm0
vmovd %xmm0, %eax
movq %r9, %rdi
cmpq %r9, %rcx
je .LBB0_11
.p2align 4, 0x90
.LBB0_10: # %for.body
# =>This Inner Loop Header: Depth=1
movzwl (%rdx,%rdi,2), %esi
addl %esi, %eax
addq , %rdi
cmpq %rcx, %rdi
jl .LBB0_10
.LBB0_11: # %for.cond.cleanup
vzeroupper
retq
static void iadd(int &R, Vector &A) {
unsigned sum = 0;
int a;
for (int i=0; i<A.vector_elements_16; i++) {
a = static_cast<int>(A.data_16[i]);
sum += a ;
}
R=static_cast<int>(sum);
}
Vector class:具有宽度为 32 且类型为 uint16_t 的静态数组。所以迭代次数是 32.
问题中没有足够的信息来自信地回答它。但我向你保证,LLVM 和 Clang 至少会在树的顶部对缩减循环进行矢量化处理(我还没有检查旧版本的功能)。
第一个问题是矢量化确实取决于架构。我将在示例中使用 x86-64 和 Haswell 微体系结构(支持 AVX2),因为您没有列出特定的体系结构。如果您指定,我很乐意更新我对其他架构的回答。
下一个问题是您的描述听起来不像是真正的缩减循环。首先,如果数组是 static,那么我真的不知道这是关于什么的——那是一个全局数组。但是假设你的意思是一个长度为 32 的成员数组,那应该等同于以下(稍微简化的)完整并编译的代码:
struct V {
static constexpr int length = 32;
unsigned short data[32];
};
int reduce(V &v) {
int sum = 0;
for (int i = 0; i < v.length; ++i)
sum += static_cast<int>(v.data[i]);
return sum;
}
尽管给出这段代码,因为长度是一个常量,LLVM 完全展开了循环,这意味着没有循环矢量化将发挥作用。可悲的是,我们实际上为展开的循环生成了非常糟糕的代码——32 次加载和 32 次添加。我们可以做得更好,更好。我已提交 http://llvm.org/PR28090 以跟踪修复此问题。
但这不再是真正的缩减 loop 因为它具有恒定的 rip 计数并展开。如果你真的有一个循环,比如下面的代码:
struct V {
int length;
unsigned short *data;
};
int reduce(V &v) {
int sum = 0;
for (int i = 0; i < v.length; ++i)
sum += static_cast<int>(v.data[i]);
return sum;
}
然后 LLVM 实际上会为 Haswell 很好地矢量化它。它将 8 个元素加载到一个向量中,将它们扩展为 32 位值,然后对它们求和。它还一次处理其中的 4 个向量,以充分利用架构的带宽。在此处查看代码:
_Z6reduceR1V: # @_Z6reduceR1V
.cfi_startproc
# BB#0: # %entry
movslq (%rdi), %rcx
xorl %eax, %eax
testq %rcx, %rcx
jle .LBB0_11
# BB#1: # %for.body.lr.ph
movq 8(%rdi), %rdx
xorl %edi, %edi
movl [=12=], %eax
cmpl , %ecx
jbe .LBB0_10
# BB#2: # %min.iters.checked
xorl %edi, %edi
movq %rcx, %r9
movl [=12=], %eax
andq $-32, %r9
je .LBB0_10
# BB#3: # %vector.body.preheader
leaq -32(%r9), %rsi
shrq , %rsi
leal 1(%rsi), %r8d
andl , %r8d
xorl %eax, %eax
testq %rsi, %rsi
je .LBB0_4
# BB#5: # %vector.body.preheader.new
leaq -1(%r8), %rdi
subq %rsi, %rdi
vpxor %ymm0, %ymm0, %ymm0
xorl %eax, %eax
vpxor %ymm1, %ymm1, %ymm1
vpxor %ymm2, %ymm2, %ymm2
vpxor %ymm3, %ymm3, %ymm3
.p2align 4, 0x90
.LBB0_6: # %vector.body
# =>This Inner Loop Header: Depth=1
vpmovzxwd (%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 16(%rdx,%rax,2), %ymm5 # ymm5 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 32(%rdx,%rax,2), %ymm6 # ymm6 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 48(%rdx,%rax,2), %ymm7 # ymm7 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm0, %ymm4, %ymm0
vpaddd %ymm1, %ymm5, %ymm1
vpaddd %ymm2, %ymm6, %ymm2
vpaddd %ymm3, %ymm7, %ymm3
vpmovzxwd 64(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 80(%rdx,%rax,2), %ymm5 # ymm5 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 96(%rdx,%rax,2), %ymm6 # ymm6 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpmovzxwd 112(%rdx,%rax,2), %ymm7 # ymm7 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
leaq 64(%rax), %rax
vpaddd %ymm0, %ymm4, %ymm0
vpaddd %ymm1, %ymm5, %ymm1
vpaddd %ymm2, %ymm6, %ymm2
vpaddd %ymm3, %ymm7, %ymm3
addq , %rdi
jne .LBB0_6
jmp .LBB0_7
.LBB0_4:
vpxor %ymm0, %ymm0, %ymm0
vpxor %ymm1, %ymm1, %ymm1
vpxor %ymm2, %ymm2, %ymm2
vpxor %ymm3, %ymm3, %ymm3
.LBB0_7: # %middle.block.unr-lcssa
testq %r8, %r8
je .LBB0_9
# BB#8: # %middle.block.epilog-lcssa
vpmovzxwd 48(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm3, %ymm4, %ymm3
vpmovzxwd 32(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm2, %ymm4, %ymm2
vpmovzxwd 16(%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm1, %ymm4, %ymm1
vpmovzxwd (%rdx,%rax,2), %ymm4 # ymm4 = mem[0],zero,mem[1],zero,mem[2],zero,mem[3],zero,mem[4],zero,mem[5],zero,mem[6],zero,mem[7],zero
vpaddd %ymm0, %ymm4, %ymm0
.LBB0_9: # %middle.block
vpaddd %ymm0, %ymm1, %ymm0
vpaddd %ymm0, %ymm2, %ymm0
vpaddd %ymm0, %ymm3, %ymm0
vextracti128 , %ymm0, %xmm1
vpaddd %ymm1, %ymm0, %ymm0
vpshufd , %xmm0, %xmm1 # xmm1 = xmm0[2,3,0,1]
vpaddd %ymm1, %ymm0, %ymm0
vphaddd %ymm0, %ymm0, %ymm0
vmovd %xmm0, %eax
movq %r9, %rdi
cmpq %r9, %rcx
je .LBB0_11
.p2align 4, 0x90
.LBB0_10: # %for.body
# =>This Inner Loop Header: Depth=1
movzwl (%rdx,%rdi,2), %esi
addl %esi, %eax
addq , %rdi
cmpq %rcx, %rdi
jl .LBB0_10
.LBB0_11: # %for.cond.cleanup
vzeroupper
retq