OpenMPのアトミックとクリティカルの違いは何ですか?
私がすることができます
#pragma omp atomic
g_qCount++;
しかし、これはと同じではありません
#pragma omp critical
g_qCount++;
?
OpenMPのアトミックとクリティカルの違いは何ですか?
私がすることができます
#pragma omp atomic
g_qCount++;
しかし、これはと同じではありません
#pragma omp critical
g_qCount++;
?
g_qCountへの影響は同じですが、実行される内容が異なります。
OpenMPのクリティカルセクションは完全に一般的であり、任意のコードブロックを囲むことができます。ただし、スレッドがクリティカルセクションに出入りするたびに(シリアル化の固有のコストに加えて)かなりのオーバーヘッドが発生することで、その一般性の代償を払うことになります。
(さらに、OpenMPでは、すべての名前のないクリティカルセクションは同一であると見なされます(必要に応じて、すべての名前のないクリティカルセクションに対して1つのロックのみがあります)。そのため、上記のように1つのスレッドが1つの[名前のない]クリティカルセクションにある場合、スレッドは[名前のない]クリティカルセクション。ご想像のとおり、名前の付いたクリティカルセクションを使用してこれを回避できます)。
アトミック操作のオーバーヘッドははるかに低くなります。利用可能な場合は、(たとえば)アトミックインクリメント操作を提供するハードウェアを利用します。その場合、コード行の開始/終了にロック/ロック解除は必要ありません。ハードウェアが干渉できないと指示するアトミックインクリメントを実行するだけです。
利点は、オーバーヘッドがはるかに低く、アトミック操作中の1つのスレッドが、発生しようとしている(異なる)アトミック操作をブロックしないことです。欠点は、アトミックがサポートする操作の制限されたセットです。
もちろん、どちらの場合も、シリアル化のコストが発生します。
OpenMPでは、名前のないクリティカルセクションはすべて相互に排他的です。
クリティカルとアトミックの最も重要な違いは、アトミックは単一の割り当てのみを保護でき、特定の演算子で使用できることです。
クリティカルセクション:
「name」タグを適切に使用して、ブロックのグループをシリアル化するように拡張できます。
もっとゆっくり!
不可分操作:
はるかに高速です!
特定の操作のシリアル化のみを保証します。
最速の方法は重要でもアトミックでもありません。およそ、クリティカルセクションを使用した加算は単純な加算よりも200倍高価であり、原子加算は単純な加算よりも25倍高価です。
最速のオプション(常に適用できるとは限りません)は、各スレッドに独自のカウンターを与え、合計が必要なときにreduce操作を行うことです。
の制限atomic
は重要です。それらはOpenMP仕様で詳細に説明する必要があります。これが変更されなくても驚かないので、MSDNは簡単なチートシートを提供しています。(Visual Studio 2012には2002年3月からのOpenMP実装があります。)MSDNを引用するには:
式ステートメントは、次のいずれかの形式である必要があります。
x
binop =expr
x++
++x
x--
--x
上記の式では、:
x
はlvalue
スカラー型の式です。expr
はスカラー型の式であり、で指定されたオブジェクトを参照しませんx
。binopはオーバーロードされた演算子ではなく+
、、、、、、、、、、、、または*
のいずれかです。-
/
&
^
|
<<
>>
可能な場合は使用し、そうでない場合はクリティカルセクションに名前atomic
を付けることをお勧めします。それらに名前を付けることは重要です。この方法で頭痛の種をデバッグすることを避けます。
ここですでに素晴らしい説明。ただし、もう少し深く掘り下げることができます。OpenMPのアトミックセクションとクリティカルセクションの概念のコアの違いを理解するには、最初にロックの概念を理解する必要があります。ロックを使用する必要がある理由を確認しましょう。
並列プログラムが複数のスレッドで実行されています。これらのスレッド間で同期を実行した場合にのみ、決定論的な結果が発生します。もちろん、スレッド間の同期は必ずしも必要ではありません。同期が必要な場合を指します。
マルチスレッドプログラムでスレッドを同期するために、 lockを使用します。一度に1つのスレッドだけでアクセスを制限する必要がある場合は、ロックが機能します。ロックの概念の実装は、プロセッサごとに異なる場合があります。アルゴリズムの観点から、単純なロックがどのように機能するかを調べてみましょう。
1. Define a variable called lock.
2. For each thread:
2.1. Read the lock.
2.2. If lock == 0, lock = 1 and goto 3 // Try to grab the lock
Else goto 2.1 // Wait until the lock is released
3. Do something...
4. lock = 0 // Release the lock
与えられたアルゴリズムは、次のようにハードウェア言語で実装できます。単一のプロセッサを想定し、その中のロックの動作を分析します。この方法では、MIPS、Alpha、ARM、またはPowerのいずれかのプロセッサを想定します。
try: LW R1, lock
BNEZ R1, try
ADDI R1, R1, #1
SW R1, lock
このプログラムは問題ないようですが、そうではありません。上記のコードには前の問題があります。同期。問題を見つけましょう。ロックの初期値をゼロと仮定します。2つのスレッドがこのコードを実行する場合、一方がSW R1に到達し、もう一方がロック変数を読み取る前にロックする可能性があります。したがって、どちらもロックは無料であると考えています。この問題を解決するために、単純なLWおよびSWではなく、別の命令が提供されています。これは、リードモディファイライト命令と呼ばれます。これは複雑な命令(サブ命令で構成)であり、ロック取得手順が1つだけで実行されることを保証します。一度にスレッド。単純な読み取りおよび書き込み命令と比較したRead-Modify-Writeの違いは、ロードと保存の方法が異なることです。LL(Load Linked)を使用してロック変数をロードし、 SC(Store Conditional)を使用してロック変数に書き込みます。追加のリンクレジスタは、ロック取得の手順が単一のスレッドによって実行されることを保証するために使用されます。アルゴリズムを以下に示します。
1. Define a variable called lock.
2. For each thread:
2.1. Read the lock and put the address of lock variable inside the Link Register.
2.2. If (lock == 0) and (&lock == Link Register), lock = 1 and reset the Link Register then goto 3 // Try to grab the lock
Else goto 2.1 // Wait until the lock is released
3. Do something...
4. lock = 0 // Release the lock
リンクレジスタがリセットされたときに、別のスレッドがロックが解放されていると想定した場合、インクリメントされた値をロックに再度書き込むことはできません。したがって、ロック変数へのアクセスの同時実行性が取得されます。
クリティカルとアトミックの主な違いは、次のような考え方にあります。
実際の変数(操作を実行している)をロック変数として使用できるのに、なぜロック(新しい変数)を使用するのですか?
ロックに新しい変数を使用するとクリティカルセクションになり、実際の変数をロックとして使用するとアトミックコンセプトになります。クリティカルセクションは、実際の変数に対して多くの計算(複数行)を実行する場合に役立ちます。これは、これらの計算の結果が実際の変数に書き込まれない場合は、手順全体を繰り返して結果を計算する必要があるためです。これは、高度に計算された領域に入る前にロックが解放されるのを待つ場合と比較して、パフォーマンスの低下につながる可能性があります。したがって、単一の計算(x ++、x-、++ x、-xなど)を実行して使用する場合は常に、アトミックディレクティブを使用することをお勧めします。より計算が複雑な領域が集中セクションによって実行されている場合の重要なディレクティブ。
アトミックは単一ステートメントのクリティカルセクションです。つまり、1つのステートメントの実行をロックします。
クリティカルセクションは、コードブロックのロックです
優れたコンパイラは、最初のコードと同じ方法で2番目のコードを変換します