入れ子にされた 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