parfor
ループ内での変数の分類
概要
MATLAB® Coder™ は、parfor
ループ内の変数を次の表のいずれかのカテゴリに分類します。分類できない変数はサポートされません。parfor
ループに一意に分類できない変数が含まれている場合または変数がそのカテゴリの制限に違反している場合、parfor
ループはエラーを生成します。
分類 | 説明 |
---|---|
loop (ループ) | 配列のループ インデックスとして機能します。 |
sliced (スライス化) | そのセグメントがループの異なる反復で演算される配列 |
broadcast (ブロードキャスト) | ループの前に定義される変数。その値はループ内で使用されますが、ループ内で割り当てられることはありません。 |
reduction (リダクション) | ループの反復にわたって値を累積します。反復の順序は考慮されません。 |
temporary (一時的) | ループ内で作成された変数。ただし、スライス化された変数やリダクション変数とは異なり、ループ外では使用できません。 |
これらの各分類は、次のコード部分では以下のように表示されます。
a=0; c=pi; z=0; r=rand(1,10); parfor i=1:10 a=i; % 'a' is a temporary variable z=z+i; % 'z' is a reduction variable b(i)=r(i); % 'b' is a sliced output variable; % 'r' a sliced input variable if i<=c % 'c' is a broadcast variable d=2*a; % 'd' is a temporary variable end end
スライス化された変数
"スライス化された変数" とは、値がセグメント、つまり "スライス" に分割可能な変数のことであり、これらのスライスは異なるスレッド上で個別に演算されます。ループのそれぞれの反復は、配列の個別のスライス上で動作します。
次の例では、A
の 1 つのスライスを示しています。スライスは該当する配列の単一の要素で構成されています。
parfor i = 1:length(A) B(i) = f(A(i)); end
スライス化された変数の特徴
parfor
ループ内の変数は、次の特徴をもつ場合にスライス化されます。
第 1 レベルのインデックス付けのタイプ — インデックス付けの第 1 レベルが小かっこ
()
である。固定インデックス リスト — 第 1 レベルの小かっこ内で、所定の変数のすべての出現に対してインデックスのリストが同じである。
インデックス付けの形式 — 変数のインデックスのリスト内で、1 つのインデックスのみがループ変数に関与する。
配列の形 — スライス化された変数への割り当て時に、割り当ての右辺が
[]
または''
ではない (これらの演算子は、要素の削除を意味します)。
第 1 レベルのインデックス付けのタイプ。スライス化される変数では、第 1 レベルのインデックス付けは小かっこ ()
で囲まれます。たとえば、A(...)
です。A.x
のようにドット表記を使用して変数を参照する場合、変数はスライス化されません。
左側の変数 A
はスライス化されません。右側の変数 A
はスライス化されます。
A.q(i,12) A(i,12).q
固定インデックス リスト。スライス化される変数のインデックス付けの第 1 レベルの小かっこ内では、指定された変数のすべての出現に対してインデックスのリストは同じです。
左側の変数 B
は i
および i+1
で別の場所でインデックス化されるため、B
はスライス化されません。右側の変数 B
はスライス化されます。
parfor i = 1:10 B(i) = B(i+1) + 1; end | parfor i = 1:10 B(i+1) = B(i+1) + 1; end |
インデックス付けの形式。スライス化される変数のインデックスのリスト内では、1 つのインデックスの形式が i
、i+k
、i-k
、k+i
または k-i
です。
i
は、ループ変数です。k
は、定数または単純な (インデックス付けされていない) 変数です。その他のインデックスは、定数、単純な変数、コロンまたは
end
です。
配列にインデックスを付けるループ変数を指定してその他の変数を使用する場合、これらの変数はループ内に指定できません。これらの変数は parfor
ステートメント全体を実行している間、値が変わりません。ループ変数とそれ自身を組み合わせてインデックス式を形成することはできません。
次の例では、i
はループ変数、j
および k
はインデックス付けされていない変数です。
変数 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) |
配列の形。スライス化された変数は、一定の形を保たなければなりません。次の例では、変数 A
はスライス化されません。
A(i,:) = []; A(end + 1) = i;
ブロードキャスト変数
"ブロードキャスト変数" とは、ループ変数またはスライス化された変数以外の、ループ内で変更されない変数のことです。
リダクション変数
MATLAB は、ループ反復が独立していなければならないというルールに対し、リダクションと呼ばれる重要な例外をサポートしています。"リダクション変数" は、すべての反復に同時に依存するが反復順序には依存しない値を累積します。MATLAB では、parfor
ループ内でリダクション変数を使用できます。
リダクション変数は、以下のいずれにも見られるように、代入ステートメントの両辺に配置されます。ここで、expr
は MATLAB 式です。
X = X + expr | X = expr + X |
X = X - expr | リダクション代入の要件の「リダクション代入の結合性」を参照してください。 |
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 = [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 = f(X, expr) | X = f(expr, X) |
次の例は、リダクション変数 X
の一般的な使用法を示しています。
X = 0; % 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
コードが従っていない場合は、エラーが発生します。MATLAB はコードの読み取り時にこうしたエラーの一部を、コードの実行時に他のエラーを検出します。これらのエラーにはそれぞれ、"必須 (静的)" または "必須 (動的)" のラベルが付けられます。エラーにつながらないガイドラインには "推奨" というラベルを付けています。MATLAB コード アナライザーを使用して、parfor
ループをガイドラインに準拠させることができます。
リダクション変数の基本ルール
次の要件は、与えられた変数に関連付けられたリダクション代入をさらに規定しています。
必須 (静的): すべてのリダクション変数に対して、その変数のすべてのリダクション代入で同じリダクション関数またはリダクション演算を使用しなければなりません。 |
左側の 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 を最初か 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 |
必須 (静的): リダクション変数にインデックスを付けたり、添字を使用したりすることはできません。 |
左側のコードは無効です。a
にインデックスを付けようとしており、MATLAB はそれをリダクション変数として分類できないためです。これを修正するために、右側のコードではインデックスのない変数を使用しています。
無効 | 有効 |
---|---|
a.x = 0 parfor i = 1:10 a.x = a.x + 1; end | tmpx = 0 parfor i = 1:10 tmpx = tmpx + 1; end a.x = tmpx; |
リダクション代入の要件
リダクション代入。リダクション変数の表に示す特定形式のリダクション代入の他には、唯一の (より一般的な) 形式のリダクション代入は以下のようになります。
X = f(X, expr) | X = f(expr, X) |
必須 (静的): f には関数または変数のいずれかを指定できます。f が変数の場合、parfor 本体で f を変更することはできません (つまり、ブロードキャスト変数であるということです)。 |
f
が変数の場合、実際的には、その実行時の値は関数ハンドルになります。ただし、右辺が評価可能である限り、結果の値は X
に格納されます。
左側の parfor
ループは正しく実行されません。ステートメント f = @times
により f
が一時変数として分類されるためです。したがって、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 v
は f(u,v)
と等価です。&&
と ||
の場合、このような関数は作成できません。u&&v
と u||v
には、v
を評価する場合としない場合があるためです。しかし、f(u,v)
は "常に" v
を評価してから f
を呼び出します。そのため、&&
と ||
は parfor
ループで利用可能なリダクション代入の表から除外されています。
各リダクション代入には、関数 f
が関連付けられています。parfor ステートメントの確定的動作を確保する f
のプロパティについては、以下の節で説明します。
リダクション代入の結合性。リダクション変数の定義で使用される関数 f
について、次の手法が推奨されています。ただし、このルールに従わなくてもエラーは発生しません。このため、コードでこの推奨に従うかどうかはユーザーに任せられています。
推奨:parfor ループの確定的な動作を得るには、リダクション関数 f が結合的でなければなりません。 |
結合的にするには、関数 f
はすべての a
、b
および 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
は結合関数を使用して記述できず、この理由で表から除外されています。
リダクション代入の可換性。+
、.*
、min
、max
、intersect
および union
を含む一部の結合的な関数は、可換性も備えています。つまり、すべての a
および b
について次を満たします。
f(a,b) = f(b,a)
非可換の関数には、*
(両方の次元のサイズが 1 より大きい行列では乗算が可換ではないため)、[,]
および [;]
が含まれます。非可換のため、これらの関数では引数の順序に一貫性が必要です。実践的な問題として、関数が可換的かつ結合的で、parfor
が可換性を生かすよう最適化されている場合は、より効率的なアルゴリズムが可能となります。
推奨:* 、[,] および [;] の場合を除いて、リダクション代入の関数 f は可換的でなくてはなりません。f が可換的ではない場合、異なるループの実行により異なる回答が返される可能性があります。 |
リダクションに使用される関数内で可換性の制限に違反すると、たとえエラーが発生しなくても、予期しない動作につながる可能性があります。
f
は、それが既知の非可換的な組み込み関数でない限りは可換性があると仮定されます。現時点では、parfor
にユーザー定義の非可換的関数を指定する方法はありません。
推奨:parfor ループのリダクション代入で + 、* 、.* 、[,] または [;] を使用する場合、そのオーバーロードは結合的でなければなりません。 |
推奨:+ 、.* 、union または intersect のオーバーロードは可換的でなければなりません。 |
同様に、X = X - expr
の特殊な扱いのため、以下を推奨します。
推奨:マイナス演算子 (- ) のオーバーロードでは、X - ( が (X - と等価であるという数学の法則に従わなければなりません。 |
カスタム リダクション関数の使用
この例では、ループでの計算を実行し、最大値とそれに対応するループ インデックスを格納します。独自のリダクション関数と parfor
ループを使用して、コードを高速化できます。反復ごとに、計算の値とループ インデックスを 2 要素の行ベクトルに格納します。カスタム リダクション関数を使用して、このベクトルを格納されたベクトルと比較します。計算からの値が格納された値より大きい場合は、古いベクトルを新しいベクトルに置き換えます。
リダクション関数 compareValue
を作成します。この関数は、入力として valueAndIndexA
と valueAndIndexB
の 2 つのベクトルを取ります。各ベクトルには値とインデックスが含まれています。リダクション関数 compareValue
は、値 (最初の要素) が最も大きいベクトルを返します。
function v = compareValue(valueAndIndexA, valueAndIndexB) valueA = valueAndIndexA(1); valueB = valueAndIndexB(1); if valueA > valueB v = valueAndIndexA; else v = valueAndIndexB; end end
すべて 0 からなる 1 行 2 列のベクトル maxValueAndIndex
を作成します。
maxValueAndIndex = [0 0];
parfor
ループを実行します。各反復で、rand
を使用して乱数値を作成します。その後、リダクション関数 compareValue
を使用して、maxValueAndIndex
を乱数値およびループ インデックスと比較します。結果を maxValueAndIndex
として格納する場合、maxValueAndIndex
をリダクション変数として使用します。parfor ii = 1:100 % Simulate some actual computation thisValueAndIndex = [rand() ii]; % Compare value maxValueAndIndex = compareValue(maxValueAndIndex, thisValueAndIndex); end
parfor
ループの実行が終了した後、リダクション変数 maxValueAndIndex
はクライアントで使用可能になります。最初の要素は parfor
ループで計算された最大の乱数値で、2 番目の要素はそれに対応するループ インデックスです。
maxValueAndIndex
maxValueAndIndex = 0.9706 89.0000
リダクション演算子の連結
X = expr op X
または X = X op expr
の形式の代入が、かっこで囲んだ代入 X = (expr) op X
または X = X op (expr)
とそれぞれ等価であるとき、MATLAB ではそれらがリダクション ステートメントとして分類されます。X
は変数、op
はリダクション演算子、そして expr
は二項リダクション演算子を 1 つ以上含む式です。その結果、MATLAB の演算子の優先順位のルールにより、MATLAB では演算子を連結する X = expr op1 X op2 expr2 ...
の形式の代入の一部が、parfor
ループ内のリダクション ステートメントとして分類されない可能性があります。
次の例では、代入が X = X + (1 * 2)
と等価であるため、MATLAB は X
をリダクション変数として分類します。
X = 0; parfor i=1:10 X = X + 1 * 2; end
次の例では、X = (X * 1) + 2
と等価である代入が X = (expr) op X
または X = X op (expr)
の形式ではないため、MATLAB は X
を一時変数として分類します。
X = 0; parfor i=1:10 X = X * 1 + 2; end
ベスト プラクティスとして、連結するリダクション代入では、かっこを使用して明示的に演算子の優先順位を指定します。
一時変数
"一時変数" とは、インデックス付けされていない、直接割り当てられているターゲットであり、リダクション変数ではない変数です。次の 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 Coder は parfor
ループの各反復を実行する前に、一時変数があればクリアにするため効率的です。反復は独立していなければならないため、一時変数の値はループの 1 つの反復から別の反復に渡すことはできません。したがって、一時変数は parfor
ループの本体内で指定されなければなりません。そのため、一時変数の値は各反復で個別に定義されます。
parfor
ステートメントのコンテキスト内の一時変数は、ループ外に存在する同じ名前の変数とは異なります。
初期化されない一時変数
一時変数がクリアされるのは各反復の開始時であるため、MATLAB Coder は、一時変数が反復内に設定される前にループ内の反復がその一時変数を使用する特定の状況を検出できます。この場合、MATLAB Coder は実行時エラーではなく静的エラーを発行します。実行時エラーが発生した場合に実行の続行を許可することにはあまり意味がないためです。たとえば、次のように記述するとします。
b = true; parfor i = 1:n if b && some_condition(i) do_something(i); b = false; end ... end
このループは、通常の for
ループとして許容されますが、parfor
ループとしては、b
はループ内の割り当てのターゲットとして直接的に発生するため、一時変数となります。したがって、この一時変数は各反復の開始時にクリアされるため、if
の条件での使用では初期化されません(parfor
を for
に変更すると、b
の値はループの連続的な実行を想定し、do_something(i)
は、b
が false
に設定されるまで、i
の低い値に対してのみ実行されるようになります)。