分散範囲に対するループ (for-drange)
メモ
分散範囲 (drange
) に対する for
ループの使用は、(spmd
ステートメントや通信ジョブなどの内側で) 対話型分散配列の分散次元に明示的にインデックスを付けることを目的としています。並列 for
ループを含む大部分のアプリケーションでは、まず parfor
ループを使用してみてください。並列 for ループ (parfor)を参照してください。
for ループの並列化
場合によっては、既に実行可能な粒度の粗いアプリケーション、つまりプログラムを起動および停止するのに必要な通信時間よりも実行時間の方が著しく長いアプリケーションが存在することがあります。ジョブおよびタスクの定義に伴うオーバーヘッドの回避が望ましい場合は、使いやすい spmd
を利用できます。既存のプログラムでは独立した全データセットの処理に数時間または数日を要する場合でも、独立した計算をクラスターに分散することで、その時間を短縮できます。
たとえば、次の逐次処理コードがあるとします。
results = zeros(1, numDataSets); for i = 1:numDataSets load(['\\central\myData\dataSet' int2str(i) '.mat']) results(i) = processDataSet(i); end plot(1:numDataSets, results); save \\central\myResults\today.mat results
以下の変更により、このコードは spmd
で対話的に、または通信ジョブで、並列に動作します。
results = zeros(1, numDataSets, codistributor()); for i = drange(1:numDataSets) load(['\\central\myData\dataSet' int2str(i) '.mat']) results(i) = processDataSet(i); end res = gather(results, 1); if spmdIndex == 1 plot(1:numDataSets, res); print -dtiff -r300 fig.tiff; save \\central\myResults\today.mat res end
for drange
ループ内で results
にインデックスを付けるには、for
反復の長さと対話型分散配列 results
の長さが一致していなければなりません。こうすると、ワーカー間での通信が不要になります。results
が、元のコードを並列実行するときと同様、単に複製された配列である場合、各ワーカーは results
の自己担当部分に値を代入して results
の残りの部分を 0 のままに残します。最終的に results
はバリアントとなり、明示的に spmdSend
および spmdReceive
または spmdCat
を呼び出さない限り、結果の全体を 1 つ (またはすべて) のワーカーにまとめ直すことはできなくなります。
関数 load
を使用する場合は、必要に応じてすべてのワーカーがデータ ファイルにアクセスできる点に注意しなければなりません。ベスト プラクティスは、共有ファイル システムでファイルへの明示的なパスを使用することです。
これに対応して、関数 save
を使用する際は、一度にただ 1 つのワーカーで (共有ファイル システム上の) 特定ファイルに保存を行うよう注意しなければなりません。このため、コードを if spmdIndex == 1
内にラッピングすることを推奨します。
results
は複数のワーカーに分散されるため、この例では gather
を使用してデータをワーカー 1 に集約しています。
ワーカーは可視的な図をプロットできないため、関数 print
でプロットの表示可能なファイルを作成します。
for-drange ループでの対話型分散配列
通信ジョブで分散範囲に対して for
ループを実行すると、各ワーカーはループの担当部分を実行して、すべてのワーカーが同時に稼動します。このため、for-drange ループの実行中はワーカー間の通信が許可されません。ワーカーは対話型分散配列のうち自己担当の分割のみにアクセスできます。ワーカーが別のワーカーの対話型分散配列の部分にアクセスする必要があるループでの計算では、エラーが発生します。
この特性の説明として、以下の例を試してください。一方の for ループは機能しますが、他方の for ループは機能しません。
spmd
を使用して、4 つのワーカーに分散される 2 つの対話型分散配列を作成します。1 つは単位行列に、もう 1 つはゼロ行列に設定します。
D = eye(8, 8, codistributor()) E = zeros(8, 8, codistributor())
既定では、これらの配列は列単位で分散されます。つまり、4 つのワーカーそれぞれには各配列の 2 つの列が格納されます。これらの配列を for-drange
ループで使用する場合、すべての計算は各ワーカー内で完結しなければなりません。すなわち、各ワーカー内でワーカーに含まれる配列の 2 つの列に制限されている計算のみを実行できます。
たとえば、配列 E
の各列を配列 D
の対応する列の倍数に設定するとします。
for j = drange(1:size(D,2)); E(:,j) = j*D(:,j); end
このステートメントでは、E
の j
番目の列を D
の j
番目の列の j
倍に設定しています。結果として、D
は主対角要素が 1
の単位行列であるのに対し、E
には 1
、2
、3
と続くシーケンスが主対角要素に設定されます。
この演算が機能するのは、各ワーカーが 8 列中の 2 列を独立かつ同時に扱うため、計算の実行に必要な E
の列と D
の列すべてにアクセスできるからです。
これに対し、E
の列の値を D
の別の列に合わせて設定するとします。
for j = drange(1:size(D,2)); E(:,j) = j*D(:,j+1); end
このメソッドは失敗します。j
が 2 のときは、D
の 3 番目の列を使用して E
の 2 番目の列の設定を試みることになるからです。これらの列は異なるワーカーに格納されているため、ワーカー間の通信が許可されないことを示すエラーが表示されます。
制限
対話型分散配列に対して for-drange
を使用するには、以下の条件が成立していなければなりません。
対話型分散配列で (
2dbc
ではなく) 1 次元の分散スキームを使用している。分散が既定の分割スキームに準拠している。
for-drange
ループでインデックス付けされる変数に分散次元用の配列添字がある。その他すべての添字は自由に選択できる (また、各次元の範囲全体に対する
for
ループから取得できる)。
配列の全要素に対してループ処理を行うには、分散の次元に対しては for-drange
を、それ以外の全次元に対しては通常の for
ループを使用します。以下の例は、4 つのワーカーからなる並列プールで実行される spmd
ステートメント内で実行されます。
spmd PP = zeros(6,8,12,"codistributed"); RR = rand(6,8,12,codistributor()) % Default distribution: % by third dimension, evenly across 4 workers. for ii = 1:6 for jj = 1:8 for kk = drange(1:12) PP(ii,jj,kk) = RR(ii,jj,kk) + spmdIndex; end end end end
配列の内容を表示するには、次のように入力します。
PP