我是 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]];
}
答案 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 个元素,并且线程之间共享工作的开销是更大。