2017-05-10 10 views
0

parallel forループ内で値を合計ため、次の簡単なコードを考えてみてください:OpenMP並列-の効率クエリ

int nMaxThreads = omp_get_max_threads(); 
int nTotalSum = 0; 
#pragma omp parallel for num_threads(nMaxThreads) \ 
    reduction(+:nTotalSum) 
    for (int i = 0; i < 4; i++) 
    { 
     nTotalSum += i; 
     cout << omp_get_thread_num() << ": nTotalSum is " << nTotalSum << endl; 
    } 

私は2つのコアマシン上でこれを実行すると、私が手出力が

です
0: nTotalSum is 0 
0: nTotalSum is 1 
1: nTotalSum is 2 
1: nTotalSum is 5 

これは、クリティカルセクション、つまりnTotalSumの更新が各ループで実行されていることを示唆しています。これは無駄に思えますが、各スレッドが行う必要がある場合は、追加する値の 'ローカル'合計を計算してから、nTotalSumをこの 'ローカル合計'に更新してから更新してください。

私の出力の解釈は正しいのですか?もしそうなら、どうすればより効率的にすることができますか?

#pragma omp parallel for num_threads(nMaxThreads) \ 
    reduction(+:nTotalSum) 
    int nLocalSum = 0; 
    for (int i = 0; i < 4; i++) 
    { 
     nLocalSum += i; 
    } 
    nTotalSum += nLocalSum; 

...しかし、コンパイラは、各OMPスレッドはnTotalSumの独自のコピーを持っている...それはpragma omp parallel for次の文forループを期待していたことを

答えて

2

あなたの出力は実際にループ中にクリティカルセクションを示していません。各スレッドには、ゼロ初期化コピーがあり、スレッド0はi = 0,1で、スレッド1はi = 2,3で作業しています。最後に、OpenMPはローカルコピーをオリジナルに追加します。

より効率的に行うことができるという特定の証拠がない限り、自分で実装しないでください。たとえば、this question/answerを参照してください。あなたは2つのディレクティブにparallel/forを分割する場合

あなたのマニュアルのバージョンは動作します:

int nTotalSum = 0; 
#pragma omp parallel 
{ 
    // Declare the local variable it here! 
    // Then it's private implicitly and properly initialized 
    int localSum = 0; 
    #pragma omp for 
    for (int i = 0; i < 4; i++) { 
    localSum += i; 
    cout << omp_get_thread_num() << ": nTotalSum is " << nTotalSum << endl; 
    } 
    // Do not forget the atomic, or it would be a race condition! 
    // Alternative would be a critical, but that's less efficient 
    #pragma omp atomic 
    nTotalSum += localSum; 
} 

私はそれがあなたのOpenMPの実装は、ちょうどそのような削減を行う可能性が高いと思います。

+0

ありがとうございます。ループの前とループ内のアドレスを出力することで、各スレッドに 'nTotalSum'のローカルコピーがあることを確認しました。予想どおり、3つの異なるアドレスがありました。ループの前と後の変数の値、および2つのスレッドのそれぞれの変数の値です。 – Wad

2

を述べ訴え:私は次のことを試してみました注意してください。 OMPセクションの最後には、元のnTotalSumに結合されています。表示されている出力は、ループの繰り返し(0,1)を1つのスレッドで、(2,3)を別のスレッドで実行しています。あなたは、ループの終わりにnTotalSum出力、あなたは6.

nLocalSum例の予想結果を見る必要がある場合は、#pragma omp行の前にnLocalSumの宣言を移動します。 forループは、プラグマの直後の行になければなりません。 OpenMPの本の中で私の並列プログラミングから

0

reduction節を理解するトリッキーすることができ、プライベートと共有ストレージの動作の両方を持っています。 reduction属性は、算術演算の削減の対象となるオブジェクトで使用されます。これは多くのアプリケーションで重要になる可能性があります...削減は、コンパイラによって効率的に実装されることを可能にします...これは、openmpがそれらを処理するために削減データスコープ節を持つような一般的な操作です...最も一般的な例は、並列構造の終わりに一時的な局所変数があります。あなたの第二の例に

訂正:あなたの最初の例で

total_sum = 0; /* do all variable initialization prior to omp pragma */ 

#pragma omp parallel for \ 
      private(i) \ 
      reduction(+:total_sum) 

    for (int i = 0; i < 4; i++) 
    { 
     total_sum += i; /* you used nLocalSum here */ 
    } 

#pragma omp end parallel for 

/* at this point in the code, 
    all threads will have done your `for` loop where total_sum is local to each thread, 
    openmp will then '+" together the values in `total_sum` coming from each thread because we used reduction, 
    do not do an explicit nTotalSum += nLocalSum after the omp for loop, it's not needed the reduction clause takes care of this 
*/ 

、私はnum_threads(nMaxThreads)が何をしているかの#pragma omp parallel for num_threads(nMaxThreads) reduction(+:nTotalSum)をご利用のかわかりません。しかし、私は奇妙な出力が印刷バッファリングによって引き起こされる可能性があると思う。

いずれの場合でも、還元節は非常に便利で、正しく使用すると非常に効率的です。より複雑で現実的な例では、より明白になるでしょう。

あなたの投稿の例では、そう、それはreduction節の有用性を誇示しないという単純な とすべてのスレッドがそれを行うための最も 効率的道はただになるだろう和をやっているので、厳密にあなたの例のために話しています total_sum並列セクション内の共有変数で、すべてのスレッドがそれにポンプインします。最後に答えは正しいでしょう。 は、クリティカルディレクティブを使用すると機能します。

+0

"これを実行する最も効率的な方法は、total_sumをパラレルセクションの共有変数にして、すべてのスレッドがそれにポンプインすることです。"それは両方とも間違っている(競合状態)と非効率的である(競合)。あなたのコードはC/OpenMPのようには見えません。 – Zulan

+0

申し訳ありませんが、投稿が悪いです。私はそれを打ちました。複雑なデータ依存がないかどうかを示すために、削減節以外の方法があります。私はしばしばFortran構文をcと混同しますが、変更されたプラグマ構文を変更しました。 – ron

+0

2レベルのループでは、simd reductionで内部和を、parallel reductionでouterを使用する方が効率的です。詳細はコンパイラによって異なります。 – tim18