使用 openmp 并行化矩阵以避免错误共享
Posted
技术标签:
【中文标题】使用 openmp 并行化矩阵以避免错误共享【英文标题】:Parallelize a matrix with openmp to avoid false sharing 【发布时间】:2019-09-13 10:32:46 【问题描述】:我正在尝试优化并行算法:
double update(double *src, int x, int y)
return ((*src)[x][y - 1] + (*src)[x][y + 1] +
(*src)[x - 1][y] + (*src)[x + 1][y])+(*src)[x][y];
void func()
int h = 300;
int w = 300;
int t = 0;
double e = 90.0;
double data[300][300];
double data2[300][300];
while (d >= e)
d = 0.0;
#pragma omp parallel for collapse(2) reduction(+:d) schedule(static,5624)
for (y = 1; y < h - 1; y++)
for (x = 1; x < w - 1; x++)
if(t % 2 == 0)
double o = data2[x][y];
double n = update(data2, x, y);
data[x][y] = n;
d += fabs(o - n);
else
double o = data[x][y];
double n = update(data, x, y);
data2[x][y] = n;
d += fabs(o - n);
t += 1;
此代码并行工作,但当线程数设置为相当高的 16(我正在运行 8 核 i9 CPU)时,程序运行速度比仅使用 8 或 4 个线程时要慢。我假设这可能与错误共享有关,所以我尝试设置一些调度,我的逻辑是这样的:
300*300 = 90k,有 16 个线程 90k/16 = 5625,这是我假设的默认静态调度?
但是我正在写入一个 8 字节的嵌套数据数组,假设我的缓存线是 64 字节,这意味着我可以在缓存线上有 8 个元素,这些元素不应该在内核之间共享。由于 5625%8 不等于 0 是否意味着缓存行是共享的?所以为了解决这个问题,我只是让它减去 1 得到 5624。
我猜这意味着一个线程将多次运行一个循环,但我认为最好不要共享缓存行?
无论如何,最后的结果是共享缓存线 (5625) 的时间是 3.8464 秒,不共享缓存线 (5624) 的时间是 3.8028。差异几乎不惊人,所以我完全误解了这一切是如何运作的?
【问题讨论】:
【参考方案1】:虚假共享很可能不是这里的问题。缓存行只能在不同线程的索引范围相遇的地方重叠,因此在线程 n 将执行的最后几个索引和线程 n+1 的第一个索引.这不仅代表了索引的一小部分,而且不太可能同时运行,您可以想象所有线程都以相似的速度进行,只要范围不太小,这永远不会发生(即线程 n 应该在线程 n+1 到达其范围的末尾之前处理第一个索引)。
这确实给我们带来了问题之一。您的问题不是特别大,单个内核上的 5600 次迭代将花费很短的时间(考虑到与循环的每次迭代相关的操作很少)并且并行化的开销可能比您获得的更大。人们希望编译器优化问题,以便在 while 循环之前创建线程,然后简单地重用它们,但这将是一种相当激进的优化,我不能说我知道它是否知道它应该这样做那个(这也可能取决于您没有共享的编译选项,这样做可能很有用)。您可以尝试自己通过移动 #pragma omp parallel
指令来做到这一点,然后只在里面添加 #pragma omp for
。
您可能会看到 16 个线程时性能下降的另一个原因是超线程。超线程只会在您的线程必须等待经常发生的事情(例如缓存未命中)时提高性能。在您的情况下,这似乎不太可能,并且完全有可能单个线程已经完全利用了核心。在这种情况下,超线程会损害性能,因为操作系统会尝试在核心上给每个线程一些时间,而这些开关不会免费。
最后,由于您的操作非常琐碎,在更大的情况下,您可能会遇到内存带宽的瓶颈。但是,对于小到足以仅将其作为一个整体存储在缓存中的矩阵,情况不应该如此。
但是,确定瓶颈在哪里的最佳方法是进行测试。您可以使用诸如 Intel VTune Amplifier 之类的工具来检查您的代码如何使用可用的 CPU 资源,这样您就可以轻松地确定您的代码大部分时间都花在了哪些方面。
【讨论】:
正如您所说,您希望优化算法,从优化循环嵌套和允许 simd 优化等基础知识开始似乎很重要。如果您有错误共享,那么纠正循环嵌套似乎会有所帮助。以上是关于使用 openmp 并行化矩阵以避免错误共享的主要内容,如果未能解决你的问题,请参考以下文章
C++ Armadillo 和 OpenMp:外积求和的并行化 - 定义 Armadillo 矩阵的约简
如何使用带有串行内循环的openMP并行化外循环以进行数组添加