通信ジョブに関する追加メモ
通信ジョブのタスク数
通信ジョブではタスクを 1 つだけ作成しますが、システムによってこのタスクはジョブを実行する各ワーカーにコピーされます。たとえば、通信ジョブが 4 つのワーカーで実行される場合、ジョブの Tasks
プロパティには 4 つのタスク オブジェクトが格納されます。ジョブの Tasks
プロパティ内の最初のタスクは、spmdIndex
が 1
であるワーカーにより実行されるタスクに対応する (以下同様) ため、タスク オブジェクトの ID
プロパティおよびそのタスクを実行したワーカーの spmdIndex
は同じ値になります。したがって、関数 fetchOutputs
により返される結果の順序は、spmdIndex
の値とジョブの Tasks
プロパティ内のタスクの順序に対応します。
デッドロックなどの依存関係エラーの回避
通信ジョブのために特定のワーカーで実行されているコードは、別のワーカーで対応コードが実行されるまでブロックされる場合があるため、通信ジョブではデッドロックが発生する可能性があります。デッドロックが最も発生しやすいのは、ワーカー間でデータを転送する場合や、コードを if
ステートメントの spmdIndex
に依存させている場合です。いくつかの例により、一般的な落とし穴について説明します。
対話型分散配列 D
があり、関数 gather
を使用して配列全体を単一のワーカーのワークスペースにまとめるとします。
if spmdIndex == 1 assembled = gather(D); end
これが失敗する理由は、関数 gather
によって、配列が分散されるすべてのワーカー間で通信が要求されるためです。if
ステートメントで実行が 1 つのワーカーに制限される場合、関数の実行に必要な他のワーカーはこのステートメントを実行していません。代替方法として、次のように gather
自体を使用してデータを単一ワーカーのワークスペースに集めることができます。 assembled = gather(D, 1)
.
もう 1 つの例として、各ワーカーのデータを (spmdIndex
が 1 つ上として定義される) 右側にある次のワーカーに転送する場合を考えます。まず、各ワーカーに対し、左右のワーカーを定義します。
from_worker_left = mod(spmdIndex - 2, spmdSize) + 1; to_worker_right = mod(spmdIndex, spmdSize) + 1;
次に、この輪に沿ってデータを渡すよう試みます。
spmdSend (outdata, to_worker_right); indata = spmdReceive(from_worker_left);
このコードは失敗する可能性がありますが、その理由は、転送するデータのサイズ次第で、受信側のワーカーが関数 spmdReceive
を実行するまで関数 spmdSend
が対応するワーカーでの実行をブロックする場合があるためです。この場合、すべてのワーカーが同時に送信を試みますが、spmdSend
によるブロックのため、どのワーカーも受信しようとしていません。つまり、どのワーカーも spmdSend
ステートメントのステップでブロックされているため、spmdReceive
ステートメントに到達できていません。この問題を回避するには、関数 spmdSendReceive
を使用します。