web-dev-qa-db-ja.com

omp criticalとomp singleの違い

OpenMPの#pragma omp critical#pragma omp singleの正確な違いを理解しようとしています。

これらのマイクロソフトの定義は次のとおりです。

  • シングル:コードのセクションを、必ずしもマスタースレッドではなく、単一のスレッドで実行するように指定できます。
  • 重要:コードは一度に1つのスレッドでのみ実行されることを指定します。

したがって、両方の場合、その後のコードの正確なセクションは1つのスレッドだけで実行され、他のスレッドはそのセクションに入りません。何かを印刷すると、結果が一度画面に表示されますよね?

違いはどうですか?重要なのは実行の時間を大事にしているように見えますが、単一ではありません!しかし、実際には違いはありません!他のスレッド(そのセクションに入らない)の一種の待機または同期は重要であると見なされますが、他のスレッドを単一に保持するものは何もありませんか?実際に結果をどのように変えることができますか?

特に例を挙げて、誰かがこれを明確にできれば幸いです。ありがとう!

26
Amir

singlecriticalは2つの非常に異なるのものです。あなたが言ったように:

  • singleは、コードのセクションを実行することを指定しますシングルスレッドによる(必ずしもマスタースレッドではない)
  • criticalは、コードが実行されることを指定します一度に1スレッドずつ

したがって、前者は実行されます一度だけ後者は実行されますスレッドの数だけ

たとえば、次のコード

int a=0, b=0;
#pragma omp parallel num_threads(4)
{
    #pragma omp single
    a++;
    #pragma omp critical
    b++;
}
printf("single: %d -- critical: %d\n", a, b);

印刷します

single: 1 -- critical: 4

違いがよりよくわかることを願っています。

完全を期すために、以下を追加できます。

  • mastersingleと非常に似ていますが、2つの違いがあります:
    1. masterはマスターによって実行されますが、singleは領域に最初に到達したスレッドによって実行できます。そして
    2. singleには領域の完了時に暗黙的なバリアがあり、すべてのスレッドが同期を待機しますが、masterには同期がありません。
  • atomiccriticalと非常に似ていますが、単純な操作の選択に制限されています。

これらの2組の命令は多くの場合、人々が混乱する傾向があるため、これらの精度を追加しました...

58
Gilles

singlecriticalは、まったく異なる2つのOpenMPコンストラクトのクラスに属します。 singleは、forおよびsectionsと並んで、ワークシェアリング構造です。ワークシェアリング構造は、スレッド間で一定量の作業を分散するために使用されます。そのような構成体は、正しいOpenMPプログラムですべてのスレッドmust実行中にスレッドに遭遇し、さらに同じ順序でbarrier構成体も含むという意味で「集合的」です。 3つのワークシェアリング構造は、3つの異なる一般的なケースをカバーします。

  • for(別名ループ構造)は、スレッドのループの繰り返しを自動的に分散します-ほとんどの場合、すべてのスレッドが処理を行います。
  • sectionsは、一連の独立したコードブロックをスレッド間で分散します。一部のスレッドは作業を行います。これはfor構造の一般化であり、100回の繰り返しを含むループは、たとえばそれぞれ10回の反復を含むループの10セクション。
  • singleは、1つのスレッド(多くの場合、最初に遭遇したコード(実装の詳細))で実行するコードブロックを選び出します。1つのスレッドのみが作業を取得します。 singleは、単一セクションのみのsectionsとほぼ同等です。

すべてのワークシェアリング構造の共通の特徴は、その端に暗黙的なバリアが存在することです。このバリアmightは、nowaitただし、標準ではこのような動作は必要なく、一部のOpenMPランタイムでは、nowaitが存在してもバリアが存在し続ける場合があります。そのため、ワークシェアリング構造体の順序が間違っている(つまり、一部のスレッドで順序が正しくない)ため、デッドロックが発生する可能性があります。バリアが存在する場合、正しいOpenMPプログラムは決してデッドロックしません。

criticalは、masteratomicなどと並んで同期構造です。同期構造は、競合状態を防ぎ、物事の実行に秩序をもたらすために使用されます。

  • criticalは、いわゆる競合グループのスレッド間でのコードの同時実行を防ぐことにより、競合状態を防ぎます。これは、allからのスレッドall同様の名前の重要な構造に遭遇する並列領域が直列化されることを意味します。
  • atomicは、通常、特別なAssembly命令を使用して、特定の単純なメモリ操作をアトミック操作に変換します。アトミックは、単一の壊れないユニットとして一度に完了します。たとえば、あるスレッドによるある場所からのアトミック読み取りは、別のスレッドによる同じ場所へのアトミック書き込みと同時に発生し、古い値または更新された値を返しますが、何らかの中間的なマッシュアップは発生しません。古い値と新しい値の両方からのビット。
  • masterは、マスタースレッド(IDが0のスレッド)のみが実行するコードブロックを選び出します。 singleとは異なり、コンストラクトの最後に暗黙的なバリアはありません。また、すべてのスレッドがmasterコンストラクトに遭遇しなければならないという要件もありません。また、暗黙的なバリアの欠如は、masterがスレッドの共有メモリビューをフラッシュしないことを意味します(これは重要ですが、OpenMPの非常によく理解されていない部分です)。 masterは、基本的にif (omp_get_thread_num() == 0) { ... }の短縮形です。

criticalは、プログラムコードの非常に異なる部分で、異なる並列領域(ネストされた並列処理の場合のみ重要)でも異なるコードをシリアル化できるため、非常に用途の広い構成です。各critical構造には、直後に括弧で囲まれたオプションの名前があります。匿名の重要な構成要素は、同じ実装固有の名前を共有します。スレッドがそのような構造に入ると、同じ名前の別の構造に遭遇する他のスレッドは、元のスレッドがその構造を出るまで保留されます。その後、シリアル化プロセスが残りのスレッドで続行されます。

上記の概念の図を以下に示します。次のコード:

_#pragma omp parallel num_threads(3)
{
   foo();
   bar();
   ...
}
_

次のような結果になります。

_thread 0: -----< foo() >< bar() >-------------->
thread 1: ---< foo() >< bar() >---------------->
thread 2: -------------< foo() >< bar() >------>
_

(スレッド2は意図的に後発です)

single構造内でfoo();呼び出しを行う:

_#pragma omp parallel num_threads(3)
{
   #pragma omp single
   foo();
   bar();
   ...
}
_

次のような結果になります。

_thread 0: ------[-------|]< bar() >----->
thread 1: ---[< foo() >-|]< bar() >----->
thread 2: -------------[|]< bar() >----->
_

ここで、_[ ... ]_はsingle構造のスコープを示し、_|_は最後の暗黙的なバリアです。後発スレッド2が他のすべてのスレッドを待機させる方法に注意してください。スレッド1は、foo()呼び出しを実行します。OpenMPランタイムは、コンストラクトに遭遇する最初のスレッドにジョブを割り当てることを選択する例です。

nowait句を追加すると、暗黙的なバリアが削除され、次のような結果になります。

_thread 0: ------[]< bar() >----------->
thread 1: ---[< foo() >]< bar() >----->
thread 2: -------------[]< bar() >---->
_

匿名のcritical構造内でfoo();呼び出しを行う:

_#pragma omp parallel num_threads(3)
{
   #pragma omp critical
   foo();
   bar();
   ...
}
_

次のような結果になります。

_thread 0: ------xxxxxxxx[< foo() >]< bar() >-------------->
thread 1: ---[< foo() >]< bar() >------------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]< bar() >--->
_

_xxxxx..._を使用すると、スレッドが他のスレッドが同じ名前の重要なコンストラクトを実行するのを待ってから、独自のコンストラクトを入力することができます。

異なる名前の重要な構成要素は互いに同期しません。例えば。:

_#pragma omp parallel num_threads(3)
{
   if (omp_get_thread_num() > 1) {
     #pragma omp critical(foo2)
     foo();
   }
   else {
     #pragma omp critical(foo01)
     foo();
   }
   bar();
   ...
}
_

次のような結果になります。

_thread 0: ------xxxxxxxx[< foo() >]< bar() >---->
thread 1: ---[< foo() >]< bar() >--------------->
thread 2: -------------[< foo() >]< bar() >----->
_

現在、スレッド2は他のスレッドと同期しません。これは、その重要な構造の名前が異なるため、foo()に対して潜在的に危険な同時呼び出しを行うためです。

一方、匿名の重要な構造体(および一般に同じ名前の構造体)は、コードのどこにいても相互に同期します。

_#pragma omp parallel num_threads(3)
{
   #pragma omp critical
   foo();
   ...
   #pragma omp critical
   bar();
   ...
}
_

結果の実行タイムライン:

_thread 0: ------xxxxxxxx[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]------------>
thread 1: ---[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]----------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]< ... >xxxxxxxxxxxxxxx[< bar() >]->
_
27
Hristo Iliev