OpenMP:nowait和reduction子句在同一个pragma上
OpenMP: nowait and reduction clauses on the same pragma
我正在学习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子句。这是正确的吗?减少条款不需要同步吗?
第二个和最后一个循环中的nowait
有些冗余。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的观点,即不应该使用它们。人们并行代码最常见的问题之一是,他们没有花时间了解变量是如何使用的。不私有化和/或保护共享变量是我看到的最大问题。通过检查变量是如何使用的,并将它们放入适当的共享、私有等子句中,可以大大减少问题的数量。
至于关于障碍的问题,第一个循环可以有一个nowait子句,因为在第二个循环中没有使用计算出的值(a)。只有在计算值之前没有使用计算的值(c)(即没有依赖项),第二个循环才能有nowait子句。在最初的示例代码中,第二个循环上有一个nowait,但在第三个循环之前有一个显式屏障。这很好,因为你的教授试图展示显式屏障的使用——尽管在第二个循环中去掉nowait会使显式屏障变得多余(因为循环末尾有一个隐式屏障)。
另一方面,第二个循环上的nowait和显式屏障可能根本不需要。在OpenMP V3.0规范之前,许多人认为规范中没有澄清的东西是真的。根据OpenMP V3.0规范,在第2.5.1节"回路构造"表2-1"调度条款"中添加了以下内容,即类值,静态(调度):
静态时间表的合规实施必须确保将逻辑迭代次数分配给线程将在两个循环中使用区域,如果满足以下条件:1)两个循环区域都具有循环迭代次数相同,2)两个循环区域具有相同的值指定了chunk_size,或者两个循环区域都没有指定chunk_size3)两个环区域结合到相同的平行区域。之间的数据依赖性保证在两个这样的循环中满足相同的逻辑迭代允许安全使用nowait条款(参见第170页A.9节示例)。
现在,在您的示例中,任何循环上都没有显示时间表,因此这可能成立,也可能不成立。原因是,默认时间表是由实现定义的,虽然目前大多数实现都将默认时间表定义为静态的,但这并不能保证。如果你的教授在所有三个循环上都设置了一个没有块大小的静态调度类型,那么nowait可以用于第一个和第二个循环,并且在第二个和第三个循环之间根本不需要任何屏障(隐式或显式)。
现在我们进入第三个循环,以及您关于nowait和reduction的问题。正如Michy所指出的,OpenMP规范允许同时指定(reduction和nowait)。然而,不需要同步就可以完成缩减,这是不正确的。在这个例子中,隐式屏障(在第三个循环的末尾)可以用nowait去除。这是因为在遇到平行区域的隐式屏障之前没有使用减少(和)。
如果您查看OpenMP V3.0规范2.9.3.6节的reduction子句,您会发现以下内容:
如果不使用nowait,则在建筑但是,如果在nowait为的构造上使用reduction子句同样适用,对原始列表项的访问将创建一个竞赛,因此未指定的效果,除非同步确保在所有线程执行了它们的所有迭代或截面构造,以及归约计算已完成并存储该列表项的计算值。这可以最简单地通过屏障同步来确保。
这意味着,如果你想在第三个循环后的并行区域中使用sum变量,那么在使用它之前,你需要一个屏障(隐式或显式)。现在的例子是正确的。
OpenMP规范说:
循环结构的语法如下:
#pragma omp for [clause[[,] clause] ... ] new-line for-loops
where子句是以下内容之一:
... reduction(operator: list) ... nowait
所以可以有更多的子句,这样既可以有减少语句也可以有无保留语句。
在reduction
子句中不需要显式同步——由于reduction(+: sum)
和之前的屏障力a
和b
在reduction
循环的时间内具有最终值,因此对sum
变量的添加是同步的。nowait
意味着,如果线程完成了循环中的工作,则不必等到所有其他线程都将完成相同的循环。
- 为什么std::async使用同一个线程运行函数
- 多个"常量引用"变量可以共享同一个内存吗?
- 在头文件和 cpp 文件中使用一次 #pragma 时出现结构重定义错误
- #pragma 包(1)会导致分段错误
- 禁止显示有关包含文件中 #pragma 包的警告
- 在什么情况下,两个堆栈分配的结构对象的 this 点指向同一个地址?
- 编译时检查 #pragma 包的使用情况
- OpenMP #pragma omp for v/s #pragma omp parallel for 之间的区别?
- 为什么同一个变量的内存地址不同?
- 两个进程可以通过跟踪附加到同一个 PID 吗?
- 如何在同一个 CMAKE 项目中强制链接到共享库?
- 是否可以在文本文件中找到最长单词的长度,并在同一个文本文件中读取,只需 1 个 while 循环?
- #pragma(*诊断)当将Clang分析器与GCC编译器混合时
- 是否可以使用非常量指针调用非常量函数,以及当两个unique_ptrs指向同一个对象时程序的行为方式?
- 不同C++文件中未命名命名空间中的名称可以引用同一个命名事物吗?
- 我应该如何使用 epoll 从同一个 FD 读取和写入
- #pragma 警告不适用于 catch 语句
- cuda:多个线程访问同一个全局变量
- 两个抽象类,派生自同一个基类.如何访问从一个抽象类到另一个抽象类的指针
- OpenMP:nowait和reduction子句在同一个pragma上