Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

spmdparfor、および parfeval からの選択

並列コードの通信

並列で計算を実行するには、parforparfevalparfevalOnAll、または spmd を使用できます。それぞれの構成では異なる並列プログラミング概念を利用しています。計算全体を通してワーカーによる通信が必要な場合は、parfevalparfevalOnAll、または spmd を使用します。

  • コードを一連のタスクに分割でき、各タスクが他のタスクの出力に依存する可能性がある場合は、parfeval または parfevalOnAll を使用する。

  • 計算中にワーカー間で通信する必要がある場合は、spmd を使用する。

parfeval による計算は、ブロッキングのあるカンバン ボードに似たグラフで最もうまく表現されます。通常、結果は計算の完了後にワーカーから収集されます。afterEach または afterAll を使用して、parfeval 演算の実行結果を収集できます。通常は、結果をその後の計算に使用します。

spmd による計算は、ウォーターフォール型ワークフローに似たフローチャートで最もうまく表現されます。spmd ステートメントを実行するプール ワーカーはラボと呼ばれます。計算中にラボから結果を収集できます。ラボは、自身の計算を終了する前に他のラボと通信しなければならないことがあります。

確信がもてない場合は、並列コード内で、それぞれの計算をワーカー間での通信なしに完了できるかどうかと自問してください。できる場合は、parfeval を使用します。それ以外の場合は、spmd を使用します。

同期および非同期の作業

parforparfeval、および spmd から選択するときには、計算にクライアントとの同期が必要かどうかを検討します。

parforspmd には同期が必要なため、MATLAB® クライアント上で新規計算の実行がブロックされます。parfeval は同期が不要のため、クライアントは自由に他の作業を続行できます。

マルチスレッディングと ProcessPool のパフォーマンスの比較

この例では、クライアントと ProcessPool での関数の実行速度を比較します。一部の MATLAB 関数ではマルチスレッディングを使用しています。これらの関数を使用するタスクは、シングル スレッドよりマルチスレッド上でパフォーマンスが向上します。このため、多数のコアをもつマシン上でこれらの関数を使用する場合、ローカル クラスターのパフォーマンスはクライアント上のマルチスレッディングより低下することがあります。

この例の最後に挙げられているサポート関数 clientFasterThanPool は、複数の実行が parfor ループよりクライアント上で高速である場合に true を返します。構文は parfeval と同じです。最初の引数として関数ハンドル、2 番目の引数として出力の数を使用してから、関数に必要なすべての引数を指定します。

まず、ローカルの ProcessPool を作成します。

p = parpool('local');
Starting parallel pool (parpool) using the 'local' profile ...
Connected to the parallel pool (number of workers: 6).

サポート関数 clientFasterThanPool を使用して、関数 eig の実行がどれだけ速いかを調べます。eig を含み、関数呼び出しを表す無名関数を作成します。

[~, t_client, t_pool] = clientFasterThanPool(@(N) eig(randn(N)), 0, 500)
t_client = 22.6243
t_pool = 4.9334

並列プールは、クライアントより速く解を計算します。t_clientmaxNumCompThreads で除算して、クライアント上のスレッドあたりの所要時間を求めます。

t_client/maxNumCompThreads
ans = 3.7707

既定で、ワーカーはシングル スレッドです。結果は、t_pool の値が t_client/maxNumCompThreads の値のほぼ 1.5 倍であり、スレッドあたりの所要時間はクライアントとプールの両方で近いことが示されます。関数 eig はマルチスレッディングの恩恵を受けていません。

次に、サポート関数 clientFasterThanPool を使用して、関数 lu の実行がどれだけ速いかを調べます。

[~, t_client, t_pool] = clientFasterThanPool(@(N) lu(randn(N)), 0, 500)
t_client = 1.0225
t_pool = 0.4693

ローカル マシンのコアが 4 個以上の場合、並列プールは通常クライアントより速く解を計算します。t_clientmaxNumCompThreads で除算して、スレッドあたりの所要時間を求めます。

t_client/maxNumCompThreads
ans = 0.1704

この結果は、t_pool の値が t_client/maxNumCompThreads のほぼ 3 倍であり、スレッドあたりの所要時間がクライアント上でプールよりもはるかに短いことを示しています。各スレッドの使用される計算時間は短く、lu がマルチスレッディングを使用することを示しています。

補助関数の定義

サポート関数 clientFasterThanPool は、クライアント上の計算が並列プール上より速いかどうかをチェックします。この関数は、入力として関数ハンドル fcn と入力引数の変数番号 (in1, in2, ...) を受け入れます。clientFasterThanPool は、クライアントとアクティブな並列プールの両方で fcn(in1, in2, ...) を実行します。たとえば、rand(500) をテストする場合、関数ハンドルは次の形式でなければなりません。

fcn = @(N) rand(N);

次に、clientFasterThanPool(fcn,500) を使用します。

function [result, t_multi, t_single] = clientFasterThanPool(fcn,numout,varargin)
    % Preallocate cell array for outputs
    outputs = cell(numout);
    
    % Client
    tic
    for i = 1:200
        if numout == 0
            fcn(varargin{:});
        else
            [outputs{1:numout}] = fcn(varargin{:});
        end
    end
    t_multi = toc;
    
    % Parallel pool
    vararginC = parallel.pool.Constant(varargin);
    tic
    parfor i = 1:200
        % Preallocate cell array for outputs
        outputs = cell(numout);
        
        if numout == 0
            fcn(vararginC.Value{:});
        else
            [outputs{1:numout}] = fcn(vararginC.Value{:});
        end
    end
    t_single = toc;
    
    % If multhreading is quicker, return true
    result = t_single > t_multi;
end

parforparfeval、および spmd のパフォーマンスの比較

spmd の使用は、計算のタイプによって parfor ループや parfeval の使用より遅い場合もあれば速い場合もあります。オーバーヘッドが parfor ループ、parfeval、および spmd の相対的なパフォーマンスに影響します。

一連のタスクでは、通常、parfor および parfeval は次の条件下で spmd より優れたパフォーマンスを示します。

  • タスクあたりの計算時間が確定的でない。

  • タスクあたりの計算時間が一様でない。

  • 各タスクから返されるデータが小さい。

parfeval は次の場合に使用します。

  • 計算をバックグラウンドで実行する。

  • 各タスクが他のタスクに依存する。

この例では、parfor ループ、parfeval、および spmd を使用して行列演算が実行される際の速さを調べます。

まず、ローカルの並列ループ p を作成します。

p = parpool('local');
Starting parallel pool (parpool) using the 'local' profile ...
Connected to the parallel pool (number of workers: 6).

乱数行列の計算

parfor ループ、parfeval、および spmd の使用によって乱数行列が生成できる速さを調べます。試行数 (n) と行列のサイズ (mm 列の行列) を設定します。試行数を増やすと後の解析で使用する統計値が向上しますが、計算自体には影響しません。

m = 1000;
n = 20;

次に、parfor ループを使用して、各ワーカーで rand(m) を 1 回実行します。n 回の試行について、それぞれの時間を測定します。

parforTime = zeros(n,1);
for i = 1:n
    tic;
    mats = cell(1,p.NumWorkers);
    parfor N = 1:p.NumWorkers
      mats{N} = rand(m);
    end
    parforTime(i) = toc;
end

次に、parfeval を使用して、各ワーカーで rand(m) を 1 回実行します。n 回の試行について、それぞれの時間を測定します。

parfevalTime = zeros(n,1);
for i = 1:n
    tic;
    f(1:p.NumWorkers) = parallel.FevalFuture;
    for N = 1:p.NumWorkers
      f(N) = parfeval(@rand,1,m);
    end
    mats = fetchOutputs(f, "UniformOutput", false)';
    parfevalTime(i) = toc;
    clear f
end

最後に、spmd を使用して、各ラボで rand(m) を 1 回実行します。ラボ、および spmd を使ってラボでコマンドを実行する方法の詳細については、複数のデータセットでの単一プログラムの実行を参照してください。n 回の試行について、それぞれの時間を測定します。

spmdTime = zeros(n,1);
for i = 1:n
    tic;
    spmd
        e = rand(m);
    end
    eigenvals = {e{:}};
    spmdTime(i) = toc;
end

rmoutliers を使用して、各試行から外れ値を削除します。次に、boxplot を使用して時間を比較します。

% Hide outliers
boxData = rmoutliers([parforTime parfevalTime spmdTime]);

% Plot data
boxplot(boxData, 'labels',{'parfor','parfeval','spmd'}, 'Symbol','')
ylabel('Time (seconds)')
title('Make n random matrices (m by m)')

通常、spmd に必要な評価あたりのオーバーヘッドは、parforparfeval より長くなります。このため、この例では、parfor ループまたは parfeval を使用する方がより効率的です。

乱数行列の和の計算

次に、乱数行列の和を計算します。これを行うには、parfor ループでリダクション変数を、parfeval で計算後の和を、または spmdgplus を使用します。ここでも、試行数 (n) と行列のサイズ (mm 列の行列) を設定します。

m = 1000;
n = 20;

次に、parfor ループを使用して、各ワーカーで rand(m) を 1 回実行します。リダクション変数を使用して和を計算します。n 回の試行について、それぞれの時間を測定します。

parforTime = zeros(n,1);
for i = 1:n
    tic;
    result = 0;
    parfor N = 1:p.NumWorkers
      result = result + rand(m);
    end
    parforTime(i) = toc;
end

次に、parfeval を使用して、各ワーカーで rand(m) を 1 回実行します。すべての行列に fetchOutputs を使用してから、sum を使用します。n 回の試行について、それぞれの時間を測定します。

parfevalTime = zeros(n,1);
for i = 1:n
    tic;
    f(1:p.NumWorkers) = parallel.FevalFuture;
    for N = 1:p.NumWorkers
      f(N) = parfeval(@rand,1,m);
    end
    result = sum(fetchOutputs(f));
    parfevalTime(i) = toc;
    clear f
end

最後に、spmd を使用して、各ラボで rand(m) を 1 回実行します。gplus を使用して、すべての行列を合計します。結果を最初のラボにのみ送信するには、オプションの引数 targetlab1 に設定します。n 回の試行について、それぞれの時間を測定します。

spmdTime = zeros(n,1);
for i = 1:n
    tic;
    spmd
        r = gplus(rand(m), 1);
    end
    result = r{1};
    spmdTime(i) = toc;
end

rmoutliers を使用して、各試行から外れ値を削除します。次に、boxplot を使用して時間を比較します。

% Hide outliers
boxData = rmoutliers([parforTime parfevalTime spmdTime]);

% Plot data
boxplot(boxData, 'labels',{'parfor','parfeval','spmd'}, 'Symbol','')
ylabel('Time (seconds)')
title('Sum of n random matrices (m by m)')

この計算の場合、spmdparfor ループや parfeval より大幅に速くなります。parfor ループでリダクション変数を使用すると、parfor ループの各反復の結果がすべてのワーカーにブロードキャストされます。一方、spmdgplus を 1 回のみ呼び出してグローバルなリダクション演算を実行するため、必要なオーバーヘッドがより少なくなります。したがって計算のリダクション部分のオーバーヘッドは、spmdO(n2)parforO(mn2) です。

参考

| |

関連するトピック