2

簡単な OpenMP プログラム:

#include <omp.h>
#include <iostream>

int main() {
  int var = 0;
  int var2 = 0;
  #pragma omp parallel private(var) shared(var2)
  {
    var = 1;
    int tid = omp_get_thread_num();
    printf("Inside the parallel region, var gets modified by thread #%d to %d\n",tid,var);
    if(tid == 0)
      var2 = var;
  }
  printf("Outside the parallel region, var  = %d\n", var);
  printf("Outside the parallel region, var2 = %d\n", var2);
}

結果:

Inside the parallel region, var gets modified by thread #3 to 1
Inside the parallel region, var gets modified by thread #0 to 1
Inside the parallel region, var gets modified by thread #6 to 1
Inside the parallel region, var gets modified by thread #1 to 1
Inside the parallel region, var gets modified by thread #5 to 1
Inside the parallel region, var gets modified by thread #7 to 1
Inside the parallel region, var gets modified by thread #2 to 1
Inside the parallel region, var gets modified by thread #4 to 1
Outside the parallel region, var  = 0
Outside the parallel region, var2 = 1

私がやりたいことは、 の値varを、並列領域内で最後に変更された値に設定することです。

#pragma omp forループではないのでlastprivate無効です。

並列領域の外でvarは、元の値 0 を取得します。共有変数を使用してvar2、マスター スレッドから変更された値を格納するのがコツです。

しかし、これはオーバーヘッドを増加させ、エレガントなアプローチではないようです。マスターではなく最後のスレッドによって変更された値を取得したい場合(たとえば、どのスレッドが最後に終了したかを調べるため)、このトリックはうまくいかない。

私は OpenMP にまったく慣れていないので、何かが欠けている可能性があります。そうでない場合、このトリッキーなことを乗り越える方法はありますか?

どうもありがとうございました。

編集:私の質問は、並列領域が終了した後、プライベート変数の最後の値を維持する方法についてです。lastprivateまたは、概念的に での使用が無効である理由を説明できれば#pragma omp parallel、それを完璧な答えと見なします。

4

3 に答える 3

0

lastprivate条項について誤解がある可能性があると思います。OpenMP 標準 (4.0、セクション 2.14.3.5) は次のように述べています。

[...]ワークシェアリング構造を識別するディレクティブにlastprivate節が現れると [...]、関連付けられたループの連続した最後の反復、または字句的に最後のセクション構造からの新しい各リスト項目の値が割り当てられます。元のリスト項目に。

lastprivateここで、「リスト項目」という用語は、句で渡す変数を指します。したがって、どの場合でも、元の変数 ( として宣言されたlastprivate) に割り当てられる値は、多かれ少なかれ決定されるものです。ループの場合、変数に割り当てられる値は、それぞれの順次ループの最後の反復で割り当てられた値と同じになります。セクションの場合は、最後のセクションでの変数への最後の割り当てです。これは、シリアル プログラムに期待されることでもあります。したがって、これらのセマンティクスを変更しても意味がないことは容易に理解できると思います。

一方、lastprivateあなたの例のように、ワークシェアリング (または SIMD) 構造とは異なるもので使用することが許可されている場合、これらのセマンティクスを破ることになります。どのスレッドが最初に終了するかを事前に知ることはできないため、実行のたびに変更される可能性が最も高いものになります (必要に応じて、非決定論的または未定義と呼んでください)。最後の段落で述べた動作とは対照的に、これはおそらくシリアル プログラムに期待するものではありません。lastprivateこれが、非ワークシェアリング構造の欠如についてのあなたの質問に答えてくれることを願っています.

あなたの例に関しては、達成したいことを実装する組み込み機能が OpenMP にあるとは思いません。しかし、私はこのトピックにかなり慣れていないので、これを確定したくありません.

ところで:あなたは言う

並列領域の外でvarは、元の値 0 を取得します。

これは、OpenMP 実装の結果である可能性があります。privateしかし、一般に、元の変数の値は、この変数に関する節を持つ並列領域の後では未定義です。だから私はこれを当然のこととは思わない。

これがあなたの質問に答えることを願っています。

于 2014-05-28T15:04:11.053 に答える
0

どのスレッドが最後に終了したかを調べるには、各スレッドに終了時刻を配列に書き込ませます。配列のサイズは、少なくとも omp_get_max_threads() である必要があります。並列領域内で omp_get_thread_num() を使用してインデックスを作成します。

コードが並列領域を離れたら、配列内の最大値を見つけます。

理想的には、スレッドが終了時刻を書き込むときに共有キャッシュ ラインを通過する必要がないように、各要素が個別のキャッシュ ラインに配置されるように、配列をアラインしてパディングする必要があります。

並列領域がプログラムの外部レベルにある場合は、スレッド プライベート変数がトップレベルの並列領域間で値を保持するという事実を利用して、これを行う別のより巧妙な方法があります。以下は、このトリックを悪用する方法の例です。

#include <omp.h>
#include <stdio.h>
#include <unistd.h>

double tmp;
#pragma omp threadprivate(tmp)

int main() {
    double start = omp_get_wtime();
#pragma omp parallel
    {
        sleep(1);
        tmp = omp_get_wtime();
    }
    double finish=start;
#pragma omp parallel reduction(max:finish)
    {
        if( tmp>finish ) finish = tmp;
    }
    printf("last thread took time = %g\n",finish-start);
}
于 2013-04-19T14:32:18.070 に答える
0

各スレッドは独自のプライベート変数を取得します。このprivate構造は、ここで混乱を引き起こしているだけです。スレッドのチームで最後のプライベート値が必要な場合は、スレッドごとに 1 つを返すだけで十分です。次のようにできます。

int *vala;
int nthreads;
#pragma omp parallel
{
    nthreads = omp_get_num_threads();
    int ithread = omp_get_thread_num();
    #pragma omp single
    vala = new int[nthreads];
    //
    vala[ithread] = ithread;
}
//vala[] = 0,1,2,3,4,5,6,7,8
delete[] vala;

ただし、一般に、メモリを自分で割り当てるのはお勧めできません。各スレッドが独自のプライベート変数にメモリを割り当てられるようにする必要があります。問題は、上記のコードがキャッシュ ラインの左側 (64 バイト) とページ レベル (4096 バイト) の両方で誤った共有を行うことです。これを修正する 1 つの方法valaは、並列ループで書き込みを行わず、代わりにスレッドごとにのみ書き込むことです。例えば

int *vala;
int nthreads;
#pragma omp parallel
{
    int nthreads = omp_get_num_threads();
    int ithread = omp_get_thread_num();
    #pragma omp single
    vala = new int[nthreads];
    int val = 0;
    #pragma omp for
    for(int i=0; i<n; i++) {
        val = i;    
    }
    vala[ithread] = val;
}

これにはまだ偽の共有がありますが、反復ごとではなくスレッドごとに行われるため、影響は重要ではありません。

昨年 OpenMP を使用したとき、最後のスレッドが並列セクションを終了した時刻を知る必要があったことは一度もありません。ただし、順序は重要です。たとえば、操作が結合的で可換的でない場合 (一連の行列乗算など)。その場合、スレッド番号の関数として配列を埋めることができ、静的スケジューリングのチャンクがスレッド番号の増加順に割り当てられるという事実に依存することができますC++ OpenMP: Split for loop in even chunks static and join data at the end .

于 2014-05-28T17:38:37.783 に答える