OpenMP如何处理嵌套循环?

use*_*128 31 c++ parallel-processing loops openmp

以下代码是仅仅并行化第一个(外部)循环,还是并行化整个嵌套循环?

    #pragma omp parallel for
    for (int i=0;i<N;i++)
    { 
      for (int j=0;j<M;j++)
      {
       //do task(i,j)//
      }
    }
Run Code Online (Sandbox Code Playgroud)

我只是想确定上面的代码是否会并行化整个嵌套的for循环(因此一个线程直接相关的任务(i,j)),或者它只是并行化外部for循环(因此它确保了每个并行)带有循环索引i的线程,它的内部循环将在一个线程中顺序完成,这非常重要).

Mas*_*ano 44

您编写的行将仅与外部循环并行化.要并行化两者,您需要添加一个collapse子句:

#pragma omp parallel for collapse(2)
    for (int i=0;i<N;i++)
    { 
      for (int j=0;j<M;j++)
      {
       //do task(i,j)//
      }
    }
Run Code Online (Sandbox Code Playgroud)

您可能需要查看OpenMP 3.1规范(第2.5.1节)以获取更多详细信息.


小智 8

您将能够通过以下示例更好地理解这一点.让我们用两个线程做到这一点.

#pragma omp parallel for num_threads(2)
for(int i=0; i< 3; i++) {
    for (int j=0; j< 3; j++) {
        printf("i = %d, j= %d, threadId = %d \n", i, j, omp_get_thread_num());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后结果将是,

i = 0, j= 0, threadId = 0 
i = 0, j= 1, threadId = 0 
i = 0, j= 2, threadId = 0 
i = 1, j= 0, threadId = 0 
i = 1, j= 1, threadId = 0 
i = 1, j= 2, threadId = 0 
i = 2, j= 0, threadId = 1 
i = 2, j= 1, threadId = 1 
i = 2, j= 2, threadId = 1
Run Code Online (Sandbox Code Playgroud)

这意味着,当您将#pragma omp parallel for添加到最上面的for循环时,for循环的索引将在线程之间分配.如您所见,当i的索引相同时,线程ID也是相同的.

取而代之的是,我们可以将嵌套for循环中的组合并行化.在这个例子中,我们可以有i和j的以下组合.

i = 0, j= 0
i = 0, j= 1
i = 0, j= 2
i = 1, j= 0
i = 1, j= 1
i = 1, j= 2
i = 2, j= 0
i = 2, j= 1
i = 2, j= 2
Run Code Online (Sandbox Code Playgroud)

为了明确地并行化代码组合,我们可以按如下方式添加collapse关键字.

#pragma omp parallel for num_threads(2) collapse(2)
for(int i=0; i< 3; i++) {
    for (int j=0; j< 3; j++) {
        printf("i = %d, j= %d, threadId = %d \n", i, j, omp_get_thread_num());
    }
}
Run Code Online (Sandbox Code Playgroud)

那么结果将如下.

i = 0, j= 0, threadId = 0 
i = 0, j= 1, threadId = 0 
i = 1, j= 2, threadId = 1 
i = 2, j= 0, threadId = 1 
i = 2, j= 1, threadId = 1 
i = 2, j= 2, threadId = 1 
i = 0, j= 2, threadId = 0 
i = 1, j= 0, threadId = 0 
i = 1, j= 1, threadId = 0 
Run Code Online (Sandbox Code Playgroud)

然后你可以看到,与以前不同,对于相同的索引i,可以有不同的线程id(当(i = 1且j = 2 threadId = 1)时(i = 1且j = 0 threadId = 0)).这意味着在这种情况下,i和j的组合在线程之间划分.

  • 当循环正确嵌套时,外循环并行化通常是最好的。如果外部循环计数与线程数量相比并不大,并且可以使嵌套循环符合条件,并且不会干扰内部循环优化(例如 simd 矢量化),则上述折叠是一个好方法。 (2认同)