OpenMP parallel for -- 多个并行 for 的 Vs。一个包含多个 for 的平行线

OpenMP parallel for -- Multiple parallel for's Vs. one parallel that includes within it multiple for's

我正在经历 Using OpenMP。作者比较和对比了以下两种结构:

//Construct 1
#pragma omp parallel for
for( ... )
{
    /* Work sharing loop 1 */
}
...
#pragma omp parallel for
for( ... )
{
    /* Work sharing loop N */
}

对比

//Construct 2
#pragma omp parallel
{
    #pragma omp for    
    for( ... )
    {
        /* Work sharing loop 1 */
    }
    ...
    #pragma omp for    
    for( ... )
    {
        /* Work sharing loop N */
    }
}

他们说 Construct 2

has fewer implied barriers, and there might be potential for cache data reuse between loops. The downside of this approach is that one can no longer adjust the number of threads on a per loop basis, but that is often not a real limitation.

我很难理解 Construct 2 的隐含障碍是如何减少的。由于 #pragma omp for,在每个 for 循环之后,Construct 2 中是否没有隐含的障碍?那么,在每种情况下,隐含障碍的数量不都相同吗,N?也就是说,Construct 2中不就是第一个循环先出现,以此类推,最后执行第N个for循环吗?

此外,Construct 2 如何更有利于循环间的缓存重用?

I am having a difficult time understanding how Construct 2 has fewer implied barriers. Is there not an implied barrier in Construct 2 after each for loop due to #pragma omp for? So, in each case, isn't the number of implied barriers the same, N? That is, is it not the case in Construct 2 that the first loop occurs first, and so on, and then the Nth for loop is executed last?

我没有读过这本书,但根据你所展示的内容,它实际上是相反的,即:

 //Construct 1
#pragma omp parallel for
for( ... )
{
    /* Work sharing loop 1 */
} // <-- implicit barrier 
...
#pragma omp parallel for
for( ... )
{
    /* Work sharing loop N */
} // <-- implicit barrier.

N个隐式障碍(在每个平行区域的末尾),而第二个代码:

 //Construct 2
#pragma omp parallel
{
    #pragma omp for    
    for( ... )
    {
        /* Work sharing loop 1 */
    } <-- implicit barrier
    ...
    #pragma omp for    
    for( ... )
    {
        /* Work sharing loop N */
    } <-- implicit barrier
} <-- implicit barrier

有N+1个障碍(在每个的末尾+平行区域)。

实际上,在这种情况下,由于最后两个隐式障碍之间没有计算,可以将nowait添加到最后一个#pragma omp for以消除冗余障碍之一。

如果您将 nowait 子句添加到 #pragma omp for 子句,则第二个代码比第二个代码具有更少隐式障碍的一种方法是。

来自 link 关于您所展示的书:

Finally, Using OpenMP considers trends likely to influence OpenMP development, offering a glimpse of the possibilities of a future OpenMP 3.0 from the vantage point of the current OpenMP 2.5. With multicore computer use increasing, the need for a comprehensive introduction and overview of the standard interface is clear.

所以这本书使用的是 old OpenMP 2.5 标准,从 standard 关于 loop 构造函数可以阅读:

There is an implicit barrier at the end of a loop constructor unless a nowait clause is specified.

A nowait 不能添加到 parallel 构造函数,但可以添加到 for 构造函数。因此,如果可以将 nowait 子句添加到 #pragma omp for 子句,则第二个代码具有 潜力 来减少隐式障碍。然而,事实上,第二个代码实际上比第一个代码有更多的隐含障碍。

Also, how is Construct 2 more favorable for cache reuse between loops?

如果您在线程之间使用 static 循环迭代分布(例如,第二个代码中的 #pragma omp for scheduler(static, ...),相同的线程将是使用相同的循环迭代。例如,对于两个线程,我们称它们为 Thread AThread B。如果我们假设 static 分布 chunk=1, Thread AB 将分别使用每个循环的奇数和偶数迭代。因此,根据实际的应用程序代码,这可能意味着这些线程将使用相同的内存位置给定的数据结构(例如, 相同的数组位置)。

在第一个代码中,理论上(但是这将取决于具体的OpenMP实现),由于有两个不同的并行区域,不同的线程可以选择相同的循环跨越两个循环的迭代。换句话说,在我们使用两个线程的示例中,无法保证在一个循环中计算偶数(或奇数)的同一线程会在其他循环中计算相同的数字。