Main Content

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

parfor のパフォーマンスの向上

さまざまな方法で parfor ループのパフォーマンスを向上させることができます。これには、ループ内での配列の並列作成、parfor ループのプロファイリング、配列のスライス化、およびクラスター上での実行前におけるローカル ワーカー上でのコードの最適化が含まれます。

配列を作成する場所

parfor ループの前にクライアントで大規模な配列を作成し、ループ内でその配列にアクセスする場合、コードの実行が遅くなることがあります。パフォーマンスを向上させるには、それぞれの MATLAB® ワーカーに独自の配列 (またはその一部) を並列作成するように指示します。各ワーカーに、それらの配列の独自のコピーをループ内に並列作成するよう求めることで、クライアントからワーカーへのデータ転送時間を短縮できます。for ループの前に変数を初期化する通常の手法を変更して、ループ内での不要な繰り返しを回避することを検討してください。ループ内で配列を並列作成するとパフォーマンスが向上することがわかります。

パフォーマンスの向上は、次のような各種要因に左右されます。

  • 配列のサイズ

  • 配列の作成に必要な時間

  • 配列のすべてまたは一部へのワーカーのアクセス

  • 各ワーカーが実行するループ反復数

for ループから parfor ループへの変換を検討するときには、このリストのすべての要因を考慮します。詳細については、for ループから parfor ループへの変換を参照してください。

代替方法として、ループの前にプール ワーカー上で変数を設定する関数 parallel.pool.Constant の使用を検討します。これらの変数はループが終了した後もワーカーに残り、引き続き複数の parfor ループで利用できます。parallel.pool.Constant を使用するとワーカーへのデータ転送が 1 回のみになるため、パフォーマンスが向上する可能性があります。

次の例では、まずビッグ データセット D を作成してから、D にアクセスする parfor ループを実行します。次に、D を使用して parallel.pool.Constant オブジェクトを作成します。これにより、D を各ワーカーにコピーしてデータを再利用できます。tictoc を使用して各ケースの経過時間を測定し、差を確認します。

function constantDemo
    D = rand(1e7, 1);
    tic
    for i = 1:20
        a = 0;
        parfor j = 1:60
            a = a + sum(D);
        end
    end
    toc
    
    tic
    D = parallel.pool.Constant(D);
    for i = 1:20
        b = 0;
        parfor j = 1:60
            b = b + sum(D.Value);
        end
    end
    toc
end
>> constantDemo
Starting parallel pool (parpool) using the 'Processes' profile ... connected to 4 workers.
Elapsed time is 63.839702 seconds.
Elapsed time is 10.194815 seconds.
2 番目のケースではデータを 1 回のみ送信します。parallel.pool.Constant オブジェクトを使用して、parfor ループのパフォーマンスを向上させることができます。

parfor ループのプロファイリング

tictoc を使用して経過時間を測定することにより、parfor ループをプロファイリングできます。また、ticBytestocBytes を使用して、並列プール内のワーカーが送受信するデータ量も測定できます。これは、MATLAB プロファイラーを使用する通常の意味での MATLAB コードのプロファイリングとは異なることに注意してください。パフォーマンス向上のためのコードのプロファイリングを参照してください。

この例では、行列のスペクトル半径を計算し、for ループを parfor ループに変換します。高速化の結果およびデータ転送量を測定します。

  1. MATLAB エディターで、次の for ループを入力します。経過時間を測定するために、tictoc を追加します。ファイルを MyForLoop.m として保存します。

    function a = MyForLoop(A)
        tic
        for i = 1:200
            a(i) = max(abs(eig(rand(A))));
        end
        toc
    end
  2. コードを実行し、経過時間を確認します。

    a = MyForLoop(500);
    Elapsed time is 31.935373 seconds.

  3. MyForLoop.m 内の for ループを parfor ループに置き換えます。並列プール内のワーカーが送受信するデータ量を測定するために、ticBytestocBytes を追加します。ファイルを MyParforLoop.m として保存します。

    ticBytes(gcp);
    parfor i = 1:200
        a(i) = max(abs(eig(rand(A))));
    end
    tocBytes(gcp)

  4. 新しいコードを実行し、もう一度実行します。最初の実行は 2 回目の実行よりも遅くなります。これは、並列プールを起動し、コードをワーカーで使用可能にしなければならないためです。2 回目の実行の経過時間を確認します。

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

    a = MyParforLoop(500);
    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. 
    経過時間は、逐次実行で 31.9 秒、並列実行で 10.8 秒であり、このコードでは parfor ループへの変換が有利に働いていることが分かります。

配列のスライス化

変数を parfor ループの前に初期化して parfor ループ内で使用する場合、その変数は、ループ反復を評価する各 MATLAB ワーカーに渡さなければなりません。ループ内で使用される変数だけがクライアント ワークスペースから渡されます。ただし、変数のすべての出現箇所にループ変数でインデックスを付ける場合、各ワーカーは配列の必要部分のみを受け取ります。

例として、まずスライス化された変数を使用して parfor ループを実行し、経過時間を測定します。

% Sliced version

M = 100;
N = 1e6;
data = rand(M, N);

tic
parfor idx = 1:M
    out2(idx) = sum(data(idx, :)) ./ N;
end
toc
Elapsed time is 2.261504 seconds.

ここで、parfor ループ内にある N の代わりに、変数 data への参照を誤って使用したとします。ここでの問題は、size(data, 2) の呼び出しにより、スライス化された変数がブロードキャスト (スライス化されていない) 変数に変換されることです。

% Accidentally non-sliced version

clear

M = 100;
N = 1e6;
data = rand(M, N);

tic
parfor idx = 1:M
    out2(idx) = sum(data(idx, :)) ./ size(data, 2);
end
toc
Elapsed time is 8.369071 seconds.
誤ってブロードキャストされた変数のために、経過時間が長くなります。

この例の場合、結果が定数であり、ループ外で計算できるため、スライス化されていない data の使用を容易に回避できます。ブロードキャスト データはループ内で変更できないため、ブロードキャスト データのみに依存する計算は通常、ループの開始前に実行できます。この例の場合、計算は簡明であり結果はスカラーになるため、計算をループから外すのが有利です。

ローカル ワーカーとクラスター ワーカーでの最適化の比較

ローカル ワーカーでコードを実行すると、クラスターのリソースを使用せずにアプリケーションをテストするという便宜が得られる場合があります。しかし、ローカル ワーカーの使用には欠点や制限もあります。データ転送がネットワーク経由で行われないため、ローカル ワーカーでの転送動作ではネットワークでの通常の動作が示されません。

ローカル ワーカーでは、すべての MATLAB ワーカー セッションが同じマシンで実行されるため、parfor ループのパフォーマンスが実行時間の上で向上しない場合があります。これは、マシンにどれだけ多くのプロセッサとコアがあるかなど、多数の要因によって決まります。ここで重要な点は、クラスターで使用可能なコアはローカル マシンより多い可能性があることです。コードを MATLAB でマルチスレッド化できる場合、高速化する唯一の方法は、クラスターを使用して、より多くのコアで問題を処理することです。

ループに入る前に配列を作成 (左下の例) した方が、各ワーカーにループ内で独自の配列を作成させる (右側の例) よりも高速になるかどうか、実験することもできます。

並列プールをローカルで実行する以下の例を試し、各ループの実行時間の差異を確認します。まず、ローカル並列プールを開きます。

parpool('Processes')

次の例を実行し、もう一度実行します。それぞれのケースで、最初の実行は 2 回目の実行よりも遅くなります。これは、並列プールを起動して、コードをワーカーで使用可能にしなければならないためです。それぞれのケースについて、2 回目の実行の経過時間を確認します。

tic;
n = 200;
M = magic(n);
R = rand(n);
parfor i = 1:n
    A(i) = sum(M(i,:).*R(n+1-i,:));
end
toc
tic;
n = 200;
parfor i = 1:n
    M = magic(n);
    R = rand(n);
    A(i) = sum(M(i,:).*R(n+1-i,:));
end
toc

リモート クラスターで実行する場合は、複数のワーカーが同時に配列を作成でき転送時間が短縮されるため、動作の相違に気づくかもしれません。したがって、ローカル ワーカー用に最適化されたコードはクラスター ワーカー用には最適化されておらず、またその逆も成り立つ可能性があります。

参考

関連するトピック