並列プログラムが遭遇する問題のタイプには、共有メモリーデータの競合と不正なロックが含まれます。
並列処理を導入すると、並列タスクが同じメモリー位置をアクセスしたときに予期しない結果となることがあります。これは、データ競合の問題として知られています。これは、データ競合の問題として知られています。例えば、Primes のサンプルでは、次の行が関数 Tick() を呼び出しています。
if (IsPrime(p)) Tick();
呼び出された関数 Tick() は、グローバル変数 primes をインクリメントします。
void Tick() { primes++; }
次のシナリオを考えてみます。変数 primes は 1 回ではなく、2 回インクリメントされます。
時間 |
スレッド 0 |
スレッド 1 |
---|---|---|
T1 |
関数 Tick() に入る |
|
T2 |
|
関数 Tick() に入る |
T3 |
|
primes の値をロード |
T4 |
primes の値をロード |
|
T5 |
|
ロードした値をインクリメント |
T6 |
|
primes の値をストア |
T7 |
ロードした値をインクリメント |
|
T8 |
primes の値をストア |
|
T9 |
リターン |
|
T10 |
|
リターン |
このプログラムをシリアルプログラムとして実行した場合、問題は発生しません。しかし、複数のスレッドで実行すると、タスクが並列に実行され、primes は安全にインクリメントされない可能性があります。
このような問題は非決定論的であり特定が困難で、一見無作為に発生します。システム上のワークロード、処理されるデータ、コア数、スレッド数など複数の要因によって結果が変化します。
共有メモリー位置へのアクセスを一度に 1 つのタスクに制限するためロックを使用できます。しかし、すべてのロックの実装にはオーバーヘッドが伴います。そのため、ストレージを複製して共有を排除したほうがより効率的です。これは、メモリー位置が再利用される場合であっても、データ値がタスク間でやり取りされなければ可能です。
スレッド (A) は、処理を続行する前に、ほかのスレッド (B) がロックを解放するのを待機しなければならないことがあります。その間、スレッド A を実行するコアは、有用なワークを実行していません。これはロックの競合と呼ばれます。さらに、スレッド B は処理を続行する前にスレッド A が異なるロックを解放するのを待機しなければならないことがあります。このような状況はデッドロックと呼ばれます。
データ競合と同様に、デッドロックもまた非決定論的に発生します。また、システム上のワークロード、処理されるデータ、またはスレッド数など特定の要因が存在する場合にのみ発生することもあります。
インテル® Advisor は、並列処理に関連する多くの問題を検出できます。しかし、インテル® Advisor はプログラムのシリアル実行のみを解析するため、すべての並列処理のエラーを特定することができません。プログラムに並列処理を導入するためインテル® Advisor の解析が完了したら、インテル® Inspector やその他のインテル® ソフトウェア・スイート製品を使用することを強く推奨します。これらのツールとデバッガーを使用すると、通常のテストでは特定できない並列処理の問題を検出し、コアがアイドル状態の時間も特定できます。