如何让这个 OpenMP 运行得更快?

时间:2021-05-08 16:52:54

标签: c++ performance openmp sparse-matrix

我是 OpenMP 的新手,我有这个稀疏矩阵向量乘法的代码,它运行在 40 到 50 秒之间。并且总共有 4237 MFlops/s。有什么办法可以更快地得到它吗? Ss 我已经编辑了帖子的完整代码和 A 作为输入我有 2 个矩阵,一个是 50000 元素,第二个是 400000。

主要问题是当我尝试不同的东西时,我有时间做得更糟。

    #pragma omp parallel for schedule (static,50)
            for (int i=0; i< (tInput->stNumRows); ++i) {
                 y[i] = 0.0;
                    for (int j=Arow[i]; j<Arow[i+1]; ++j)
                        y[i] += Aval[j]*x[Acol[j]];
            }
            

1 个答案:

答案 0 :(得分:2)

提高代码性能的方法是使用矢量化(感谢 SIMD 指令)。生成的代码如下:

for (int i=0; i< (tInput->stNumRows); ++i) {
    double s = 0.0;
    #pragma omp simd reduction(+:s)
    for (int j=Arow[i]; j<Arow[i+1]; ++j)
        s += Aval[j] * x[Acol[j]];
    y[i] = s;
}

请注意,y[i] 不会在循环中连续读/写,从而实现进一步的编译器优化。请注意编译 -O3(或 MSVC 的 /O2)中的代码,以便有效地矢量化代码。然而,这可能不足以让这段代码被向量化。

确实,此代码的一个问题是内存间接 x[Acol[j]],它很难有效地矢量化。最近的 x86-64 处理器(带有 AVX2 的处理器)和最新的 ARM 处理器(带有 SVE 的处理器)都有 SIMD 指令来做到这一点(尽管由于内存访问模式,它们仍然不是很好)。没有这些指令,任何编译器都不可能对代码进行矢量化处理。因此,您应该告诉编译器它可以使用这些指令(假设目标处理器实际上是最新的)。对于 GCC/Clang,一种方法是使用不可移植的 -march=native。另一种方法是在 x86-64 处理器上将 -mavx2-mfma 结合使用(尽管在这种情况下由于非常复杂的原因,这似乎不如 -march=native)。

改进代码的另一种方法是减轻可能的负载平衡问题和不必要的开销。事实上,如果表达式 Arow[i+1]-Arow[i]+1 对于许多 i 值非常不同,负载平衡问题可能会出现在您的代码中。在这种情况下,您可以使用 guided 计划或 dynamic 计划。但是,请记住,使用非静态计划可能会引入大量开销(尤其是在循环非常小或值之间的差距很大的情况下)。最后,您可以将 omp parallel 指令移到计时循环主体之外,因为这会引入大量开销(由于针对目标 OpenMP 运行时的线程创建)。

请注意,上述解决方案假设输入矩阵足够大,因此并行性很有用。此外,如果 x 很大,则代码可能会受到内存层次结构的限制,您无能为力。由于这些问题,稀疏矩阵计算通常很慢。

这是最终代码:

#pragma omp parallel
{
    // Timing loop
    // [...]

    #pragma omp for schedule(guided)
    for (int i=0; i< (tInput->stNumRows); ++i) {
        double s = 0.0;
        #pragma omp simd reduction(+:s)
        for (int j=Arow[i]; j<Arow[i+1]; ++j)
            s += Aval[j] * x[Acol[j]];
        y[i] = s;
    }

    // [...]
}

编辑:使用您的输入数据,我机器上的最佳解决方案(使用 Clang/IOMP)根本不使用多线程,因为可以在大约 0.3 毫秒内计算 400000 个元素,并且线程之间共享工作的开销是更大。

相关问题