inf*_*mal 25 optimization assembly vectorization avx2
我一直在研究使用AVX2指令集的新收集指令.具体来说,我决定对一个简单问题进行基准测试,其中一个浮点数组被置换并添加到另一个浮点数组中.在c中,这可以实现为
void vectortest(double * a,double * b,unsigned int * ind,unsigned int N)
{
int i;
for(i=0;i<N;++i)
{
a[i]+=b[ind[i]];
}
}
Run Code Online (Sandbox Code Playgroud)
我用g ++ -O3 -march = native编译这个函数.现在,我以三种方式在汇编中实现它.为简单起见,我假设数组N的长度可以被4整除.简单的非矢量化实现:
align 4
global vectortest_asm
vectortest_asm:
;; double * a = rdi
;; double * b = rsi
;; unsigned int * ind = rdx
;; unsigned int N = rcx
push rax
xor rax,rax
loop: sub rcx, 1
mov eax, [rdx+rcx*4] ;eax = ind[rcx]
vmovq xmm0, [rdi+rcx*8] ;xmm0 = a[rcx]
vaddsd xmm0, [rsi+rax*8] ;xmm1 += b[rax] ( and b[rax] = b[eax] = b[ind[rcx]])
vmovq [rdi+rcx*8], xmm0
cmp rcx, 0
jne loop
pop rax
ret
Run Code Online (Sandbox Code Playgroud)
没有收集指令的循环矢量化:
loop: sub rcx, 4
mov eax,[rdx+rcx*4] ;first load the values from array b to xmm1-xmm4
vmovq xmm1,[rsi+rax*8]
mov eax,[rdx+rcx*4+4]
vmovq xmm2,[rsi+rax*8]
mov eax,[rdx+rcx*4+8]
vmovq xmm3,[rsi+rax*8]
mov eax,[rdx+rcx*4+12]
vmovq xmm4,[rsi+rax*8]
vmovlhps xmm1,xmm2 ;now collect them all to ymm1
vmovlhps xmm3,xmm4
vinsertf128 ymm1,ymm1,xmm3,1
vaddpd ymm1, ymm1, [rdi+rcx*8]
vmovupd [rdi+rcx*8], ymm1
cmp rcx, 0
jne loop
Run Code Online (Sandbox Code Playgroud)
最后,使用vgatherdpd实现:
loop: sub rcx, 4
vmovdqu xmm2,[rdx+4*rcx] ;load the offsets from array ind to xmm2
vpcmpeqw ymm3,ymm3 ;set ymm3 to all ones, since it acts as the mask in vgatherdpd
vgatherdpd ymm1,[rsi+8*xmm2],ymm3 ;now gather the elements from array b to ymm1
vaddpd ymm1, ymm1, [rdi+rcx*8]
vmovupd [rdi+rcx*8], ymm1
cmp rcx, 0
jne loop
Run Code Online (Sandbox Code Playgroud)
我在具有Haswell cpu(Xeon E3-1245 v3)的机器上对这些功能进行基准测试.一些典型的结果是(以秒为单位):
Array length 100, function called 100000000 times.
Gcc version: 6.67439
Nonvectorized assembly implementation: 6.64713
Vectorized without gather: 4.88616
Vectorized with gather: 9.32949
Array length 1000, function called 10000000 times.
Gcc version: 5.48479
Nonvectorized assembly implementation: 5.56681
Vectorized without gather: 4.70103
Vectorized with gather: 8.94149
Array length 10000, function called 1000000 times.
Gcc version: 7.35433
Nonvectorized assembly implementation: 7.66528
Vectorized without gather: 7.92428
Vectorized with gather: 8.873
Run Code Online (Sandbox Code Playgroud)
gcc和nonvectorized程序集版本彼此非常接近.(我还检查了gcc的汇编输出,这与我的手动编码版本非常相似.)矢量化为小型数组提供了一些好处,但对于大型数组则更慢.(至少对我来说)最大的惊喜是使用vgatherpdp的版本太慢了.所以,我的问题是,为什么?我在这里做些蠢事吗?有人可以提供一个示例,其中收集指令实际上只会提供多个加载操作的性能优势吗?如果没有,实际拥有这样一条指令的重点是什么?
测试代码,包含g ++和nasm的makefile,可以在https://github.com/vanhala/vectortest.git上找到,以防有人想尝试一下.
Pau*_*l R 11
不幸的是,收集的加载指令并不是特别"智能" - 它们似乎每个元素生成一个总线周期,而不管加载地址如何,所以即使您碰巧有连续的元素,显然也没有用于合并负载的内部逻辑.因此,就效率而言,聚集的负载并不比N个标量负载好,只是它只使用一条指令.
收集指令的唯一真正好处是无论如何都要实现SIMD代码,并且需要加载非连续数据,然后您将应用进一步的SIMD操作.在这种情况下,SIMD收集的加载指令将比通常由例如_mm256_set_xxx()(或一堆连续的加载和置换等等,取决于实际的访问模式)生成的一串标量代码更有效.
较新的微体系结构已将可能性转移到了收集指令上。在具有Skylake微体系结构的Intel GHz Xeon Gold 6138 CPU @ 2.00 GHz上,我们可以为您提供基准测试:
9.383e+09 8.86e+08 2.777e+09 6.915e+09 7.793e+09 8.335e+09 5.386e+09 4.92e+08 6.649e+09 1.421e+09 2.362e+09 2.7e+07 8.69e+09 5.9e+07 7.763e+09 3.926e+09 5.4e+08 3.426e+09 9.172e+09 5.736e+09
9.383e+09 8.86e+08 2.777e+09 6.915e+09 7.793e+09 8.335e+09 5.386e+09 4.92e+08 6.649e+09 1.421e+09 2.362e+09 2.7e+07 8.69e+09 5.9e+07 7.763e+09 3.926e+09 5.4e+08 3.426e+09 9.172e+09 5.736e+09
9.383e+09 8.86e+08 2.777e+09 6.915e+09 7.793e+09 8.335e+09 5.386e+09 4.92e+08 6.649e+09 1.421e+09 2.362e+09 2.7e+07 8.69e+09 5.9e+07 7.763e+09 3.926e+09 5.4e+08 3.426e+09 9.172e+09 5.736e+09
9.383e+09 8.86e+08 2.777e+09 6.915e+09 7.793e+09 8.335e+09 5.386e+09 4.92e+08 6.649e+09 1.421e+09 2.362e+09 2.7e+07 8.69e+09 5.9e+07 7.763e+09 3.926e+09 5.4e+08 3.426e+09 9.172e+09 5.736e+09
Array length 10000, function called 1000000 times.
Gcc version: 6.32353
Nonvectorized assembly implementation: 6.36922
Vectorized without gather: 5.53553
Vectorized with gather: 4.50673
Run Code Online (Sandbox Code Playgroud)
表明聚集现在可能值得付出努力。
| 归档时间: |
|
| 查看次数: |
4084 次 |
| 最近记录: |