Main Content

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 ループの反復を並列プール内のワーカーに分割して実行します。

Diagram showing a MATLAB client using a parfor-loop to divide work between three parallel workers.

この例では、行列のスペクトル半径を計算し、for ループを parfor ループに変換します。高速化の結果と、並列プール内のワーカーが送受信するデータ量を測定する方法を調べます。

  1. MATLAB エディターで、次の for ループを入力します。計算時間を測定するために、tictoc を追加します。

    tic
    n = 200;
    A = 500;
    a = zeros(1,n);
    for i = 1:n
        a(i) = max(abs(eig(rand(A))));
    end
    toc
  2. スクリプトを実行し、経過時間を確認します。

    Elapsed time is 31.935373 seconds.

  3. スクリプト内の for ループを parfor ループに置き換えます。並列プール内のワーカーが送受信するデータ量を測定するために、ticBytestocBytes を追加します。

    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. 新しいスクリプトを 4 つのワーカーで実行し、もう一度実行します。最初の実行は、2 回目の実行よりも遅くなります。これは、並列プールが起動して、コードをワーカーで使用可能にするのに時間がかかるためです。2 回目の実行でのデータ転送量と経過時間を確認します。

    既定では、MATLAB によって、ローカル マシン上に複数のワーカーからなる並列プールが自動的に開かれます。

    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. 
    4 つのワーカーでの parfor の実行は、対応する for ループの計算よりも約 3 倍高速です。高速化は、4 つのワーカーでの理想的な高速化の係数 4 よりも小さくなっています。これは、データをクライアントとワーカー間で転送するために必要な時間を含む、並列オーバーヘッドによるものです。ticBytestocBytes の結果を使用して、データ転送量を調べます。データ転送に必要な時間はデータのサイズに比例すると仮定します。この概算により、データ転送に必要な時間の指標が得られ、並列オーバーヘッドを他の parfor ループの反復と比較できるようになります。この例では、データ転送量と並列オーバーヘッドが、次の例と比較して小さくなっています。

現在の例では並列オーバーヘッドが小さく、parfor ループへの変換が有利に働いています。この例を、次の例 (並列オーバーヘッドの高い parfor の例を参照) にある単純なループ反復と比較します。

計算負荷の高いタスクを含む parfor ループの他の例は、入れ子にされた parfor ループおよび for ループ、およびその他の parfor の要件を参照してください。

並列オーバーヘッドの高い parfor の例

この例では、単純な正弦波を作るループを作成します。for ループを parfor ループに置き換えても、計算は速く "なりません"。このループには多くの反復がなく、実行に長くはかからないため、実行速度の上昇は確認されません。この例では並列オーバーヘッドが高く、parfor ループへの変換が有利に働きません。

  1. 正弦波を作るループを作成します。tictoc を使用して経過時間を測定します。

    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.
  2. for ループを parfor ループに置き換えます。並列プール内のワーカーが送受信するデータ量を測定するために、ticBytestocBytes を追加します。

    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

  3. 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 ループへの変換を参照してください。

参考

| |

関連するトピック