OpenMP:同一个pragma上的nowait和reduction子句

时间:2011-06-11 12:27:48

标签: c++ openmp reduction

我正在学习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子句。它是否正确?减少条款不需要同步吗?

3 个答案:

答案 0 :(得分:17)

第二个和最后一个循环中的nowait s有些多余。 OpenMP规范在区域结束之前提到nowait,所以也许这可以保留。

但是第二个循环前的nowait和它之后的显式障碍相互抵消了。

最后,关于sharedprivate条款。在您的代码中,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

答案 1 :(得分:9)

在某些方面,这似乎是一个家庭作业问题,我讨厌为人们做。另一方面,上述答案并不完全准确,我觉得应该纠正。

首先,虽然在这个例子中不需要共享和私有条款,但我不同意Konrad不应该使用它们。人们并行化代码的最常见问题之一是他们没有花时间去理解变量的使用方式。不应该私有化和/或保护共享变量,这是我看到的最大数量的问题。通过检查如何使用变量并将它们放入适当的共享,私有等条款中,将大大减少您遇到的问题。

关于障碍的问题,第一个循环可以有一个nowait子句,因为在第二个循环中没有使用计算值(a)。只有在计算值之前未使用计算值(c)时,第二个循环才能具有nowait子句(即,没有依赖性)。在原始示例代码中,第二个循环上有一个nowait,但在第三个循环之前是一个显式屏障。这很好,因为你的教授试图显示使用显式屏障 - 虽然在第二个循环中不使用nowait会使显式屏障变得冗余(因为在循环结束时存在隐式屏障)。 p>

另一方面,根本不需要第二个循环上的现在和明确障碍 。在OpenMP V3.0规范之前,许多人认为某些事情是真的,但在规范中没有说明。使用OpenMP V3.0规范,以下内容添加到2.5.1循环结构,表2-1调度子句种类值,静态(调度):

  

静态计划的兼容实施必须确保相同   将逻辑迭代次数分配给线程将在两个循环中使用   如果满足以下条件: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变量,那么在使用之前你需要一个障碍(隐式或显式)。正如现在的例子所示,这是正确的。

答案 2 :(得分:2)

OpenMP speficication说:

  

循环结构的语法如下:

#pragma omp for [clause[[,] clause] ... ] new-line
    for-loops
     

where子句是以下之一:

 ...
 reduction(operator: list)
 ...
 nowait

因此可以有更多的条款,因此可以有减少和现在的陈述。

reduction子句中不需要显式同步 - 由于sum以及之前的屏障力reduction(+: sum)和{{{{}}},a变量的添加已同步1}}在b循环的时间内具有最终值。 reduction表示如果线程完成循环中的工作,则不必等到所有其他线程完成相同的循环。