Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/135.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 单/双精度SpMV在CPU上的性能_C++_Performance_Sparse Matrix_Precision - Fatal编程技术网

C++ 单/双精度SpMV在CPU上的性能

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

稀疏矩阵向量积是一种内存受限的运算,因为它的运算强度非常低。由于浮点存储格式每非零需要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%。怎么能理解呢?


我正在使用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