parfor
を使用するタイミングの決定
MATLAB の parfor ループ
MATLAB® の parfor
ループは、ループ本体内にある一連のステートメントを並列で実行します。MATLAB クライアントは parfor
コマンドを発行し、MATLAB ワーカーと連係して "並列プール" 内のワーカー上でループ反復を実行します。クライアントは parfor
の処理に必要なデータを複数のワーカーに送信し、そこで計算の大部分が実行されます。結果はクライアントに送り返されて整理されます。
いくつかの MATLAB ワーカーが同一のループで同時に計算できるため、parfor
ループは類似の for
ループよりもはるかにパフォーマンスが向上します。
parfor
ループの本体の各実行は "反復" です。MATLAB ワーカーは特定の順序を定めず、相互に無関係なものとして反復を評価します。各反復は独立しているため、反復が何らかの形で同期される保証はなく、またその必要もありません。ワーカー数がループ反復数と同じである場合、各ワーカーはループの 1 つの反復を実行します。ワーカーより多くの反復がある場合は、一部のワーカーが複数のループ反復を実行します。この場合、通信時間を短縮するために、ワーカーが一度に複数の反復を受け取る可能性があります。
parfor を使用するタイミングの決定
低速の for
ループがある場合は、parfor
ループが有用なことがあります。以下の場合は parfor
の使用を検討してください。
実行に長時間かかるループ反復がいくつかある場合。この場合、ワーカーは複数の長い反復を同時に実行できます。反復の数がワーカー数を超えていることを確認してください。そうでない場合、使用可能なワーカーの一部は使用されません。
モンテ カルロ シミュレーションやパラメーター スイープなど、単純な計算のループ反復が多数ある場合。
parfor
はループ反復をグループに分割し、各ワーカーによって反復の総数の一部が実行されます。複数の GPU があり、計算で GPU 対応関数を使用している場合。
parfor
ループで複数の GPU を使用する方法の詳細については、複数の GPU での MATLAB 関数の実行を参照してください。
以下の場合は、parfor
ループが有用でない可能性があります。
ベクトル化により
for
ループが取り除かれたコードの場合。一般に、コードをより高速で実行させるには、まずコードのベクトル化を試します。この方法の詳細については、ベクトル化を参照してください。コードのベクトル化では、多くの基礎 MATLAB ライブラリのマルチスレッド特性によって提供される、組み込み並列処理を活用できます。しかし、ベクトル化したコードがあり、"ローカル" ワーカーにしかアクセスできない場合、parfor
ループの実行はfor
ループより遅くなることがあります。parfor
を使用できるようベクトル化したコードを元に戻すことはしないでください。通常、この解決法はうまく機能しません。実行時間の短いループ反復の場合。この場合、並列オーバーヘッドが計算の大半を占めます。
すべて同じ GPU を使用するループ反復の場合。GPU には、計算を並列実行できる多数のマイクロプロセッサが含まれており、
parfor
ループを使用して GPU 計算をさらに並列化しようとしても、コードが高速化される可能性はほとんどありません。
ループでの反復が他の反復の結果によって変わる場合、parfor
ループは使用できません。個々の反復は他のすべての反復から独立していなければなりません。独立ループの処理についてのヘルプは、parfor ループ反復が独立していることの確認を参照してください。この規則の例外は、リダクション変数の使用によりループ内に値を累積する場合です。
parfor
を使用するタイミングの決定に際しては、並列オーバーヘッドについて検討します。並列オーバーヘッドには、クライアントとワーカー間での通信、調整、およびデータ転送 (データの送受信) に必要な時間が含まれます。反復が高速で評価される場合、このオーバーヘッドは合計時間の大きな部分を占める可能性があります。2 種類のループ反復について考えます。
計算負荷の高いタスクを伴う
for
ループ。これらのループは通常、parfor
ループへの変換の優れた候補です。計算に必要な時間が、データ転送に必要な時間よりずっと長いためです。単純な計算タスクを伴う
for
ループ。これらのループでは通常、parfor
ループへの変換が有利に働きません。計算に必要な時間と比較して、データ転送に必要な時間がかなりの長さとなるためです。
並列オーバーヘッドの小さい parfor
の例
この例では、for
ループ内の計算負荷の高いタスクから始めます。for
ループが低速なため、代わりに parfor
ループを使用して計算を高速化します。parfor
は、for
ループの反復を並列プール内のワーカーに分割して実行します。
この例では、行列のスペクトル半径を計算し、for
ループを parfor
ループに変換します。高速化の結果と、並列プール内のワーカーが送受信するデータ量を測定する方法を調べます。
MATLAB エディターで、次の
for
ループを入力します。計算時間を測定するために、tic
とtoc
を追加します。tic n = 200; A = 500; a = zeros(1,n); for i = 1:n a(i) = max(abs(eig(rand(A)))); end toc
スクリプトを実行し、経過時間を確認します。
Elapsed time is 31.935373 seconds.
スクリプト内の
for
ループをparfor
ループに置き換えます。並列プール内のワーカーが送受信するデータ量を測定するために、ticBytes
とtocBytes
を追加します。tic ticBytes(gcp); n = 200; A = 500; a = zeros(1,n); parfor i = 1:n a(i) = max(abs(eig(rand(A)))); end tocBytes(gcp) toc
新しいスクリプトを 4 つのワーカーで実行し、もう一度実行します。最初の実行は、2 回目の実行よりも遅くなります。これは、並列プールが起動して、コードをワーカーで使用可能にするのに時間がかかるためです。2 回目の実行でのデータ転送量と経過時間を確認します。
既定では、MATLAB によって、ローカル マシン上に複数のワーカーからなる並列プールが自動的に開かれます。
4 つのワーカーでのStarting parallel pool (parpool) using the 'Processes' profile ... connected to 4 workers. ... BytesSentToWorkers BytesReceivedFromWorkers __________________ ________________________ 1 15340 7024 2 13328 5712 3 13328 5704 4 13328 5728 Total 55324 24168 Elapsed time is 10.760068 seconds.
parfor
の実行は、対応するfor
ループの計算よりも約 3 倍高速です。高速化は、4 つのワーカーでの理想的な高速化の係数 4 よりも小さくなっています。これは、データをクライアントとワーカー間で転送するために必要な時間を含む、並列オーバーヘッドによるものです。ticBytes
とtocBytes
の結果を使用して、データ転送量を調べます。データ転送に必要な時間はデータのサイズに比例すると仮定します。この概算により、データ転送に必要な時間の指標が得られ、並列オーバーヘッドを他のparfor
ループの反復と比較できるようになります。この例では、データ転送量と並列オーバーヘッドが、次の例と比較して小さくなっています。
現在の例では並列オーバーヘッドが小さく、parfor
ループへの変換が有利に働いています。この例を、次の例 (並列オーバーヘッドの高い parfor の例を参照) にある単純なループ反復と比較します。
計算負荷の高いタスクを含む parfor
ループの他の例は、入れ子にされた parfor ループおよび for ループ、およびその他の parfor の要件を参照してください。
並列オーバーヘッドの高い parfor
の例
この例では、単純な正弦波を作るループを作成します。for
ループを parfor
ループに置き換えても、計算は速く "なりません"。このループには多くの反復がなく、実行に長くはかからないため、実行速度の上昇は確認されません。この例では並列オーバーヘッドが高く、parfor
ループへの変換が有利に働きません。
正弦波を作るループを作成します。
tic
とtoc
を使用して経過時間を測定します。tic n = 1024; A = zeros(n); for i = 1:n A(i,:) = (1:n) .* sin(i*2*pi/1024); end toc
Elapsed time is 0.012501 seconds.
for
ループをparfor
ループに置き換えます。並列プール内のワーカーが送受信するデータ量を測定するために、ticBytes
とtocBytes
を追加します。tic ticBytes(gcp); n = 1024; A = zeros(n); parfor (i = 1:n) A(i,:) = (1:n) .* sin(i*2*pi/1024); end tocBytes(gcp) toc
4 つのワーカーでスクリプトを実行してから、コードをもう一度実行します。最初の実行は、2 回目の実行よりも遅くなります。これは、並列プールが起動して、コードをワーカーで使用可能にするのに時間がかかるためです。2 回目の実行でのデータ転送量と経過時間を確認します。
逐次的なBytesSentToWorkers BytesReceivedFromWorkers __________________ ________________________ 1 13176 2.0615e+06 2 15188 2.0874e+06 3 13176 2.4056e+06 4 13176 1.8567e+06 Total 54716 8.4112e+06 Elapsed time is 0.743855 seconds.
for
ループでは、経過時間が 4 つのワーカー上でのparfor
ループよりはるかに短くなっています。この例では、for
ループをparfor
ループに変換することが有利に働きません。その理由は、前述の例 (並列オーバーヘッドの小さい parfor の例を参照) よりもデータ転送量がはるかに多いためです。現在の例では、並列オーバーヘッドが計算時間の大半を占めています。そのため、正弦波の反復ではparfor
ループへの変換が有利に働きません。
この例は、並列オーバーヘッドの高い計算で parfor
ループへの変換が有利に働かない理由を示しています。コードの高速化の詳細については、for ループから parfor ループへの変換を参照してください。