OpenMP 如何处理嵌套循环?
Posted
技术标签:
【中文标题】OpenMP 如何处理嵌套循环?【英文标题】:How does OpenMP handle nested loops? 【发布时间】:2012-11-01 15:57:19 【问题描述】:以下代码是仅并行化第一个(外部)循环,还是并行化整个嵌套循环?
#pragma omp parallel for
for (int i=0;i<N;i++)
for (int j=0;j<M;j++)
//do task(i,j)//
我只是想确定上面的代码是否将并行化整个嵌套的 for 循环(因此一个线程直接相关任务(i,j)),或者它只并行化外部 for 循环(因此它确保,对于每个循环索引为 i 的并行线程,其内部循环将在单个线程中按顺序完成,这是非常重要的)。
【问题讨论】:
【参考方案1】:您编写的行将仅并行化外循环。要并行化两者,您需要添加 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)//
您可能需要查看OpenMP 3.1 规范(第 2.5.1 节)了解更多详情。
【讨论】:
谢谢,太好了,我只想并行外部循环,同时确保内部循环按顺序完成工作。 在这种情况下我是否需要将i
和j
声明为私有?
@giannis gonidelis,不,你没有。有人告诉我这样做是一种很好的做法,但我个人不喜欢这样做 - 因为不将 i 和 j 声明为私有更容易/更简单。
i
是循环索引,因此默认为私有。对于j
,这将是两种不同的情况。如果j
在 for 循环之外声明,它将是公共的,您应该将其声明为私有以确保答案正确。如果 j
在 for 循环中声明,它将是私有的,因此无需手动声明。【参考方案2】:
通过以下示例,您将能够更好地理解这一点。 让我们用两个线程来做。
#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());
那么结果就是,
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
这意味着,当您将#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
为了使代码组合更加并行化,我们可以添加折叠关键字,如下所示。
#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());
那么结果如下。
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
然后你可以看到,与之前不同的是,对于同一个索引 i,可以有不同的线程 id(when (i=1 and j=2 threadId=1) 也可以是 (i=1 and j=0 threadId=0) )。这意味着在这种情况下,i 和 j 的组合在线程之间进行划分。
【讨论】:
在正确嵌套循环的情况下,外部循环并行化通常是最好的。如果外层循环数与线程数相比并不大,如果可以使嵌套循环符合条件,并且不干扰 simd 矢量化等内层循环优化,则上述折叠是一个很好的方法。 由于数据依赖性,并非所有循环都是可折叠的,所以一般的答案是否定的,因为嵌套并行并不适用于每个循环,这就是为什么人们更多地转向 GPU 的三个层次并行性以上是关于OpenMP 如何处理嵌套循环?的主要内容,如果未能解决你的问题,请参考以下文章