入れ子にされた parfor
ループおよび for
ループ、およびその他の parfor
の要件
入れ子にされた parfor
ループ
ある parfor
ループを他の parfor
ループ内で使用することはできません。たとえば、次の parfor
ループの入れ子は許可されません。
parfor i = 1:10 parfor j = 1:5 ... end end
ヒント
parfor
を他の parfor
ループ内で直接入れ子にすることはできません。parfor
ループは、parfor
ループを含む関数を呼び出すことができますが、並列処理の追加にはなりません。
MATLAB® エディターのコード アナライザーは、parfor
ループ内での別の parfor
の使用にフラグを設定します。
並列化は 1 つのレベルのみで実行可能なため、parfor
ループを入れ子にすることはできません。したがって、並列実行するループを選択し、他のループは for
ループに変換します。
入れ子にされたループを扱うときには、以下のパフォーマンスの問題を考慮してください。
並列処理ではオーバーヘッドが発生します。通常は、外側のループを並列で実行してください。オーバーヘッドの発生が 1 回だけになるためです。内側のループを並列実行すると、複数回実行される
parfor
の各々においてオーバーヘッドが発生します。並列オーバーヘッドの測定方法の例は、入れ子にされた for ループから parfor ループへの変換を参照してください。反復の数がワーカー数を超えていることを確認してください。そうでない場合、使用可能なワーカーの一部は使用されません。
parfor
ループの反復回数のバランスをとるようにします。parfor
は、ある程度の負荷の不均衡を補正しようとします。
ヒント
並列オーバーヘッドを減らすため、必ず最も外側のループを並列実行してください。
また、parfor
を使用する関数を使い、その関数を parfor
ループ内に組み込むこともできます。並列化は外側のレベルのみで行われます。以下の例では、外側の parfor
ループの内部で関数 MyFun.m
を呼び出します。MyFun.m
に組み込まれた内側の parfor
ループは、並列ではなく逐次的に実行されます。
parfor i = 1:10 MyFun(i) end function MyFun(i) parfor j = 1:5 ... end end
ヒント
入れ子にされた parfor
ループには、通常、計算上の利点はありません。
入れ子にされた for
ループから parfor
ループへの変換
入れ子にされたループの典型的な用法は、1 つのループの変数を使用して 1 つの次元にインデックスを付け、入れ子にされたループの変数を使用して別の次元にインデックスを付けて、配列をステップ実行することです。基本的な形式は次のようになります。
X = zeros(n,m); for a = 1:n for b = 1:m X(a,b) = fun(a,b) end end
次のコードは単純な例を示しています。tic
と toc
を使用して、必要な計算時間を測定します。
A = 100; tic for i = 1:100 for j = 1:100 a(i,j) = max(abs(eig(rand(A)))); end end toc
Elapsed time is 49.376732 seconds.
入れ子にされたいずれかのループを並列化することはできますが、両方を並列実行することはできません。その理由は、並列プールのワーカーからさらに並列プールを起動したり、並列プールにアクセスしたりすることはできないためです。
i
でカウントされるループが parfor
ループに変換された場合、プール内の各ワーカーはループ カウンター j
を使用して入れ子にされたループを実行します。j
ループ自体を各ワーカーで parfor
として実行することはできません。
並列処理ではオーバーヘッドが発生するため、内側または外側の for
ループのいずれを parfor
ループに変換するかを慎重に選択しなければなりません。以下の例では、並列オーバーヘッドの測定方法を説明します。
まず、"外側" の for
ループのみを parfor
ループに変換します。tic
と toc
を使用して、必要な計算時間を測定します。ticBytes
と tocBytes
を使用して、並列プール内のワーカーが送受信するデータ量を測定します。
新しいコードを実行し、もう一度実行します。最初の実行は以降の実行よりも遅くなります。これは、並列プールが起動して、コードをワーカーで使用可能にするのに時間がかかるためです。
A = 100; tic ticBytes(gcp); parfor i = 1:100 for j = 1:100 a(i,j) = max(abs(eig(rand(A)))); end end tocBytes(gcp) toc
BytesSentToWorkers BytesReceivedFromWorkers __________________ ________________________ 1 32984 24512 2 33784 25312 3 33784 25312 4 34584 26112 Total 1.3514e+05 1.0125e+05 Elapsed time is 14.130674 seconds.
次に、"内側" のループのみを parfor
ループに変換します。前回と同様に、必要な時間とデータ転送量を測定します。
A = 100; tic ticBytes(gcp); for i = 1:100 parfor j = 1:100 a(i,j) = max(abs(eig(rand(A)))); end end tocBytes(gcp) toc
BytesSentToWorkers BytesReceivedFromWorkers __________________ ________________________ 1 1.3496e+06 5.487e+05 2 1.3496e+06 5.4858e+05 3 1.3677e+06 5.6034e+05 4 1.3476e+06 5.4717e+05 Total 5.4144e+06 2.2048e+06 Elapsed time is 48.631737 seconds.
"内側" のループを parfor
ループに変換した場合、時間とデータ転送量がどちらも並列の外側のループよりはるかに大きくなります。この場合、経過時間は入れ子にされた for
ループの例とほぼ同じです。速度の上昇は、外側のループの並列実行よりも小さくなります。これは、データ転送量がより多く、このために並列オーバーヘッドが大きくなるからです。したがって、"内側" のループを並列実行しても、for
ループの逐次実行と比較して計算上有利になることはありません。
並列オーバーヘッドを減らして計算を高速化する場合は、外側のループを並列実行してください。
代わりに "内側" のループを変換すると、外側のループの反復ごとに別々の parfor
ループが開始されます。つまり、内側のループを変換すると 100 回の parfor
ループが作成されます。複数回実行される parfor
の各々においてオーバーヘッドが発生します。並列オーバーヘッドを減らす場合は、代わりに外側のループを並列実行しなければなりません。オーバーヘッドの発生が 1 回だけになるためです。
ヒント
コードを高速化する場合は、必ず外側のループを並列実行してください。これにより並列オーバーヘッドが減少します。
入れ子にされた for
ループ: 要件と制限
入れ子にされた for
ループを parfor
ループに変換する場合、ループ変数が適切に分類されていることを確認しなければなりません。parfor ループ内の変数のトラブルシューティングを参照してください。必須というラベルの付いたガイドラインと制限にコードが従っていない場合は、エラーが発生します。MATLAB はコードの読み取り時こうしたエラーの一部を検出します。これらのエラーには、必須 (静的) のラベルが付けられます。
必須 (静的): parfor ループ内に入れ子にされた for ループの範囲を、定数またはブロードキャスト変数で定義しなければなりません。 |
次の例では、左側のコードは機能しません。for
ループの上限を関数呼び出しで定義しているためです。右側のコードは、parfor
ループの外側でブロードキャスト変数または定数変数をまず定義することにより、回避策を提供しています。
無効 | 有効 |
---|---|
A = zeros(100, 200); parfor i = 1:size(A, 1) for j = 1:size(A, 2) A(i, j) = i + j; end end | A = zeros(100, 200); n = size(A, 2); parfor i = 1:size(A,1) for j = 1:n A(i, j) = i + j; end end |
必須 (静的): 入れ子にされた for ループのインデックス変数は、その for ステートメント以外で明示的に代入してはなりません。 |
この制限に従うことは必須です。入れ子にされた for
ループの変数を、for
ステートメントによってではなく、parfor
ループのいずれかの場所で変更した場合、for
ループの変数によってインデックス付けされる領域を確実に各ワーカーで使用できるとは限りません。
左側のコードは有効でありません。ループ本体の中で、入れ子にされた for
ループの変数 j
の値を変更しようとしているためです。右側のコードは、入れ子にされた for
ループの変数を一時変数 t
に代入してから t
を更新することにより、回避策を提供しています。
無効 | 有効 |
---|---|
A = zeros(10); parfor i = 1:10 for j = 1:10 A(i, j) = 1; j = j+1; end end | A = zeros(10); parfor i = 1:10 for j = 1:10 A(i, j) = 1; t = j; t = t + 1; end end |
必須 (静的): 入れ子にされた for ループの変数にインデックスを付けたり、添字を使用したりすることはできません。 |
この制限に従うことは必須です。入れ子にされた for
ループの変数にインデックスを付けた場合、反復の独立は保証されません。
左側の例は無効です。入れ子にされた for
ループの変数 j
にインデックスを付けようとしているためです。右側の例では、このインデックス付けが削除されています。
無効 | 有効 |
---|---|
A = zeros(10); parfor i = 1:10 for j = 1:10 j(1); end end | A = zeros(10); parfor i = 1:10 for j = 1:10 j; end end |
必須 (静的): スライス化された配列のインデックス付けに入れ子にされた for ループの変数を使用する場合は、変数を式の一部でなくシンプルな形式で使用しなければなりません。 |
たとえば、次の左側のコードは機能しませんが、右側のコードは機能します。
無効 | 有効 |
---|---|
A = zeros(4, 11); parfor i = 1:4 for j = 1:10 A(i, j + 1) = i + j; end end | A = zeros(4, 11); parfor i = 1:4 for j = 2:11 A(i, j) = i + j - 1; end end |
必須 (静的): 入れ子にされた for ループを使用して、スライス化された配列にインデックスを付ける場合、parfor ループ内の他の場所でその配列を使用することはできません。 |
次の例では、左側のコードは機能しません。A
がスライス化され、入れ子にされた for
ループ内でインデックスが付けられているためです。右側のコードは機能します。入れ子にされたループの外側で v
が A
に代入されているためです。
無効 | 有効 |
---|---|
A = zeros(4, 10); parfor i = 1:4 for j = 1:10 A(i, j) = i + j; end disp(A(i, j)) end | A = zeros(4, 10); parfor i = 1:4 v = zeros(1, 10); for j = 1:10 v(j) = i + j; end disp(v(j)) A(i, :) = v; end |
parfor
ループの制限
入れ子関数
parfor
ループの本体では、入れ子関数を参照できません。ただし、関数ハンドルによって入れ子関数を呼び出すことは可能です。次の例を試してください。parfor
ループ内では A(idx) = nfcn(idx)
が機能しないことに注意してください。parfor
ループ本体で fcn
ハンドルを呼び出すには feval
を使用しなければなりません。
function A = pfeg function out = nfcn(in) out = 1 + in; end fcn = @nfcn; parfor idx = 1:10 A(idx) = feval(fcn, idx); end end
>> pfeg Starting parallel pool (parpool) using the 'Processes' profile ... connected to 4 workers. ans = 2 3 4 5 6 7 8 9 10 11
ヒント
入れ子関数を参照する関数ハンドルを parfor
ループ内で使用する場合、スコープ外の変数の値はワーカー間で同期されません。
入れ子にされた parfor
ループ
parfor
ループの本体に parfor
ループを含めることはできません。詳細については、入れ子にされた parfor ループを参照してください。
入れ子にされた spmd
ステートメント
parfor
ループの本体に spmd
ステートメントを含めることはできず、spmd
ステートメントに parfor
ループを含めることはできません。その理由は、ワーカーからさらに並列プールを起動したり、並列プールにアクセスしたりすることができないためです。
break
および return
ステートメント
parfor
ループの本体に、break
または return
ステートメントを含めることはできません。代わりとして、parfeval
または parfevalOnAll
を検討してください。それらに対し cancel
を使用できるためです。
グローバル変数および永続変数
parfor
ループの本体に、global
または persistent
の変数宣言を含めることはできません。その理由は、これらの変数がワーカー間で同期されないためです。関数内では global
変数または persistent
変数を使用できますが、それらの値はそれらの変数を作成するワーカーでのみ可視になります。global
変数の代わりに、関数の引数を使用して値を共有することをお勧めします。
変数の要件の詳細については、parfor ループ内の変数のトラブルシューティングを参照してください。
スクリプト
スクリプトで変数が導入される場合、このスクリプトを parfor
ループまたは spmd
ステートメントの内部から呼び出すことはできません。理由は、このスクリプトが透過性の違反の原因となるからです。詳細については、parfor ループまたは spmd ステートメント内での透過性の確保を参照してください。
無名関数
parfor
ループの本体内で無名関数を定義できます。ただし、無名関数内でのスライス化された出力変数はサポートされていません。次の例に示すように、スライス化された変数の代わりに一時変数を使用することで、これに対処できます。
x = 1:10; parfor i=1:10 temp = x(i); anonymousFunction = @() 2*temp; x(i) = anonymousFunction() + i; end disp(x);
スライス化された変数の詳細については、スライス化された変数を参照してください。
関数 inputname
parfor
ループ内で inputname
を使用して、引数番号に対応するワークスペース変数名を返すことはサポートされていません。その理由は、parfor
ワーカーが MATLAB デスクトップのワークスペースにアクセスできないためです。これに対処するには、次の例に示すように、parfor
の前に inputname
を呼び出します。
a = 'a'; myFunction(a) function X = myFunction(a) name = inputname(1); parfor i=1:2 X(i).(name) = i; end end
関数 load
parfor
ループ内で、出力構造体に代入しない load
の構文はサポートされていません。parfor
内では、必ず load
の出力を構造体に代入します。
関数 nargin
または関数 nargout
parfor
ループ内での次の使用はサポートされていません。
関数の引数を指定せずに
nargin
またはnargout
を使用する現在実行している関数の呼び出し内にある入力引数または出力引数の数を検証する目的で
narginchk
またはnargoutchk
を使用する
その理由は、ワーカーが MATLAB デスクトップのワークスペースにアクセスできないためです。これに対処するには、次の例に示すように、これらの関数を parfor
の前に呼び出します。
myFunction('a','b') function X = myFunction(a,b) nin = nargin; parfor i=1:2 X(i) = i*nin; end end
P コード スクリプト
parfor
ループ内から P コード スクリプト ファイルを呼び出すことができますが、P コード スクリプトに parfor
ループを含めることはできません。これに対処するには、P コード スクリプトの代わりに P コード関数を使用します。
参考
parfor
| parfeval
| parfevalOnAll