OpenMP:同一个编译指示上的 nowait 和 reduction 子句
Posted
技术标签:
【中文标题】OpenMP:同一个编译指示上的 nowait 和 reduction 子句【英文标题】:OpenMP: nowait and reduction clauses on the same pragma 【发布时间】:2011-09-13 01:00:15 【问题描述】:我正在研究 OpenMP,并遇到了以下示例:
#pragma omp parallel shared(n,a,b,c,d,sum) private(i)
#pragma omp for nowait
for (i=0; i<n; i++)
a[i] += b[i];
#pragma omp for nowait
for (i=0; i<n; i++)
c[i] += d[i];
#pragma omp barrier
#pragma omp for nowait reduction(+:sum)
for (i=0; i<n; i++)
sum += a[i] + c[i];
/*-- End of parallel region --*/
在最后一个for循环中,有一个nowait和一个reduction子句。它是否正确?减少子句不需要同步吗?
【问题讨论】:
我很好奇,你在哪里找到这个例子的? 这个例子是我的教授给出的。它旨在解释显式障碍的使用,但我不确定最终循环是否正确。 【参考方案1】:第二个和最后一个循环中的nowait
s 有点多余。 OpenMP 规范在区域结束之前提到了nowait
,所以也许这可以保留。
但是第二个循环之前的nowait
和它之后的显式屏障相互抵消了。
最后,关于shared
和private
子句。在您的代码中,shared
没有任何作用,并且根本不应该使用 private
:如果您需要一个线程私有变量,只需在并行区域内声明它。特别是,您应该在循环内部声明循环变量,而不是之前。
要使shared
有用,您需要告诉 OpenMP 默认情况下它不应共享任何内容。您应该这样做是为了避免由于意外共享变量而导致的错误。这是通过指定default(none)
来完成的。这给我们留下了:
#pragma omp parallel default(none) shared(n, a, b, c, d, sum)
#pragma omp for nowait
for (int i = 0; i < n; ++i)
a[i] += b[i];
#pragma omp for
for (int i = 0; i < n; ++i)
c[i] += d[i];
#pragma omp for nowait reduction(+:sum)
for (int i = 0; i < n; ++i)
sum += a[i] + c[i];
// End of parallel region
【讨论】:
嘿@Konrad Rudolph 你能看看我的代码吗?我不明白你为什么说我们不需要使用共享和私有,我正在开发 VS2012default(none) shared(...)
的使用是确保并行性正常工作的重要保障。
@Richard 原代码没有使用default(none)
。也就是说,我同意最好明确说明共享的内容,并防止意外共享。我将其添加到答案中。【参考方案2】:
在某些方面,这似乎是一个家庭作业问题,我讨厌为人们做。另一方面,上面的答案并不完全准确,我觉得应该更正。
首先,虽然在此示例中不需要共享子句和私有子句,但我不同意康拉德不应该使用它们的观点。人们并行化代码最常见的问题之一是他们没有花时间了解变量的使用方式。不私有化和/或保护应该是的共享变量是我看到的最多问题的原因。通过检查如何使用变量并将它们放入适当的共享、私有等子句的练习将大大减少您遇到的问题。
关于障碍的问题,第一个循环可以有一个 nowait 子句,因为在第二个循环中没有使用计算的值 (a)。仅当在计算值之前未使用计算的值 (c) 时,第二个循环才能具有 nowait 子句(即,没有依赖关系)。在原始示例代码中,第二个循环有一个 nowait,但在第三个循环之前有一个明确的屏障。这很好,因为您的教授试图展示显式障碍的使用 - 尽管在第二个循环中不使用 nowait 会使显式障碍变得多余(因为在循环结束时存在隐式障碍)。
另一方面,第二个循环的 nowait 和显式屏障可能根本不需要。在 OpenMP V3.0 规范之前,许多人认为规范中没有说明的事情是真实的。在 OpenMP V3.0 规范中,以下内容已添加到第 2.5.1 节循环构造、表 2-1 调度子句 kind 值、静态(调度):
静态调度的合规实现必须确保相同的 将逻辑迭代数分配给线程将在两个循环中使用 如果满足以下条件的区域:1)两个循环区域都有 相同数量的循环迭代,2)两个循环区域具有相同的值 指定了 chunk_size,或者两个循环区域都没有指定 chunk_size,并且 3) 两个循环区域都绑定到同一个并行区域。之间的数据依赖 保证满足两个这样的循环中的相同逻辑迭代 允许安全使用 nowait 子句(参见第 170 页的第 A.9 节 示例)。
现在在您的示例中,任何循环都没有显示时间表,因此这可能会或可能不会成立。原因是,默认调度是实现定义的,虽然大多数实现当前将默认调度定义为静态的,但不能保证这一点。如果您的教授在所有三个循环上都设置了一个没有 chunk-size 的静态调度类型,那么 nowait 可以在第一个和第二个循环上使用,并且没有障碍(隐式或显式)完全需要在第二个和第三个循环之间。
现在我们进入第三个循环以及您关于 nowait 和 reduction 的问题。正如 Michy 所指出的,OpenMP 规范允许同时指定(reduction 和 nowait)。但是,完成归约不需要同步是不正确的。在示例中,可以使用 nowait 删除隐式屏障(在第三个循环结束时)。这是因为在遇到并行区域的隐式障碍之前没有使用归约(求和)。
如果您查看 OpenMP V3.0 规范,第 2.9.3.6 节缩减子句,您会发现以下内容:
如果不使用 nowait,则归约计算将在结束时完成 构造;但是,如果减少子句用于 nowait 的构造 同样适用,对原始列表项的访问将创建一个竞赛,因此, 未指定的效果,除非同步确保它们发生在所有线程完成之后 执行所有的迭代或部分构造,以及归约计算 已完成并存储了该列表项的计算值。这可以最简单地是 通过屏障同步确保。
这意味着如果您想在第三次循环之后在并行区域中使用 sum 变量,那么在使用它之前您需要一个屏障(隐式或显式)。就目前的例子而言,它是正确的。
【讨论】:
感谢您非常详细的回答ejd!我同意你的观点,事实上,这似乎是一个家庭作业问题,因为这不是我的代码——我刚刚开始通过学习示例来学习 OpenMP。如果你知道一个更合适的地方问这些问题,或者一个标签来代表它们(类似于 homework 标签),请告诉我。 我不同意shared
/private
子句的使用,我在回答中对此进行了扩展:如果您使用私有变量,将它们设为私有。不要依赖预处理子句,在本地范围内声明它们。在 C++ 中没有没有 理由不这样做,事实上,在 C++ 中应该总是 来声明接近用法的变量;因此,使用 private
是 C++ 中代码异味的明显标志。
Konrad - 由于该示例没有说 C 或 C++ 并且在 C89 中您无法执行您所展示的操作,因此我相信提出来是有效的。最重要的是,正如我所说,在过去 12 年中我看到的 OpenMP 中最常见的问题是用户没有花时间去查看变量在他们的代码中是如何使用的。我个人认为默认应该是要求用户说明如何使用每个变量。它肯定会减少发生的问题的数量。与此同时,我将继续研究工具来帮助人们找出他们做错了什么。
关于在 C++ 中避免私有的 cmets 同样适用于 C。您不应该对 OpenMP 使用过时的(在 C99 或标准 C++ 之前)语法。示例要求更新,也可以到 OpenMP 4.0。我也想知道为什么甚至允许 default(private) ,尽管它无论如何都不会在这里工作。我认为没有消除 firstprivate/lastprivate 的好方法,只是循环索引不需要它们(尽管 omp_cancel 可能需要一些解决方法)。【参考方案3】:
OpenMP speficication 说:
循环构造的语法如下:
#pragma omp for [clause[[,] clause] ... ] new-line for-loops
where 子句是下列之一:
... reduction(operator: list) ... nowait
因此可以有更多的子句,因此可以同时存在 reduce 和 nowait 语句。
在 reduction
子句中不需要显式同步 - 对 sum
变量的添加是同步的,因为 reduction(+: sum)
和先前的障碍力 a
和 b
在reduction
循环。 nowait
表示如果线程完成循环中的工作,它不必等到所有其他线程都完成同一个循环。
【讨论】:
感谢您的澄清。以上是关于OpenMP:同一个编译指示上的 nowait 和 reduction 子句的主要内容,如果未能解决你的问题,请参考以下文章
使用非线程安全随机数生成器在 C 中为 pi monte carlo 更正 OpenMP 编译指示