使用 OpenMP 的多级并行性 - 可能吗?聪明的?实际的?

Posted

技术标签:

【中文标题】使用 OpenMP 的多级并行性 - 可能吗?聪明的?实际的?【英文标题】:Multiple levels of parallelism using OpenMP - Possible? Smart? Practical? 【发布时间】:2010-07-01 16:28:07 【问题描述】:

我目前正在为我管理的模拟工具开发 C++ 稀疏矩阵/数学/迭代求解器库。我更愿意使用现有的包,但是,经过广泛调查,没有一个适合我们的模拟器(我们查看了 flens、it++、PetSC、eigen 和其他几个)。好消息是我的求解器和稀疏矩阵结构现在非常高效和强大。坏消息是,我现在正在研究使用 OpenMP 进行并行化,但学习曲线有点陡峭。

我们解决的域可以分解为子域,这些子域以块对角线格式组合在一起。所以我们的存储方案最终看起来像一个较小的方形矩阵(blocks [])数组,每个矩阵都有适合子域的格式(例如压缩行存储:CRS,压缩对角存储:CDS,密集等),和一个背景矩阵(目前使用 CRS),它说明了子域之间的连通性。

大多数(全部?)迭代求解器中的“热点”是矩阵向量乘法运算,我的库也是如此。因此,我一直专注于优化我的 MxV 例程。对于块对角结构,M*x=b 的伪代码如下:

b=background_matrix*x
start_index = 1;
end_index = 0;
for(i=1:number of blocks) 
    end_index=start_index+blocks[i].numRows();
    b.range(start_index, end_index) += blocks[i] * x.range(start_index, end_index);
    start_index = end_index+1;

其中 background_matrix 是背景 (CRS) 矩阵,blocks 是子域矩阵数组,.range 返回向量中从起始索引到结束索引的部分。

显然,循环可以(并且已经)并行化,因为操作独立于循环的其他迭代(范围不重叠)。由于我们在典型系统中有 10-15 个块,因此 4 个以上的线程实际上会产生显着差异。

并行化被认为是一个不错的选择的另一个地方是每个子域存储方案的 MxV 操作(上述代码中的第 1 行和第 6 行中的调用)。有很多关于并行化 CRS、CDS 和密集矩阵 MxV 操作的内容。通常,使用 2 个线程可以看到很好的提升,随着添加更多线程,收益会大大减少。

我正在设想一个方案,其中 4 个线程将用于上述代码的块循环中,每个线程将使用 2 个线程来解决子域。但是,我不确定如何使用 OpenMP 来管理线程池——是否可以限制 openmp for 循环中的线程数?这种多级并行性在实践中是否有意义?对我在这里提出的任何其他想法将不胜感激(感谢您一直阅读到最后!)

【问题讨论】:

你最终使用了哪个求解器? @Jacob- 我正在求解的系统有几种不同类型的子域,使用 CRS 上的 jacobi 预条件 GMRES、CDS 上的 ICC 预条件 CG 求解或直接密集求解。为了充分利用每个子域,我最终在全局系统上进行了 GMRES 求解,使用 1 步非重叠 Additive Schwarz 预处理器,其中预处理器中的局部求解是适合的求解器算法子域类型。 您是否考虑过当您想要解决更大的系统或想要更快地解决这些问题时您会做什么? OpenMP 非常适合单个共享内存节点,但是一旦您超越它(出于大小原因或只是在更短的时间内进行计算),您最终会想要其他可以扩展的东西。我建议我的实验室开发的东西(见简介),但你已经提到 OpenMP 具有陡峭的学习曲线。遗憾的是,我们的软件仍然更陡峭,但确实有一些可以更自然地表示事物的结构。 【参考方案1】:

请注意,我所描述的一切都取决于实现。

是否可以限制 openmp for 循环中的线程数?

是的。有不同的方法可以做到这一点。设置omp_set_nested(1); 并在你的外循环中使用#pragma omp parallel for num_threads(4) 或类似的东西,在你的内循环中使用#pragma omp parallel for num_threads(2) 指令。这应该为您提供 8 个线程(取决于实现,如果您的内核少于 8 个,您可能还必须设置 OMP_THREAD_LIMIT

或者,您可以手动展开循环,例如使用类似的东西

#pragma omp parallel sections 
     #pragma omp section 
     do your stuff for the first part, nest parallel region again
     #pragma omp section 
     and so on for the other parts

在 OpenMP 3.0 中使用#pragma omp task 有时可以更有效地执行相同的操作。

或者您启动 8 个线程并在并行部分中获取当前线程号并根据线程号手动调度。

最后,如果你有一个完美嵌套的循环(一个循环是完美嵌套的,如果实际赋值只发生在最里面的循环中),你可以将所有内容重写为一个循环。基本上将你的两个迭代器ij 打包成一个大迭代器(i, j)。请注意,这可能会降低局部性,从而降低性能

这种多级并行在实践中是否有意义?

这取决于你自己。通常,多级并行性使您的问题更具可扩展性。然而,调度可能更复杂。这个paper 可能很有趣。

关于手动设置线程数:设置线程数的主要优点是您可以在调度时使用有关您的问题的特定知识。 因此,您可以减少开销并获得更高的执行代码局部性,从而获得更多的缓存命中率和更少的主内存 I/O。

在嵌套并行中手动设置线程数的主要缺点是最内层循环中的线程可能会在隐式屏障处空闲等待,而可以完成额外的工作(example)。此外,粗粒度并行性不能很好地扩展。因此,如果您的外循环在循环内的运行时间非常不同,您希望安排更灵活,而不是简单地分成 4 个线程。

任何其他想法

您是否考虑过使用 SIMD 进行 MxV。根据架构,这可以提供 2-4 的加速。我很快为你搜索了这个presentation。

对于 MxV,loop tiling、register and cache blocking 和相关技术可以增加数据局部性并减少其他问题,例如虚假分享。这个book,第 11 章(你可以预览它)可能会给你一些关于如何重构数据访问的额外想法。

【讨论】:

stephan- 非常感谢您提供这篇内容丰富的帖子和链接- 我一定会阅读日程安排。这里有很多要消化的。至于将 SIMD 用于 MxV,我已经为密集矩阵乘法实现了一个 SSE MxV 例程,它确实有一个非常好的加速。不幸的是,根据我的阅读,稀疏 CRS 或 CDS MxV 操作的内存访问模式通常会阻止矢量化。我已经阅读了几篇论文,这些论文展示了从 SIMD 中获得一些用于稀疏 MxV 的方法,但还没有时间真正深入研究它。 @MarkD:很高兴它有帮助。我必须承认,我对稀疏数据的 SIMD 了解不多。不过,我添加了一个关于缓存阻止的链接,这可能会有所帮助。【参考方案2】:

何不向 OpenMP.org 的专家咨询

注册并登录: http://openmp.org/forum/viewforum.php?f=3

【讨论】:

谢谢 rchrd- 实际上我不知道 openMP 有一个论坛。我会在那里开始细读,看看我能学到什么。

以上是关于使用 OpenMP 的多级并行性 - 可能吗?聪明的?实际的?的主要内容,如果未能解决你的问题,请参考以下文章

OpenMP 嵌套循环任务并行性,计数器未给出正确结果

并行任务中的 C++ OpenMP 变量可见性

我应该在 openMP 并行区域内使用 gnu 并行模式函数吗(for-loop,tasks)

系统地并行化 fortran 2008 `do concurrent`,可能使用 openmp

openmp 共享数组

C++ openmp并行程序在多核linux上如何最大化使用cpu