C++ 单/双精度SpMV在CPU上的性能
稀疏矩阵向量积是一种内存受限的运算,因为它的运算强度非常低。由于浮点存储格式每非零需要4+4=8字节,而双精度(值和列索引)需要4+8=12字节,因此在切换到浮点时,执行速度应能提高约33%。我构建了一个基准测试,它组装了一个100000x1000000矩阵,每行有200个非零,然后从20次乘法中取最小值。github上的源代码 结果与我的预期大致相同。当我在我的Intel Core i7-2620M上运行基准测试时,我看到执行速度提高了30%。小的差别可以从带宽下降中看出,在21.3 GB/s的带宽中,带宽从大约19.0 GB/s(双倍)下降到大约18.0 GB/s(浮动) 现在,由于矩阵的数据几乎比向量的数据大1000,我们可以预期,对于只有矩阵是单精度的,但向量仍然是双倍的情况,也应该获得更快的性能。我继续尝试这个,然后确保在计算中使用较低的精度。但是,当我运行它时,有效带宽使用率突然下降到14.4 GB/s左右,执行速度仅比完整的double版本快12%。怎么能理解呢?C++ 单/双精度SpMV在CPU上的性能,c++,performance,sparse-matrix,precision,C++,Performance,Sparse Matrix,Precision,稀疏矩阵向量积是一种内存受限的运算,因为它的运算强度非常低。由于浮点存储格式每非零需要4+4=8字节,而双精度(值和列索引)需要4+8=12字节,因此在切换到浮点时,执行速度应能提高约33%。我构建了一个基准测试,它组装了一个100000x1000000矩阵,每行有200个非零,然后从20次乘法中取最小值。github上的源代码 结果与我的预期大致相同。当我在我的Intel Core i7-2620M上运行基准测试时,我看到执行速度提高了30%。小的差别可以从带宽下降中看出,在21.3 GB/s
我正在使用Ubuntu14.04和GCC4.9.3
Run times:
// double(mat)-double(vec)
Wall time: 0.127577 s
Bandwidth: 18.968 GB/s
Compute: 3.12736 Gflop/s
// float(mat)-float(vec)
Wall time: 0.089386 s
Bandwidth: 18.0333 GB/s
Compute: 4.46356 Gflop/s
// float(mat)-double(vec)
Wall time: 0.112134 s
Bandwidth: 14.4463 GB/s
Compute: 3.55807 Gflop/s
更新 见下面Peter Cordes的答案。简而言之,从双精度到浮点转换的循环迭代之间的依赖关系是造成开销的原因。通过展开循环(请参阅github上的展开循环分支),float-double和float-float版本都可以恢复全部带宽使用率
New run times:
// float(mat)-float(vec)
Wall time: 0.084455 s
Bandwidth: 19.0861 GB/s
Compute: 4.72417 Gflop/s
// float(mat)-double(vec)
Wall time: 0.0865598 s
Bandwidth: 18.7145 GB/s
Compute: 4.6093 Gflop/s
必须在运行中转换的双浮点循环不能很快发出。通过一些循环展开,gcc可能会做得更好 你的。当瓶颈是CPU uop吞吐量,而不是分支预测失误、缓存未命中,甚至只是长延迟链时,超读没有帮助。仅仅用标量操作来饱和内存带宽并不容易
从asm输出的代码来看:gcc 5.3具有大致相同的内部循环,顺便说一句,因此在本例中,使用旧的gcc版本不会损失太多 双版本内环(gcc 4.9.3
-O3-march=sandybridge-fopenmp
):
如果没有循环开销,每个元素的工作将减少到8个融合域UOP,并且不是一个每3个周期只发出2个UOP的小循环(因为10不是4的倍数)
它可以使用位移来保存lea
指令,例如[r8+rax*4+12]
。IDK为什么gcc选择不这样做
甚至连-ffast math
都无法将其矢量化。这可能没有意义,因为从稀疏矩阵进行聚集将超过从非稀疏向量加载4或8个连续值的好处。(insertps
from memory是一条2-uop指令,即使在单寄存器寻址模式下也无法微融合。)
在Broadwell或Skylake上,
vgatherdps
的速度可能足够快,可以加速。可能是天湖上的一次大加速。(可以收集8个单精度浮点,吞吐量为每5个时钟8个浮点。或者vgatherqpd
可以收集4个双精度浮点,吞吐量为每4个时钟4个双精度浮点)。这为您设置了256b矢量FMA。必须在运行中转换的双浮点循环不能很快发出。通过一些循环展开,gcc可能会做得更好
你的。当瓶颈是CPU uop吞吐量,而不是分支预测失误、缓存未命中,甚至只是长延迟链时,超读没有帮助。仅仅用标量操作来饱和内存带宽并不容易
从asm输出的代码来看:gcc 5.3具有大致相同的内部循环,顺便说一句,因此在本例中,使用旧的gcc版本不会损失太多 双版本内环(gcc 4.9.3
-O3-march=sandybridge-fopenmp
):
如果没有循环开销,每个元素的工作将减少到8个融合域UOP,并且不是一个每3个周期只发出2个UOP的小循环(因为10不是4的倍数)
它可以使用位移来保存lea
指令,例如[r8+rax*4+12]
。IDK为什么gcc选择不这样做
甚至连-ffast math
都无法将其矢量化。这可能没有意义,因为从稀疏矩阵进行聚集将超过从非稀疏向量加载4或8个连续值的好处。(insertps
from memory是一条2-uop指令,即使在单寄存器寻址模式下也无法微融合。)
在Broadwell或Skylake上,
vgatherdps
的速度可能足够快,可以加速。可能是天湖上的一次大加速。(可以收集8个单精度浮点,吞吐量为每5个时钟8个浮点。或者vgatherqpd
可以收集4个双精度浮点,吞吐量为每4个时钟4个双精度浮点)。这为您设置了256b矢量FMA。如果这两种类型不匹配,是否会发生额外的转换?没有说这一定是主要原因,但与double-double和float-float变体相比,这将产生大致相同的代码,这将是不同的,并且更长。问题是为各种数据类型组合生成什么程序集。vs。不确定那里是否会发生这种情况。一个可以尝试的工具是@DanMašek:对于gcc来说,这可能是更智能的代码。它使用xorpd
来破坏假dep,然后使用不能微融合的cvtss2sd
,因此它在融合域uop吞吐量上形成瓶颈。@PeterCordes谢谢。不可否认,我对这件事了解得还不够透彻
## inner loop of <double,double>mult() with fused-domain uop counts
.L7:
mov edx, eax # 1 uop
add eax, 1 # 1 uop
mov ecx, DWORD PTR [r9+rdx*4] # 1 uop
vmovsd xmm0, QWORD PTR [r10+rdx*8] # 1 uop
vmulsd xmm0, xmm0, QWORD PTR [r8+rcx*8] # 2 uops
vaddsd xmm1, xmm1, xmm0 # 1 uop
cmp eax, esi # (macro-fused)
jne .L7 # 1 uop
## inner loop of <double,float>mult() with fused-domain uop counts
.L7:
mov edx, eax # 1 uop
vxorpd xmm0, xmm0, xmm0 # 1 uop (no execution unit needed).
add eax, 1 # 1 uop
vcvtss2sd xmm0, xmm0, DWORD PTR [r9+rdx*4] # 2 uops
mov edx, DWORD PTR [r8+rdx*4] # 1 uop
vmulsd xmm0, xmm0, QWORD PTR [rsi+rdx*8] # 2 uops
vaddsd xmm1, xmm1, xmm0 # 1 uop
cmp eax, ecx # (macro-fused)
jne .L7 # 1 uop
mov edx, DWORD PTR [rsi+r14*4] # D.56355, *_40
lea r14d, [rax+2] # D.56355,
vcvtss2sd xmm6, xmm4, DWORD PTR [r8+r14*4] # D.56358, D.56358, *_36
vmulsd xmm2, xmm1, QWORD PTR [rcx+rdx*8] # D.56358, D.56358, *_45
vaddsd xmm14, xmm0, xmm13 # tmp, tmp, D.56358
vxorpd xmm1, xmm1, xmm1 # D.56358
mov edx, DWORD PTR [rsi+r14*4] # D.56355, *_40
lea r14d, [rax+3] # D.56355,
vcvtss2sd xmm10, xmm9, DWORD PTR [r8+r14*4] # D.56358, D.56358, *_36
vmulsd xmm7, xmm6, QWORD PTR [rcx+rdx*8] # D.56358, D.56358, *_45
vaddsd xmm3, xmm14, xmm2 # tmp, tmp, D.56358
vxorpd xmm6, xmm6, xmm6 # D.56358