このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。
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
を各ワーカーにコピーしてデータを再利用できます。tic
と toc
を使用して各ケースの経過時間を測定し、差を確認します。
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.
parallel.pool.Constant
オブジェクトを使用して、parfor
ループのパフォーマンスを向上させることができます。parfor
ループのプロファイリング
tic
と toc
を使用して経過時間を測定することにより、parfor
ループをプロファイリングできます。また、ticBytes
と tocBytes
を使用して、並列プール内のワーカーが送受信するデータ量も測定できます。これは、MATLAB プロファイラーを使用する通常の意味での MATLAB コードのプロファイリングとは異なることに注意してください。パフォーマンス向上のためのコードのプロファイリングを参照してください。
この例では、行列のスペクトル半径を計算し、for
ループを parfor
ループに変換します。高速化の結果およびデータ転送量を測定します。
MATLAB エディターで、次の
for
ループを入力します。経過時間を測定するために、tic
とtoc
を追加します。ファイルをMyForLoop.m
として保存します。function a = MyForLoop(A) tic for i = 1:200 a(i) = max(abs(eig(rand(A)))); end toc end
コードを実行し、経過時間を確認します。
a = MyForLoop(500);
Elapsed time is 31.935373 seconds.
MyForLoop.m
内のfor
ループをparfor
ループに置き換えます。並列プール内のワーカーが送受信するデータ量を測定するために、ticBytes
とtocBytes
を追加します。ファイルをMyParforLoop.m
として保存します。ticBytes(gcp); parfor i = 1:200 a(i) = max(abs(eig(rand(A)))); end tocBytes(gcp)
新しいコードを実行し、もう一度実行します。最初の実行は 2 回目の実行よりも遅くなります。これは、並列プールを起動し、コードをワーカーで使用可能にしなければならないためです。2 回目の実行の経過時間を確認します。
既定では、MATLAB によって、ローカル マシン上に複数のワーカーからなる並列プールが自動的に開かれます。
a = MyParforLoop(500);
経過時間は、逐次実行で 31.9 秒、並列実行で 10.8 秒であり、このコードでは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
ループへの変換が有利に働いていることが分かります。
配列のスライス化
変数を 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 |
リモート クラスターで実行する場合は、複数のワーカーが同時に配列を作成でき転送時間が短縮されるため、動作の相違に気づくかもしれません。したがって、ローカル ワーカー用に最適化されたコードはクラスター ワーカー用には最適化されておらず、またその逆も成り立つ可能性があります。