理解OpenMP中collapse子句

43

我发现了一个带有collapse子句的OpenMP代码,这对我来说是新的。我试图理解它的意义,但我不认为我完全掌握了它的含义;我找到的一个定义是:

COLLAPSE:指定嵌套循环中有多少个循环应折叠成一个大迭代空间,并根据schedule子句进行划分。所有相关循环中迭代的顺序决定了折叠迭代空间中的迭代顺序。

我认为我理解了这意味着什么,因此我尝试了以下简单程序:

int i, j;
#pragma omp parallel for num_threads(2) private(j)
for (i = 0; i < 4; i++)
    for (j = 0; j <= i; j++)
        printf("%d %d %d\n", i, j, omp_get_thread_num());

产生了什么

0 0 0
1 0 0
1 1 0
2 0 0
2 1 0
2 2 1
3 0 1
3 1 1
3 2 1
3 3 1

然后我添加了collapse(2)子句。我期望在前两列中得到相同的结果,但现在最后一列有相等数量的01

但是我得到了:

0 0 0
1 0 0
2 0 1
3 0 1

所以我的问题是:

  1. 我的代码中正在发生什么?
  2. 在什么情况下应该使用 collapse
  3. 您能否提供一个示例来展示使用 collapse 和不使用它之间的区别?

很好的问题。您正在尝试融合一个三角形双循环。我不认为收缩适用于此。它需要是方形双循环。[其他SO上的人说收缩适用于三角形循环](https://stackoverflow.com/questions/23950056/how-to-parallel-nested-loop-to-find-the-nearest-two-point-in-openmp)。我还没有阅读规范。如果要融合三角形循环,请参见此[问题](https://dev59.com/9IDba4cB1Zd3GeqPCEei)。虽然,我现在知道使用归纳变量更好的方法。 - Z boson
但如果是一个方形双重循环,使用collapse的好处是什么?无论哪种方式,每个线程都会得到相同数量的迭代。 - iomartin
如果您在折叠之前有两个嵌套循环,其中一个是 n,另一个是 m,那么每个线程得到的迭代次数为 n/nthreads。而在折叠之后,迭代次数为 n*m。这可以帮助解决当 n 相对于 nthreads 不是很大,但 n*m 是很大的情况。 - Z boson
如果您使用C99,它可以省去您私有化循环索引的麻烦... #pragma omp parallel for for (int i = 0; i < 4; i++) for (int j = 0; j <= i; j++) printf("%d %d %d\n", i, j, omp_get_thread_num()); - Jeff Hammond
当前未折叠的输出不正确,每个线程显示5个输出--应该只有外部循环值0和2适用于线程#0(即0 0 0,2 0 0,2 1 0),其他输出应该是线程#1的。 - ofer.sheffer
@iomartin:你展示的第一个输出是错误的。正确的应该是:{0 0 0},{1 0 0},{1 1 0},{2 0 1},{2 1 1},{2 2 1},{3 0 1},{3 1 1},{3 2 1},{3 3 1}。我很惊讶没有人指出来。 - Gaurav Saxena
2个回答

55
你的代码问题在于内部循环的迭代次数取决于外部循环。根据OpenMP规范中关于绑定和collapse子句描述的部分:

如果执行任何相关循环会更改用于计算任何迭代计数的任何值,则行为未指定。

例如,当使用一个正方形循环时,可以使用collapse。

#pragma omp parallel for private(j) collapse(2)
for (i = 0; i < 4; i++)
    for (j = 0; j < 100; j++)

实际上,这是一个很好的使用collapse的例子。外层循环只有四次迭代。如果你有超过四个线程,那么有些线程将会被浪费。但是当你折叠线程时,线程将分布在400次迭代中,这可能比线程数要大得多。使用collapse的另一个原因是负载不均衡。如果你只用了四次迭代,第四次迭代花费的时间最长,其他线程就得等待。但是如果你使用了400次迭代,负载很可能会更加均衡。

你可以像下面这样手动合并循环:

#pragma omp parallel for
for(int n=0; n<4*100; n++) {
    int i = n/100; int j=n%100;

这里提供了一个手动融合三重循环的示例。

最后,这里提供了一个示例,展示如何融合一个无法使用"collapse"指令的三角形循环。


这里提供了一个解决方案,将一个矩形循环映射到OPs问题中的三角形循环。这可以用于融合OPs的三角形循环。

//int n = 4;
for(int k=0; k<n*(n+1)/2; k++) {
    int i = k/(n+1), j = k%(n+1);
    if(j>i) i = n - i -1, j = n - j;
    printf("(%d,%d)\n", i,j);
}

这适用于任何n的值。

对于此问题的OP,映射如下:

(0,0),
(1,0), (1,1),
(2,0), (2,1), (2,2),
(3,0), (3,1), (3,2), (3,3),

(0,0), (3,3), (3,2), (3,1), (3,0),
(1,0), (1,1), (2,2), (2,1), (2,0),

对于奇数n值,该图不完全是一个矩形,但公式仍然有效。

例如,n = 3被映射为

(0,0),
(1,0), (1,1),
(2,0), (2,1), (2,2),
(0,0), (2,2), (2,1), (2,0),
(1,0), (1,1),

这里是测试此功能的代码

#include <stdio.h>
int main(void) {
    int n = 4;
    for(int i=0; i<n; i++) {
        for(int j=0; j<=i; j++) {
            printf("(%d,%d)\n", i,j);
        }
    }
    puts("");
    for(int k=0; k<n*(n+1)/2; k++) {
        int i = k/(n+1), j = k%(n+1);
        if(j>i) i = n - i - 1, j = n - j;
        printf("(%d,%d)\n", i,j);
    }
}

@Gilles,你为什么在我的回答中添加了注释“<!-- language-all: lang-c -->”?那样做有什么意义呢?我不是在抱怨,只是不知道这是干嘛用的。 - Z boson
3
我刚刚按照这里的描述,为C语言添加了语法突出提示。在我的浏览器上,所有的代码片段都是单调沉闷的灰色。现在,在我的浏览器以及其他很多人的浏览器上,C语言的语法被标记颜色了。好吧,输出片段中的索引也是,这可能不是你想要的,但如果你想要的话,它可以被修复。总之,我并不想干涉,但我认为一个好的答案理应拥有好的颜色……我做得太过分了吗? - Gilles
1
@Gilles,我之前不知道这个。谢谢!你改进我的回答我一点也不介意。 - Z boson
但我不知道参数是什么意思?collapse(2)中的2是什么?! - N0rA
2
@N0rA 循环次数。collapse(n) 将以下的 n 个嵌套循环折叠成一个由线程共享的单个并行循环。 - Chris Swierczewski

0
如果您的目的是在增加行数时平衡负载,假设每个项目的工作量是规律或分散的,那么将行索引折叠一半,忽略collapse子句如何?
#pragma omp for
for (int iy0=0; iy0<n; ++iy0){
  int iy = iy0;
  if (iy0 >= n/2) iy = n-1 -iy0 +n/2;
  for (int ix=iy+1; ix<n; ++ix){
    work(ix, iy);
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接