使用AVX2为什么加速比低于预期?

我已经使用AVX2的内在指令向量化了矩阵加法的内部循环,我也有这里的延迟表。 我预计加速应该是5的因子,因为在128次迭代中,在6次延迟的1024次迭代中发生了近4次延迟,但是加速是3的因数。所以问题是这里还有什么我看不到的。 我正在使用gcc,在c中编码,内在函数,CPU是skylake 6700hq

这是内循环的c和汇编输出。

全球数据:

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 

AVX2:

 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 

除了循环计数器之外,没有循环携带的依赖链。 因此,不同循环迭代的操作可以立即进行。 这意味着延迟不是瓶颈,只是吞吐量(执行单元和前端(每个时钟最多4个融合域uop))。

而且,你的数字完全是疯了。 mov加载不需要4个ALU执行单元! 并且加载/存储延迟数字是错误的/无意义的(参见最后一节)。

 # 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) 

总计:7个融合域uops意味着循环可以每2c一次迭代从循环缓冲区发出。 (不是每1.75c)。 由于我们使用了加载,存储和ALU uop的混合,执行端口不是瓶颈,只是融合域4宽的问题宽度。 每2c两个负载和每2c一个存储只是加载和存储执行单元的一半吞吐量。

请注意,2寄存器寻址模式不能在Intel SnB系列上进行微熔丝 。 这对于纯负载来说不是问题,因为即使没有微融合,它们也是1 uop。

矢量循环的分析是相同的。 ( vpaddd在Skylake和几乎所有其他CPU上都有1c的延迟。该表没有在带有内存操作数的padd的延迟列中列出任何内容,因为加载的延迟与添加的延迟是分开的。只需提前知道加载地址,就可以向dep链中添加一个涉及寄存器src / dest的循环。)


Agner Fog的存储和负载延迟数字也有点虚假。 他随意将总加载 – 存储往返延迟(带存储转发)划分为加载和存储的延迟数。 IDK为什么没有列出由指针追逐测试(例如重复的mov (%rsi), %rsi )测量的负载延迟。 这表明Intel SnB系列CPU具有4个周期的负载使用延迟。

我打算给他发一张关于那个的说明,但还没有找到它。


应该看到AVX2加速为32/4,即8x。 您的问题大小仅为4096B,这对于该大小的三个数组来说足够小以适合L1缓存。 ( 编辑:问题是误导性的:显示的循环是嵌套循环的内部循环。请参阅注释:显然即使使用4karrays(不是4M),OP仍然只能看到3倍的加速(与4Marrays相比为1.5倍) ),所以AVX版本存在某种瓶颈。)

所有3个数组都是对齐的,因此它不是内存操作数中的高速缓存行交叉,不需要对齐( %r8 )。

我的其他理论似乎也不太可能,但你的arrays地址是否相互偏移了4096B? 来自Agner Fog的微观PDF:

不能同时从间隔4 KB的地址读取和写入

这个例子显示了一个商店然后加载,所以IDK如果真的解释了它。 即使内存排序硬件认为加载和存储可能是相同的地址,我也不确定为什么会阻止代码维持尽可能多的内存操作,或者为什么它会影响AVX2代码比标量代码更糟糕。

值得尝试通过额外的128B或256B或其他东西来抵消arrays。

以下限制限制了两个实现的性能。 首先,除了循环计数器之外,没有循环携带的依赖链,因此可以立即执行来自不同循环迭代的操作,这意味着延迟不是主要瓶颈,如何延迟是HPC中的重要因素。 由于延迟相等,因此执行单元的吞吐量对于两种实现都更有效。 IACA将标量实现的吞吐量瓶颈演示为“迭代间”,这意味着循环的连续迭代之间存在依赖关系,矢量化有助于使代码运行更快。此外,矢量化模式下的vpaddd可以在端口5,1上发布但是当端口0在第一个周期忙时,add使用执行端口1,5,6。 其次,融合域前端的吞吐量可能会影响性能,但在此算法中,根据IACA的结果,对于每个实现,每次迭代需要7 uop,而HSW / SKL微架构最多可以发出4个融合 – 每个时钟的域uop因此它需要内循环的每次迭代2个循环,并且这个限制违反了AVX2实现而不是标量实现。 第三,算法的数据依赖性导致许多高速缓存未命中。 通过减小适合L1D(第一级数据高速缓存)的矩阵的大小变为5的因子( 我多久测试得到5但IDK再次测试加速为7.3 )。