ドキュメンテーション センター

  • 評価版
  • 製品アップデート

最新のリリースでは、このページがまだ翻訳されていません。 このページの最新版は英語でご覧になれます。

詳細なトピック

プログラミング メモについて

この節では、下に示すような影付きボックスにガイドラインと制限を示します。"必須" というラベルの付いたボックスの内容に parfor コードが準拠していない場合は、エラーになります。MATLAB® ソフトウェアはコード読み取り時に一部のエラーを、コード実行時に他のエラーを捕捉します。ここでは、これらのエラーをそれぞれ "静的" エラーおよび "動的" エラーと呼び、"必須 (静的)" または "必須 (動的)" というラベルを付けています。エラーにつながらないガイドラインには "推奨" というラベルを付けています。MATLAB コード アナライザーを使用して、parfor ループをこれらのガイドラインに準拠させることができます。

必須 (静的): ガイドラインまたは制限の説明

変数の分類

概要

parfor ループ内の名前が変数を参照していると認識される場合、以下のカテゴリの 1 つに分類されます。parfor ループに一意に分類できない変数が含まれている場合、またはいずれかの変数がそのカテゴリの制限に違反している場合、ループはエラーを生成します。

分類説明
loop (ループ)配列のループ インデックスとして機能します。
sliced (スライス化)そのセグメントがループの異なる反復で演算される配列
broadcast (ブロードキャスト)ループの前に定義される変数。その値はループ内で使用されますがループ内で割り当てられることはありません。
reduction (リダクション)ループの反復にわたって値を累積します。反復の順序は考慮されません。
temporary (一時的)ループ内で作成される変数。ただし、スライス化された変数やリダクション変数とは異なり、ループ外では使用できません。

これらの各分類は、コード部分において以下のように表示されます。

ループ変数

parfor 本体で i を変更すると、クライアントとワーカー間の通信に関する MATLAB の想定が無効となるため、次の制限は必須です。

必須 (静的): ループ変数への代入は許可されません。

次の例は、ループ本体でループ変数 i の値を変更しようとしているため、無効です。

parfor i = 1:n
   i = i + 1;
   a(i) = i;
end

スライス化された変数

"スライス化された変数" とは、値がセグメント、つまり "スライス" に分割可能な変数のことであり、これらのスライスはワーカーおよび MATLAB クライアントで個別に演算されます。ループのそれぞれの反復は、配列の個別のスライス上で動作します。スライス化された変数はクライアントとワーカー間の通信を削減できるため、その使用は重要です。ワーカーが必要とするスライスのみが、ワーカーが特定範囲のインデックスを扱い始めるときに限ってそのワーカーに送信されます。

次の例では、A の 1 つのスライスは配列の単一の要素で構成されています。

parfor i = 1:length(A)
   B(i) = f(A(i));
end

スライス化された変数の特性-  parfor ループ内の変数は、次の特性をすべてもつ場合にスライス化されます。リストの後に、各特性の説明を示します。

  • 第 1 レベルのインデックス付けのタイプ - インデックス付けの第 1 レベルが丸かっこ () または中かっこ {} である。

  • 固定インデックス リスト - 第 1 レベルの丸かっこまたは中かっこ内で、与えられた変数のすべての出現箇所に対するインデックスのリストが同じである。

  • インデックス付けの形式 — 変数のインデックスのリスト内で、1 つのインデックスのみがループ変数に関与する。

  • 配列の形 - スライス化された変数への代入に際し、代入の右辺が [] または '' ではない (これらの演算子は要素の削除を意味します)。

第 1 レベルのインデックス付けのタイプ。スライス化された変数では、第 1 レベルのインデックス付けは丸かっこ () または中かっこ {} で囲まれます。

次の表には、スライス化されている配列およびスライス化されていない配列に対する第 1 レベルのインデックス付け形式がリストされています。

スライス化されていない変数の参照スライス化された変数の参照
A.xA(...)
A.(...)A{...}

第 1 レベルの後、第 2 レベルやそれ以降では、任意のタイプの有効な MATLAB インデックス付けを使用できます。

次の左側の例に示す変数 A はスライス化されていません。これに対し、右側に示す変数 A はスライス化されています。

A.q{i,12}                         A{i,12}.q

固定インデックス リスト。スライス化された変数のインデックス付けにおける第 1 レベルの丸かっこまたは中かっこ内では、与えられた変数のすべての出現箇所に対しインデックスのリストは同じです。

次の左側の例に示す変数 A は、異なる場所で i および i+1 のインデックスが付いているため、スライス化されていません。右側に示す変数 A はスライス化されています。

parfor i = 1:k
   B(:) = h(A(i), A(i+1));
end
parfor i = 1:k
   B(:) = f(A(i));
   C(:) = g(A{i});
end

右上の例では、第 1 レベルの丸かっこのインデックス付けと第 1 レベルの中かっこのインデックス付けを伴うスライス化された変数が、同じループ内に現れます。これは許容されます。

インデックス付けの形式。スライス化された変数のインデックスのリストにおいて、いずれかのインデックスの形式は ii+ki-kk+i または k-i のいずれかとなります。ここで、i はループ変数、k は定数または単純な (インデックス付けされていない) ブロードキャスト変数です。また、他のインデックスは定数、単純なブロードキャスト変数、コロンまたは end となります。

i をループ変数として、次の左側の例に示す変数 A はスライス化されていません。右側に示す変数 A はスライス化されています。

A(i+f(k),j,:,3)
A(i,20:30,end)
A(i,:,s.field1)
A(i+k,j,:,3)
A(i,:,end)
A(i,:,k)

配列にインデックスを付けるループ変数を指定してその他の変数を使用する場合、これらの変数はループ内に指定できません。実際、このような変数は parfor ステートメント全体の実行を通して値が変わりません。ループ変数とそれ自身を組み合わせてインデックス式を形成することはできません。

配列の形。スライス化された変数は、一定の形を保たなければなりません。次の 2 行に示されている変数 A はどちらもスライス化されていません。

A(i,:) = [];
A(end + 1) = i;

どちらの場合も、A がスライス化されない理由は、スライス化された配列の形を変更すると、クライアントとワーカー間の通信を統御する前提に違反するためです。

スライス化された入力変数と出力変数-  すべてのスライス化された変数には入力または出力としての特性があります。スライス化された変数が両方の特性をもつ場合もあります。MATLAB はスライス化された入力変数をクライアントからワーカーへ、スライス化された出力変数をワーカーからクライアントへと転送します。変数が入力と出力の両方である場合は、両方向に転送されます。

次の parfor ループでは、r はスライス化された入力変数で、b はスライス化された出力変数です。

a = 0;
z = 0;
r = rand(1,10);
parfor ii = 1:10
   a = ii;
   z = z + ii;
   b(ii) = r(ii);
end

しかし、すべての反復で、配列要素へのすべての参照が使用前に設定されることが明らかな場合、変数はスライス化された入力変数ではありません。次の例では、A の全要素が設定されてから、それらの固定値のみが使用されています。

parfor ii = 1:n
   if someCondition
      A(ii) = 32;
   else
      A(ii) = 17;
   end
   loop code that uses A(ii)
end

スライス化された変数が入力として明示的に参照されない場合でも、暗黙的な使用により入力として参照される場合があります。次の例では、parfor ループ内で A のすべての要素が必ずしも設定されていないため、配列の元の値が受け取られ、保持され、ループから返されて、A はスライス化された入力変数であると同時にスライス化された出力変数となっています。

A = 1:10;
parfor ii = 1:10
    if rand < 0.5
        A(ii) = 0;
    end
end

ブロードキャスト変数

"ブロードキャスト変数" とは、ループ変数またはスライス化された変数以外の、ループ内の代入の影響を受けない任意の変数です。parfor ループの開始時に、すべてのブロードキャスト変数の値はすべてのワーカーに送信されます。このタイプの変数は有用で、不可欠の場合もありますが、大きなブロードキャスト変数はクライアントとワーカーの間で大量の通信を発生させる可能性があります。場合によっては、この目的で一時変数を使用し、ループ内で作成と割り当てを行う方が効率的なことがあります。

リダクション変数

MATLAB は、ループ反復が独立していなければならないというルールに対し、重要な例外としてリダクションをサポートしています。"リダクション変数" は、すべての反復に全体として依存し、かつ反復順序とは独立した値を累積します。MATLAB では、parfor ループ内でリダクション変数を使用できます。

リダクション変数は、以下すべてで見られるように、代入ステートメントの両辺に配置されます。ここで、expr は MATLAB の式です。

X = X + exprX = expr + X
X = X - exprリダクション変数に関するその他の考慮事項」の「リダクション代入の結合性」を参照してください。
X = X .* exprX = expr .* X
X = X * exprX = expr * X
X = X & exprX = expr & X
X = X | exprX = expr | X
X = [X, expr]X = [expr, X]
X = [X; expr]X = [expr; X]
X = {X, expr}X = {expr, X}
X = {X; expr}X = {expr; X}
X = min(X, expr)X = min(expr, X)
X = max(X, expr)X = max(expr, X)
X = union(X, expr)X = union(expr, X)
X = intersect(X, expr)X = intersect(expr, X)

この表に挙げられている利用可能な各ステートメントは "リダクション代入" と呼ばれ、定義上、リダクション変数はこのタイプの代入のみに現れます。

次の例は、リダクション変数 X の一般的な使用法を示しています。

X = ...;            % Do some initialization of X
parfor i = 1:n
    X = X + d(i);
end

このループは次と等価です。ここで、各 d(i) は異なる反復により計算されます。

X = X + d(1) + ... + d(n)

ループが標準的な for ループである場合、各反復内の変数 X は、ループに入る前に、またはループの前の反復からその値を取得します。ただし、この概念は parfor ループには適用されません。

parfor ループでは、X の値がクライアントからワーカーへ、またワーカーからクライアントへ伝送されることはありません。代わりに、d(i) の加算は各ワーカー内で実行され、i の範囲はそのワーカーで実行される 1:n のサブセットとなります。その後、結果がクライアントに返送され、クライアントがワーカーの部分和を X に加算します。したがって、ワーカーが加算の一部を実行し、クライアントが残りを行うことになります。

リダクション変数の基本ルール-  次の要件は、与えられた変数に関連付けられたリダクション代入をさらに規定しています。

必須 (静的): すべてのリダクション変数に対して、その変数のすべてのリダクション代入で同じリダクション関数またはリダクション演算を使用しなければなりません。

左側の parfor ループはリダクション代入の 1 つのインスタンスに + を使用し、別のインスタンスに [,] を使用しているため、無効となります。右側の parfor ループは有効です。

parfor i = 1:n
   if testLevel(k)
      A = A + i;
   else
      A = [A, 4+i];
   end
   % loop body continued
end
parfor i = 1:n
   if testLevel(k)
      A = A + i;
   else
      A = A + i + 5*k;
   end
   % loop body continued
end

必須 (静的): リダクション代入で * または [,] を使用する場合は、X のリダクション代入すべてで、X を一貫して最初の引数として指定するか、または一貫して 2 番目の引数として指定しなければなりません。

左下の parfor ループは無効です。連結内での項目の順序がループ内で一貫していないためです。右側の parfor ループは有効です。

parfor i = 1:n
   if testLevel(k)
      A = [A, 4+i];
   else
      A = [r(i), A];
   end
   % loop body continued
end
parfor i = 1:n
   if testLevel(k)
      A = [A, 4+i];
   else
      A = [A, r(i)];
   end
   % loop body continued
end

リダクション変数に関するその他の考慮事項-  この節では、リダクション関数のリダクション代入、結合性、可換性およびオーバーロードについて詳細を示します。

リダクション代入。リダクション変数」の表に示す特定形式のリダクション代入の他には、唯一の (より一般的な) 形式のリダクション代入は以下のようになります。

X = f(X, expr)X = f(expr, X)

必須 (静的): f には関数または変数のいずれかを指定できます。変数の場合は、parfor 本体の影響を受けてはなりません (つまり、ブロードキャスト変数となります)。

f が変数の場合、実際的には、その実行時の値は関数ハンドルになります。ただし、これは必ずしも必須ではありません。右辺が評価可能である限り、結果の値は X に格納されます。

左下の parfor ループは正しく実行されません。f = @times ステートメントにより、f が一時変数に分類され、このため各反復の開始時に消去されるからです。右側の parfor は、ループ内で f に代入を行っていないため適正です。

f = @(x,k)x * k;
parfor i = 1:n
   a = f(a,i);
   % loop body continued
   f = @times;  % Affects f
end
f = @(x,k)x * k;
parfor i = 1:n
   a = f(a,i);
   % loop body continued
end

&& および || 演算子は「リダクション変数」の表にリストされていないことに注意してください。&&|| を除く MATLAB のすべての行列演算には、対応する関数 f があります。たとえば、u op vf(u,v) と等価です。&&|| の場合、このような関数は作成できません。u&&vu||vv を評価する場合としない場合がありますが、f(u,v)"常に" v を評価したうえで f を呼び出すためです。この理由により、&&||parfor ループで利用可能なリダクション代入の表から除外されています。

各リダクション代入には、関数 f が関連付けられています。parfor ステートメントの確定的動作を保証する f のプロパティについては、以下の節で説明します。

リダクション代入の結合性。 リダクション変数の定義で使用される関数 f については、次のプラクティスを推奨しますが、これを順守しなくてもエラーは発生しません。このため、コードでこの推奨を順守するかどうかはユーザーに任せられています。

推奨: parfor ループの確定的動作を得るには、リダクション関数 f が結合的でなければなりません。

結合的にするには、関数 f はすべての ab および c について次を満たさなければなりません。

f(a,f(b,c)) = f(f(a,b),c)

リダクション変数を含め、変数の分類ルールは純粋に構文的なものです。こうしたルールでは、指定した f が本当に結合的かどうかを判断できません。結合性が仮定されますが、これに違反した場合、異なった形でのループの実行により異なった結果が返される可能性があります。

    メモ:   数学的な実数の加算が結合的であるのに対し、浮動小数点数の加算は近似的にのみ結合的であり、この parfor ステートメントの実行が異なれば、異なる丸め誤差をもつ X の値が出力される可能性があります。これはパラレル化の回避不能な代価です。

たとえば、左側のステートメントが 1 を出力するのに対し、右側のステートメントは 1 + eps を返します。

(1 + eps/2) + eps/2           1 + (eps/2 + eps/2)

マイナス演算子 (-) を除き、「リダクション変数」の表に挙げられたすべての特殊ケースには、対応する (おそらくは近似的に) 結合的な関数があります。MATLAB は代入 X = X - expr を、X = X + (-expr) を使用して計算します (このため、技術的には、このリダクション代入を計算する関数は plus であり、minus ではありません)。しかし、代入 X = expr - X は結合的な関数を使用して記述できず、この理由で表から除外されています。

リダクション代入の可換性。 +.*minmaxintersect および union を含む一部の結合的な関数は、可換性も備えています。つまり、これらの関数はすべての a および b について次を満たします。

f(a,b) = f(b,a)

非可換の関数の例は * (両方の次元のサイズが 2 以上の行列では乗算が可換ではないため)、[,][;]{,} および {;} です。非可換のため、これらの関数では引数の順序に一貫性が必要です。実践的な問題として、関数が可換的かつ結合的で、parfor が可換性を生かすよう最適化されている場合は、より効率的なアルゴリズムが可能となります。

推奨: *[,][;]{,} および {;} の場合を除いて、リダクション代入の関数 f は可換的でなければなりません。f が可換的ではない場合、異なるループの実行により異なる回答が返される可能性があります。

f が既知の非可換的な組み込み関数でない限り、これは可換的であると想定されます。現時点では、parfor にユーザー定義の非可換的関数を指定する方法はありません。

リダクション代入におけるオーバーロード。 大部分の結合的な関数 f には単位元 e があるため、任意の a に対して次が成り立ちます。

f(e,a) = a = f(a,e)

いくつかの関数について、単位元の例を次の表に示します。

関数単位元
+0
*.*1
[,][;][]

リダクション関数の単位元が認識される場合、MATLAB ではこれを使用します。このため、リダクション関数をオーバーロードするときは、結合性と可換性に加えて単位元にも注意しなければなりません。

推奨: parfor のリダクション代入で使用する場合、+*.*[,] または [;] のオーバーロードは結合的でなければなりません。オーバーロードでは、上に挙げた各単位元 (いずれもクラス double) を単位元として扱わなければなりません。

推奨: +.*union または intersect のオーバーロードは可換的でなければなりません。

関数に単位元を指定する方法はありません。こうした場合、parfor の動作は既知の単位元をもつ関数よりも効率が低くなりますが、結果は適正なものとなります。

同様に、X = X - expr の特殊な扱いのため、以下が推奨されます。

推奨: マイナス演算子 (-) のオーバーロードでは、X - (y + z)(X - y) - z と等価であるという数学の法則に従わなければなりません。

例: カスタム リダクション関数の使用-  ループの各反復で計算を行い、どの反復が最大値を出力するかを求めたいとします。これは、複数のループ反復の間に累積を行うリダクションに関する演習です。使用するリダクション関数では、すべての反復を比較した後に最終的に最大値が決められるようになるまで、反復結果を比較しなければなりません。

まずリダクション関数自体について考察します。反復の結果を別の反復結果と比較するため、関数には、入力として現在の反復の結果とこれまでの他の反復から得た既知の最大の結果が必要です。2 つの入力はいずれも反復の結果データと反復番号を含むベクトルです。

function mc = comparemax(A, B)
% Custom reduction function for 2-element vector input

if A(1) >= B(1) % Compare the two input data values
    mc = A;     % Return the vector with the larger result
else
    mc = B;
end

ループ内の各反復は、リダクション関数 (comparemax) を呼び出して 1 組の 2 成分ベクトルを渡します。

  • 累積された最大値とその反復インデックス (これはリダクション変数 cummax です)

  • その反復自体の計算値とインデックス

現在の反復のデータ値が cummmax の最大値よりも大きい場合、関数は新しい値とその反復番号を成分とするベクトルを返します。そうでない場合は、既存の最大値とその反復番号を返します。

ループのコードは以下のようになります。各反復では、リダクション関数 comparemax を呼び出して、その反復自体のデータ [dat i]cummax に既に集積されているデータと比較します。

% First element of cummax is maximum data value
% Second element of cummax is where (iteration) maximum occurs
cummax = [0 0];  % Initialize reduction variable
parfor ii = 1:100
    dat = rand(); % Simulate some actual computation
    cummax = comparemax(cummax, [dat ii]);
end
disp(cummax);

一時変数

"一時変数" とは、インデックス付けされていない直接的代入の対象であり、リダクション変数ではない任意の変数です。次の parfor ループでは、a および d は一時変数です。

a = 0;
z = 0;
r = rand(1,10);
parfor i = 1:10
   a = i;          % Variable a is temporary
   z = z + i;
   if i <= 5
      d = 2*a;     % Variable d is temporary
   end
end

for ループの動作とは異なり、MATLAB は parfor ループの各反復を実行する前に、一時変数があれば事実上これをクリアします。反復の独立性を保証するため、一時変数の値をループの 1 つの反復から別の反復に渡すことはできません。したがって、一時変数は parfor ループの本体内で指定されなければなりません。そのため、一時変数の値は各反復で個別に定義されます。

MATLAB は一時変数をクライアントに送り返しません。parfor ステートメントのコンテキスト内の一時変数は、ここでも通常の for ループとは異なり、ループ外に存在する同じ名前の変数に影響を与えません。

初期化されない一時変数-  一時変数は各反復の開始時にクリアされるため、MATLAB は、一時変数が反復内に設定される前にループ内の任意の反復がその一時変数を使用する特定のケースを検出できます。この場合、MATLAB は実行時のエラーではなく静的エラーを発します。実行時のエラーが間違いなく発生する場合に実行の続行を許可することにはあまり意味がないためです。このようなエラーは、特に変数の分類ルールに関して forparfor の間で混同があるため、頻繁に発生します。たとえば、次のように記述するとします。

  b = true;
  parfor i = 1:n
     if b && some_condition(i)
        do_something(i);
        b = false;
     end
     ...
  end

このループは、通常の for ループとして許容されますが、parfor ループとしては、b はループ内の割り当てのターゲットとして直接的に発生するため、一時変数となります。したがって、この一時変数は各反復の開始時にクリアされるため、if の条件での使用では絶対に初期化されません(parforfor に変更すると、b の値はループの連続的な実行を想定し、do_something(i) は、bfalse に設定されるまで、i の低い値に対してのみ実行されるようになります)。

リダクション変数としての一時変数-  一時変数が初期化されないもう 1 つの一般的原因は、リダクション変数として設定した変数をループの別のところで使用したために、その変数が形の上で一時変数として分類される場合です。以下に例を示します。

s = 0;
parfor i = 1:n
   s = s + f(i);
   ...
   if (s > whatever)
      ...
   end
end

s の出現箇所が本体の最初のステートメントにある 2 個だけであるなら、s はリダクション変数として分類されます。しかしこの例では、s はリダクション変数ではありません。リダクション代入以外にも、s > whatever の行で使用されているためです。s は (最初のステートメントで) 代入の対象なので、一時変数であり、MATLAB はこのことについてエラーを発しますが、リダクション変数との関連の可能性についても指摘します。

parforfor に変更した場合、リダクション代入以外での s の使用は、反復が特定の順序で実行されるかどうかに依存します。ここでのポイントは、parfor ループでは、その過程においてリダクション変数の値を "顧慮しない" 点が重要であるということです。リダクション値が使用可能になるのは、ループを抜けてからです。

パフォーマンスの改善

配列を作成する場所

parfor ループでは、各 MATLAB ワーカーに独自の配列または配列の一部を並列で作成させた方が、ループに入る前にクライアントに大きな配列を作成してすべてのワーカーに個別に送信するよりも、処理が高速になる可能性があります。各ワーカーにこうした配列の独自コピーをループ内で作成させると、すべてのワーカーがコピーを同時に作成できるため、クライアントからワーカーへデータを転送する時間が節約されます。これは、ループ内で無駄に初期化を繰り返さないよう、for ループに入る前にできるだけ多くの変数初期化を行うという通常のプラクティスには逆行するかもしれません。

配列を parfor ループに入る前と parfor ループ内のいずれで作成するかは、配列のサイズ、配列の作成に必要な時間、ワーカーが配列の全部または一部のいずれを必要とするか、各ワーカーが実行するループ反復の回数、その他の要因で決まります。多くの for ループは parfor ループに直接変換できますが、変換できる場合でも、コードの最適化には他の問題が関わってくることがあります。

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

ローカル ワーカーでは、すべての MATLAB ワーカー セッションが同じマシンで実行されるため、parfor ループのパフォーマンスが実行時間の上で向上しない場合があります。これは、マシンにどれだけ多くのプロセッサとコアがあるかなど、多数の要因によって決まります。ループに入る前に配列を作成 (左下の例) した方が、各ワーカーにループ内で独自の配列を作成させる (右側の例) よりも高速になるかどうか、実験することもできます。

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

parpool('local')

次に、以下の例を入力します(このドキュメンテーションを MATLAB ヘルプ ブラウザーで表示している場合は、下記のコードの各セグメントを強調表示し、右クリックで表示されるコンテキスト メニューから [選択の実行] を選択して、MATLAB でブロックを実行します。こうすると、時間測定に貼り付けや入力にかかる時間が含められません)。

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

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

この情報は役に立ちましたか?