我使用AVX2的内在指令向量化了矩阵加法的内部循环,我也有here的延迟表。我预计加速应该是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
答案 0 :(得分:3)
除了循环计数器之外,没有循环携带的依赖链。因此,不同循环迭代的操作可以立即进行。这意味着延迟不是瓶颈,只是吞吐量(执行单元和前端(每个时钟最多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寄存器寻址模式can't micro-fuse on Intel SnB-family。对于纯负载来说,这不是一个问题,因为即使没有微融合,它们也会出现问题。
矢量循环的分析是相同的。 (vpaddd
在Skylake和几乎所有其他CPU上都有1c的延迟。该表没有在padd
的延迟列中列出具有内存操作数的任何内容,因为加载的延迟是与add的延迟分开。它只向一个涉及寄存器src / dest的dep链添加一个循环,只要提前知道加载地址足够远。)
Agner Fog的商店和负载延迟数字也有点虚假。他任意将总加载 - 存储往返延迟(带存储转发)分为加载和存储的延迟数。 IDK为什么没有列出由指针追逐测试(例如重复mov (%rsi), %rsi
)测量的负载延迟。这表明Intel SnB系列CPU有4个周期的负载使用延迟。
我打算给他发一张关于那个的说明,但还没有找到它。
你应该看到AVX2的加速比为32/4,即8x。您的问题大小仅为4096B,这对于该大小的三个数组来说足够小以适合L1缓存。 (编辑:问题是误导性的:显示的循环是嵌套循环的内部循环。看到评论:显然即使有4k阵列(不是4M),OP仍然只看到3倍的加速(与4M阵列的1.5倍相比),因此AVX版本存在某种瓶颈。)
所有3个数组都是对齐的,所以它不是缓存线交叉的
内存操作数不需要对齐(%r8
)。
我的其他理论似乎也不太可能,但你的阵列地址是否相互偏移了4096B?来自Agner Fog的microarch PDF:
无法同时从地址读取和写入 间隔4千字节的倍数
该示例显示了一个商店然后加载,所以IDK如果真的解释它。即使内存排序硬件认为加载和存储可能是相同的地址,我也不确定为什么会阻止代码维持尽可能多的内存操作,或者为什么它会影响AVX2代码比标量码。
值得尝试通过额外的128B或256B或其他东西来抵消阵列。
答案 1 :(得分:0)
以下限制限制了两个实现的性能。首先,除了循环计数器之外,没有循环携带的依赖链,因此可以立即执行来自不同循环迭代的操作,这意味着延迟不是主要的瓶颈,如何延迟是HPC中的重要因素。由于延迟相等,因此执行单元的吞吐量对于两种实现都更有效。 IACA将标量实现的吞吐量瓶颈演示为“迭代间”,这意味着循环的连续迭代之间存在依赖关系,矢量化有助于使代码运行更快。此外,矢量化模式下的vpaddd可以在端口5,1上发布但是当端口0在第一个周期忙时,add使用执行端口1,5,6。其次,融合域前端的吞吐量可能会影响性能,但在此算法中,根据IACA结果,两次实现需要每次迭代7次uops,而HSW / SKL微架构最多可发出4次融合 - 每个时钟的域uop因此它需要内循环的每次迭代2个循环,并且这个限制违反了AVX2实现而不是标量实现。第三,算法的数据依赖性导致许多高速缓存未命中。通过减小适合L1D(第一级数据高速缓存)的矩阵的大小变为5的因子(我多久测试得到5但是IDK再次测试加速是7.3 )。