C 为什么使用AVX2时加速比低于预期?
我已经使用AVX2的intrinsics指令对矩阵加法的内环进行了矢量化,我还有来自的延迟表。我认为加速应该是5倍,因为1024次迭代中几乎有4次延迟发生在128次迭代中的6次延迟上,但是加速是3倍。所以问题是这里还有什么我看不到的。我使用的是gcc,用c编写,内部语言,CPU是skylake 6700hq 这是c和组件从内环中取出 全球数据:C 为什么使用AVX2时加速比低于预期?,c,x86,intrinsics,avx2,C,X86,Intrinsics,Avx2,我已经使用AVX2的intrinsics指令对矩阵加法的内环进行了矢量化,我还有来自的延迟表。我认为加速应该是5倍,因为1024次迭代中几乎有4次延迟发生在128次迭代中的6次延迟上,但是加速是3倍。所以问题是这里还有什么我看不到的。我使用的是gcc,用c编写,内部语言,CPU是skylake 6700hq 这是c和组件从内环中取出 全球数据: int __attribute__(( aligned(32))) a[MAX1][MAX2] ; int __attribute__(( aligne
int __attribute__(( aligned(32))) a[MAX1][MAX2] ;
int __attribute__(( aligned(32))) b[MAX2][MAX3] ;
int __attribute__(( aligned(32))) c_result[MAX1][MAX3] ;
顺序:
for( i = 0 ; i < MAX1 ; i++)
for(j = 0 ; j < MAX2 ; j++)
c_result[i][j] = a[i][j] + b[i][j];
.L16:
movl (%r9,%rax), %edx // latency : 2 , throughput : 0.5 number of execution unit : 4 ALU
addl (%r8,%rax), %edx // latency : dont know , throughput : 0.5 number of execution unit : 4 ALU
movl %edx, c_result(%rcx,%rax) // latency : 2 , throughput : 1 number of execution unit : 4 ALU
addq $4, %rax
cmpq $4096, %rax
jne .L16
(i=0;i
对于(j=0;j
AVX2:
(i=0;i{
对于(j=0;j
除了循环计数器,没有循环携带的依赖链。因此,来自不同循环迭代的操作可以同时进行。这意味着延迟不是瓶颈,只是吞吐量(执行单元和前端(每个时钟最多4个融合域UOP))
而且,你的数字简直是疯了mov
加载不需要4个ALU执行单元!并且加载/存储延迟数字是错误的/没有意义的(参见最后一节)
总计:7个融合域UOP表示循环可以从循环缓冲区以每2c一次迭代的速度发出。(不符合1.75c)。因为我们混合使用了加载、存储和ALU UOP,所以执行端口不是瓶颈,只是融合了域4宽的问题宽度。每2c两次加载和每2c一次存储仅为加载和存储执行单元吞吐量的一半
请注意,2寄存器寻址模式。这对于纯负载来说不是问题,因为即使没有微熔合,它们也是1 uop
对于向量循环,分析是相同的。(vpaddd
在Skylake和几乎所有其他CPU上的延迟为1c。该表没有列出带有内存操作数的padd
的延迟列中的任何内容,因为加载的延迟与添加的延迟是分开的。只要加载的延迟为(衣服早就知道了。)
Agner Fog的存储和加载延迟数字也有点虚假。他任意将总负载存储往返延迟(使用存储转发)划分为负载和存储的延迟数。IDK为什么他没有列出通过指针跟踪测试测量的加载延迟(例如重复mov(%rsi),%rsi
)。这表明Intel SnB系列CPU有4个周期的加载使用延迟
我本想给他写封信的,但还没来得及告诉他
您应该看到AVX2的加速比为32/4,即8倍。您的问题大小只有4096B,这足够小,可以容纳三个大小相同的数组,以装入一级缓存。(编辑:问题是误导性的:显示的循环是嵌套循环的内部循环。请参阅注释:显然,即使使用4k阵列(而不是4M),OP仍然只能看到3倍的加速(而使用4M阵列则是1.5倍),因此AVX版本中存在某种瓶颈。)
所有3个阵列都是对齐的,因此在
不需要对齐的内存操作数(%r8
)
我的另一个理论似乎也不太可能,但数组地址之间的偏移量是否正好是4096B?来自Agner Fog的Microach PDF:
从地址同时读写是不可能的
以4千字节的倍数间隔的
这个例子显示了一个存储然后加载,所以IDK,如果这真的能解释它的话。即使内存排序硬件认为加载和存储可能位于同一个地址,我也不确定为什么这会阻止代码维持同样多的内存操作,或者为什么它对AVX2代码的影响比标量代码更大
值得尝试用额外的128B或256B或其他方式将数组相互抵消。除了循环计数器之外,没有循环携带的依赖链。因此,来自不同循环迭代的操作可以同时进行。这意味着延迟不是瓶颈,只是吞吐量(执行单元和前端(每个时钟最多4个融合域UOP))
而且,你的数字简直是疯了mov
加载不需要4个ALU执行单元!并且加载/存储延迟数字是错误的/没有意义的(参见最后一节)
总计:7个融合域UOP表示循环可以从循环缓冲区以每2c一次迭代的速度发出。(不符合1.75c)。因为我们混合使用了加载、存储和ALU UOP,所以执行端口不是瓶颈,只是融合了域4宽的问题宽度。每2c两次加载和每2c一次存储仅为加载和存储执行单元吞吐量的一半
请注意,2寄存器寻址模式。这对于纯负载来说不是问题,因为即使没有微熔合,它们也是1 uop。for( i = 0 ; i < MAX1 ; i++){
for(j = 0 ; j < MAX2 ; j += 8){
a0_i= _mm256_add_epi32( _mm256_load_si256((__m256i *)&a[i][j]) , _mm256_load_si256((__m256i *)&b[i][j]));
_mm256_store_si256((__m256i *)&c_result[i][j], a0_i);
}}
.L22:
vmovdqa (%rcx,%rax), %ymm0 // latency : 3 , throughput : 0.5 number of execution unit : 4 ALU
vpaddd (%r8,%rax), %ymm0, %ymm0 // latency : dont know , throughput : 0.5 number of execution unit : 3 VEC-ALU
vmovdqa %ymm0, c_result(%rdx,%rax) // latency : 3 , throughput : 1 number of execution unit : 4 ALU
addq $32, %rax
cmpq $4096, %rax
jne .L22
# Scalar (serial is the wrong word. Both versions are serial, not parallel)
.L16:
movl (%r9,%rax), %edx // fused-domain uops: 1. Unfused domain: a load port
addl (%r8,%rax), %edx // fused-domain uops: 2 Unfused domain: a load port and any ALU port
movl %edx, c_result(%rcx,%rax) // fused-domain uops: 2 Unfused domain: store-address and store-data ports. port7 can't handle 2-reg addresses
addq $4, %rax // fused-domain uops: 1 unfused: any ALU
cmpq $4096, %rax // fused-domain uops: 0 (fused with jcc)
jne .L16 // fused-domain uops: 1 unfused: port6 (predicted-taken branch)