10

我正在研究 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 子句。这个对吗?减少条款不需要同步吗?

4

3 回答 3

19

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

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

最后,关于sharedandprivate子句。在您的代码中,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
于 2011-06-11T13:35:25.723 回答
9

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

首先,虽然在此示例中不需要共享子句和私有子句,但我不同意康拉德不应该使用它们的观点。人们并行化代码最常见的问题之一是他们没有花时间了解变量的使用方式。不私有化和/或保护应该是的共享变量是我看到的最多问题的原因。通过检查如何使用变量并将它们放入适当的共享、私有等子句的练习将大大减少您遇到的问题的数量。

至于关于障碍的问题,第一个循环可以有一个 nowait 子句,因为在第二个循环中没有使用计算的值 (a)。仅当在计算值之前未使用计算的值 (c) 时,第二个循环才可以有一个 nowait 子句(即,没有依赖关系)。在原始示例代码中,第二个循环有一个 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 可以在第一个和第二个循环上使用,并且在第二个和第三个循环之间不需要障碍(隐式或显式)一点也不。

现在我们进入第三个循环以及您关于 nowait 和 reduction 的问题。正如 Michy 所指出的,OpenMP 规范允许同时指定(reduction 和 nowait)。但是,完成归约不需要同步是不正确的。在示例中,可以使用 nowait 删除隐式屏障(在第三个循环结束时)。这是因为在遇到并行区域的隐式障碍之前没有使用归约(求和)。

如果您查看 OpenMP V3.0 规范的第 2.9.3.6 节缩减子句,您会发现以下内容:

如果不使用 nowait,则归约计算将在构造结束时完成;但是,如果在一个也应用了 nowait 的构造上使用了 reduction 子句,则对原始列表项的访问将产生竞争,因此,除非同步确保它们发生在所有线程都执行完所有迭代之后,否则会产生未指定的效果或部分构造,并且归约计算已完成并存储了该列表项的计算值。这可以通过屏障同步最简单地确保。

这意味着如果您想在第三个循环之后在并行区域中使用 sum 变量,那么在使用它之前您需要一个屏障(隐式或显式)。就像现在的例子一样,它是正确的。

于 2011-06-12T18:45:23.270 回答
2

OpenMP 规范说:

循环结构的语法如下:

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

where 子句是以下之一:

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

所以可以有更多的子句,因此可以同时存在 reduce 和 nowait 语句。

子句中不需要显式同步reduction- 对sum变量的添加是同步的,因为reduction(+: sum)之前的障碍力a并且在循环b时具有最终值。reductionnowait意味着如果线程完成循环中的工作,它不必等到所有其他线程都完成同一个循环。

于 2011-06-11T13:24:57.810 回答