OpenMPの違いは何ですか?
#pragma omp parallel sections
{
#pragma omp section
{
fct1();
}
#pragma omp section
{
fct2();
}
}
および:
#pragma omp parallel
{
#pragma omp single
{
#pragma omp task
fct1();
#pragma omp task
fct2();
}
}
2番目のコードが正しいかどうかわかりません...
タスクとセクションの違いは、コードが実行される時間枠にあります。セクションはsections
コンストラクトで囲まれ、(nowait
句が指定されていない限り)スレッドはすべてのセクションが実行されるまでそれを残しません。
_ [ sections ]
Thread 0: -------< section 1 >---->*------
Thread 1: -------< section 2 >*------
Thread 2: ------------------------>*------
... *
Thread N-1: ---------------------->*------
_
ここで、N
スレッドは、2つのセクションを持つsections
コンストラクトに遭遇します。最初の2つのスレッドは、それぞれ1つのセクションを実行します。他の_N-2
_スレッドは、セクションコンストラクトの最後にある暗黙のバリアで単純に待機します(ここでは_*
_として表示)。
タスクは、いわゆるタスクスケジューリングポイントで可能な限りキューに入れられ、実行されます。ある条件下では、ランタイムは、その寿命の途中であっても、スレッド間でタスクを移動することを許可される可能性があります。このようなタスクはアンタイドと呼ばれ、アンタイドタスクは1つのスレッドで実行を開始し、あるスケジューリングポイントでランタイムによって別のスレッドに移行される場合があります。
それでも、タスクとセクションは多くの点で似ています。たとえば、次の2つのコードフラグメントは、本質的に同じ結果を達成します。
_// sections
...
#pragma omp sections
{
#pragma omp section
foo();
#pragma omp section
bar();
}
...
// tasks
...
#pragma omp single nowait
{
#pragma omp task
foo();
#pragma omp task
bar();
}
#pragma omp taskwait
...
_
taskwait
はbarrier
と非常によく似ていますが、タスクの場合-現在の実行フローは、キューに入れられたすべてのタスクが実行されるまで一時停止します。これはスケジューリングポイントです。つまり、スレッドがタスクを処理できるようにします。タスクが1つのスレッドのみで作成されるように、single
構成が必要です。 single
コンストラクトがなかった場合、各タスクは_num_threads
_回作成されますが、これは望んでいない場合があります。 nowait
コンストラクトのsingle
句は、single
コンストラクトが実行されるまで待機しないように他のスレッドに指示します(つまり、single
コンストラクト)。したがって、彼らはすぐにtaskwait
をヒットし、タスクの処理を開始します。
taskwait
は、明確にするためにここに示す明示的なスケジューリングポイントです。明示的か暗黙的かに関係なく、最も重要なのはバリア同期内の暗黙的なスケジューリングポイントもあります。したがって、上記のコードは単純に次のように書くこともできます。
_// tasks
...
#pragma omp single
{
#pragma omp task
foo();
#pragma omp task
bar();
}
...
_
以下に、3つのスレッドがある場合に発生する可能性のあるシナリオの1つを示します。
_ +--+-->[ task queue ]--+
| | |
| | +-----------+
| | |
Thread 0: --< single >-| v |-----
Thread 1: -------->|< foo() >|-----
Thread 2: -------->|< bar() >|-----
_
ここに表示される_| ... |
_は、スケジューリングポイントのアクション(taskwait
ディレクティブまたは暗黙的なバリアのいずれか)です。基本的に、スレッド_1
_および_2
_は、その時点で実行していることを中断し、キューからタスクの処理を開始します。すべてのタスクが処理されると、スレッドは通常の実行フローを再開します。スレッド_1
_および_2
_は、スレッド_0
_がsingle
コンストラクトを終了する前にスケジューリングポイントに到達する可能性があるため、左側の_|
_ sを揃える必要はありません。 (これは上の図に表されています)。
また、スレッド_1
_がfoo()
タスクの処理を終了し、他のスレッドがタスクを要求できるようになる前に別のタスクを要求できる場合もあります。したがって、foo()
とbar()
の両方が同じスレッドによって実行される可能性があります。
_ +--+-->[ task queue ]--+
| | |
| | +------------+
| | |
Thread 0: --< single >-| v |---
Thread 1: --------->|< foo() >< bar() >|---
Thread 2: --------------------->| |---
_
スレッド2が遅すぎる場合、シングルアウトスレッドが2番目のタスクを実行する可能性もあります。
_ +--+-->[ task queue ]--+
| | |
| | +------------+
| | |
Thread 0: --< single >-| v < bar() >|---
Thread 1: --------->|< foo() > |---
Thread 2: ----------------->| |---
_
場合によっては、コンパイラまたはOpenMPランタイムがタスクキューを完全にバイパスして、タスクをシリアルに実行することさえあります。
_Thread 0: --< single: foo(); bar() >*---
Thread 1: ------------------------->*---
Thread 2: ------------------------->*---
_
リージョンのコード内にタスクスケジューリングポイントが存在しない場合、OpenMPランタイムは、適切と判断したときにタスクを開始する場合があります。たとえば、parallel
リージョンの最後のバリアに到達するまで、すべてのタスクが延期される可能性があります。