spmd
、parfor
、および parfeval
からの選択
並列コードの通信
並列で計算を実行するには、parfor
、parfeval
、parfevalOnAll
、または spmd
を使用できます。それぞれの構成では異なる並列プログラミング概念を利用しています。計算全体を通してワーカーによる通信が必要な場合は、parfeval
、parfevalOnAll
、または spmd
を使用します。
コードを一連のタスクに分割でき、各タスクが他のタスクの出力に依存する可能性がある場合は、
parfeval
またはparfevalOnAll
を使用する。計算中にワーカー間で通信する必要がある場合は、
spmd
を使用する。
parfeval
による計算は、ブロッキングのあるカンバン ボードに似たグラフで最もうまく表現されます。通常、結果は計算の完了後にワーカーから収集されます。afterEach
または afterAll
を使用して、parfeval
演算の実行結果を収集できます。通常は、結果をその後の計算に使用します。
spmd
による計算は、ウォーターフォール型ワークフローに似たフローチャートで最もうまく表現されます。spmd
ステートメントを実行するプール ワーカーはラボと呼ばれます。計算中にラボから結果を収集できます。ラボは、自身の計算を終了する前に他のラボと通信しなければならないことがあります。
確信がもてない場合は、並列コード内で、それぞれの計算をワーカー間での通信なしに完了できるかどうかと自問してください。できる場合は、parfeval
を使用します。それ以外の場合は、spmd
を使用します。
同期および非同期の作業
parfor
、parfeval
、および spmd
から選択するときには、計算にクライアントとの同期が必要かどうかを検討します。
parfor
と spmd
には同期が必要なため、MATLAB® クライアント上で新規計算の実行がブロックされます。parfeval
は同期が不要のため、クライアントは自由に他の作業を続行できます。
マルチスレッディングと ProcessPool
のパフォーマンスの比較
この例では、クライアントと ProcessPool
での関数の実行速度を比較します。一部の MATLAB 関数ではマルチスレッディングを使用しています。これらの関数を使用するタスクは、シングル スレッドよりマルチスレッド上でパフォーマンスが向上します。このため、多数のコアをもつマシン上でこれらの関数を使用する場合、ローカル クラスターのパフォーマンスはクライアント上のマルチスレッディングより低下することがあります。
この例の最後に挙げられているサポート関数 clientFasterThanPool
は、複数の実行が parfor
ループよりクライアント上で高速である場合に true
を返します。構文は parfeval
と同じです。最初の引数として関数ハンドル、2 番目の引数として出力の数を使用してから、関数に必要なすべての引数を指定します。
まず、ローカルの ProcessPool
を作成します。
p = parpool('Processes');
Starting parallel pool (parpool) using the 'Processes' 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 = 34.8639
t_pool = 6.9755
並列プールは、クライアントより速く解を計算します。t_client
を maxNumCompThreads
で除算して、クライアント上のスレッドあたりの所要時間を求めます。
t_client/maxNumCompThreads
ans = 5.8107
既定で、ワーカーはシングル スレッドです。結果は、t_pool
の値が t_client/maxNumCompThreads
の値のほぼ 1.5 倍であり、スレッドあたりの所要時間はクライアントとプールの両方で近いことが示されます。関数 eig
はマルチスレッディングの恩恵を受けていません。
次に、サポート関数 clientFasterThanPool
を使用して、関数 lu
の実行がどれだけ速いかを調べます。
[~, t_client, t_pool] = clientFasterThanPool(@(N) lu(randn(N)), 0, 500)
t_client = 1.0447
t_pool = 0.5785
ローカル マシンのコアが 4 個以上の場合、並列プールは通常クライアントより速く解を計算します。t_client
を maxNumCompThreads
で除算して、スレッドあたりの所要時間を求めます。
t_client/maxNumCompThreads
ans = 0.1741
この結果は、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
parfor
、parfeval
、および spmd
のパフォーマンスの比較
spmd
の使用は、計算のタイプによって parfor
ループや parfeval
の使用より遅い場合もあれば速い場合もあります。オーバーヘッドが parfor
ループ、parfeval
、および spmd
の相対的なパフォーマンスに影響します。
一連のタスクでは、通常、parfor
および parfeval
は次の条件下で spmd
より優れたパフォーマンスを示します。
タスクあたりの計算時間が確定的でない。
タスクあたりの計算時間が一様でない。
各タスクから返されるデータが小さい。
parfeval
は次の場合に使用します。
計算をバックグラウンドで実行する。
各タスクが他のタスクに依存する。
この例では、parfor
ループ、parfeval
、および spmd
を使用して行列演算が実行される際の速さを調べます。
まず、プロセス ワーカーの並列プール p
を作成します。
p = parpool('Processes');
Starting parallel pool (parpool) using the 'Processes' profile ... Connected to the parallel pool (number of workers: 6).
乱数行列の計算
parfor
ループ、parfeval
、および spmd
の使用によって乱数行列が生成できる速さを調べます。試行数 (n
) と行列のサイズ (m
行 m
列の行列) を設定します。試行数を増やすと後の解析で使用する統計値が向上しますが、計算自体には影響しません。
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
に必要な評価あたりのオーバーヘッドは、parfor
や parfeval
より長くなります。このため、この例では、parfor
ループまたは parfeval
を使用する方がより効率的です。
乱数行列の和の計算
次に、乱数行列の和を計算します。これを行うには、parfor
ループでリダクション変数を、parfeval
で計算後の和を、または spmd
で gplus
を使用します。ここでも、試行数 (n
) と行列のサイズ (m
行 m
列の行列) を設定します。
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 回実行します。spmdPlus
を使用して、すべての行列を合計します。結果を最初のワーカーにのみ送信するには、オプションのターゲット ワーカー引数を 1
に設定します。n
回の試行について、それぞれの時間を測定します。
spmdTime = zeros(n,1); for i = 1:n tic; spmd r = spmdPlus(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)')
この計算の場合、spmd
は parfor
ループや parfeval
より大幅に速くなります。parfor
ループでリダクション変数を使用すると、parfor
ループの各反復の結果がすべてのワーカーにブロードキャストされます。一方、spmd
は spmdPlus
を 1 回のみ呼び出してグローバルなリダクション演算を実行するため、必要なオーバーヘッドがより少なくなります。したがって計算のリダクション部分のオーバーヘッドは、spmd
で 、parfor
で です。