web-dev-qa-db-ja.com

OpenMPの折りたたみ句を理解する

折りたたみ句を含むOpenMPコードに出会いましたが、これは私にとっては新しいものでした。私はそれが何を意味するのか理解しようとしていますが、その意味を完全に理解しているとは思いません。私が見つけた1つの定義は次のとおりです。

[〜#〜] collapse [〜#〜] :ネストされたループ内のループを1つの大きな反復スペースに折り畳み、スケジュール句に従って分割するループ数を指定します。関連するすべてのループでの反復の順次実行により、縮小された反復空間での反復の順序が決まります。

私はそれが何を意味するのか理解できたと思ったので、次の簡単なプログラムを試しました。

_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)句を追加しました。最初の2つの列で同じ結果になると予想していましたが、最後の列で_0_と_1_の数が等しくなりました。しかし、私は得た

_0 0 0
1 0 0
2 0 1
3 0 1
_

だから私の質問は:

  1. 私のコードで何が起こっていますか?
  2. どのような状況でcollapseを使用する必要がありますか?
  3. collapseを使用する場合と使用しない場合の違いを示す例を提供できますか?
24
iomartin

コードの問題は、内側のループの繰り返しが外側のループに依存することです。バインディングとcollapse節に関するセクションの説明にあるOpenMP仕様によると:

関連するループの実行により、反復カウントの計算に使用される値のいずれかが変更される場合、動作は指定されません。

正方形ループの場合など、これが当てはまらない場合は、折りたたみを使用できます

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

実際、これは折りたたみをいつ使用するかを示す良い例です。外側のループには4つの反復しかありません。 4つを超えるスレッドがある場合、一部が無駄になります。ただし、折りたたむと、スレッドは400回の反復間で分散され、スレッドの数よりもはるかに多くなる可能性があります。折りたたみを使用するもう1つの理由は、負荷が適切に分散されていない場合です。 4回の反復のみを使用し、4回目の反復にほとんどの時間がかかった場合、他のスレッドは待機します。ただし、反復を400回使用すると、負荷の分散が向上する可能性があります。

このように上記のコードのループを手動で融合することができます

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

ここ は、三重に融合したループを手動で融合する方法を示す例です。

最後に、 here は、collapseが定義されていない三角ループを融合する方法を示す例です。


これは、OP質問の長方形ループを三角形ループにマップするソリューションです。これを使用して、OPの三角ループを融合できます。

//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);
    }
}
31
Z boson

目的が、各アイテムのワークロードが規則的または十分に分散していると仮定して、増加する行にわたって負荷を分散することである場合、行インデックスを半分に折り、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);
  }
}
0
h2kyeong