问题描述
||
我正在研究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和reduce子句。它是否正确?归约条款是否需要同步化?
解决方法
第二个循环和最后一个循环中的1有点多余。 OpenMP规范在区域结尾之前提到了“ 1”,因此也许可以保留。
但是第二个循环之前的ѭ1和它之后的显式壁垒相互抵消。
最后,关于
shared
和private
子句。在您的代码中,“ 4”无效,而根本不应该使用“ 5”:如果需要线程专用变量,只需在并行区域内声明即可。特别是,您应该在循环内部而不是之前声明循环变量。
要使“ 4”有用,您需要告诉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的观点,不应使用它们。人们并行化代码最常见的问题之一是,他们没有花时间去理解变量的使用方式。没有私有化和/或保护应该是的共享变量,导致了我看到的最大数量的问题。进行检查如何使用变量并将其放入适当的shared,private等子句中的练习将极大地减少您遇到的问题。
至于障碍的问题,第一个循环可以有一个nowait子句,因为在第二个循环中没有使用(a)计算的值。仅当在计算值之前不使用计算的值(c)(即没有依赖性)时,第二个循环才可以具有nowait子句。在原始示例代码中,第二个循环没有等待,但第三个循环之前有显式障碍。这很好,因为您的教授试图显示显式屏障的使用-尽管在第二个循环中不使用nowait将使显式屏障变得多余(因为循环的末尾存在隐式屏障)。
另一方面,可能根本不需要第二个循环的nowait和显式的障碍。在OpenMP V3.0规范之前,许多人都认为在规范中未作说明的事情是对的。使用OpenMP V3.0规范,以下内容已添加到2.5.1循环构造部分中,表2-1调度子句种类值,静态(调度):
静态时间表的合规实现必须确保相同
将逻辑迭代编号分配给线程将在两个循环中使用
满足以下条件的区域:1)两个循环区域都具有
相同数量的循环迭代,2)两个循环区域的值相同
指定了chunk_size,或者两个循环区域都没有指定chunk_size,并且3)
两个环区都结合到相同的平行区。数据之间的依赖
保证满足两个这样的循环中相同的逻辑迭代
允许安全使用nowait子句(请参阅第170页第A.9节
例子)。
现在,在您的示例中,任何循环都没有显示时间表,因此这可能成立也可能不会成立。原因是,默认时间表是由实现定义的,尽管当前大多数实现将默认时间表定义为静态的,但不能保证这一点。如果您的教授在所有三个循环上都设置了静态的调度类型,而没有块大小,则可以在第一和第二个循环上使用nowait,并且在第二个和第三个循环之间不需要任何屏障(隐式或显式)完全没有
现在我们进入第三个循环,以及您有关无等待和减少的问题。正如Michy指出的,OpenMP规范允许同时指定(减少和不等待)。但是,并非完全不需要同步即可完成还原。在该示例中,可以使用nowait删除隐式屏障(在第三循环的末尾)。这是因为在遇到并行区域的隐式屏障之前未使用缩减(总和)。
如果查看OpenMP V3.0规范的2.9.3.6节reduce子句,则会发现以下内容:
如果不使用nowait,则缩减计算将在结束时完成。
构造;但是,如果将减少子句用于不等待的构造
同样适用于原始列表项的访问将创建种族,因此具有
未指定效果,除非同步确保它们在所有线程都具有之后发生
执行所有的迭代或节构造,以及约简计算
已经完成并存储了该列表项的计算值。这很简单
通过障碍同步确保。
这意味着,如果您想在第三个循环之后在并行区域中使用sum变量,那么在使用它之前,您将需要一个屏障(隐式或显式)。就目前的例子而言,这是正确的。
,OpenMP的特点是:
循环构造的语法如下:
#pragma omp for [clause[[,] clause] ... ] new-line
for-loops
where子句是以下之一:
...
reduction(operator: list)
...
nowait
因此,可以有更多的子句,因此可以同时有reduce和nowait语句。
reduction
子句中不需要显式同步-由于reduction(+: sum)
和先前的屏障力a
和b
在reduction
循环时具有最终值,因此对sum
变量的添加是同步的。 “ 1”表示如果线程在循环中完成工作,则不必等待所有其他线程都将完成同一循环。