データの不要なコピーの回避
関数への値の引き渡し
入力引数をもつ関数を呼び出すと、MATLAB® では呼び出し元の関数のワークスペースから呼び出されている関数のパラメーター変数に値をコピーします。しかし、MATLAB では、コピーが必要ではない場合は、さまざまな手法によってそれを回避します。
MATLAB では、C++ などの言語のような、値への参照の定義方法が提供されていません。代わりに、MATLAB では複数の出力および複数の入力パラメーターを使用して、関数に出入りする値を把握することができます。
コピーオンライト
関数によって入力引数が変更されない場合、MATLAB では入力変数に含まれる値のコピーは作成されません。
たとえば、関数に大きい配列を渡したとします。
A = rand(1e7,1); B = f1(A);
関数 f1
は入力配列 X
の各要素を 1.1
で乗算し、結果を変数 Y
に代入します。
function Y = f1(X) Y = X.*1.1; % X is a shared copy of A end
関数は入力値を変更しないため、ローカル変数 X
と呼び出し元のワークスペースの変数 A
はデータを共有します。f1
が実行された後、A
に代入された値は変更されていません。呼び出し元のワークスペースの変数 B
には、要素ごとの乗算の結果が含まれます。入力は値により渡されます。ただし、f1
を呼び出すときはコピーは作成されません。
関数 f2
は入力変数のそのローカル コピーを変更しないため、ローカル コピーは入力 A
と共有されません。関数の X
の値は、呼び出し元のワークスペースの入力変数 A
の独立したコピーになります。f2
が呼び出し元のワークスペースに結果を返すと、ローカル変数 X
は破棄されます。
A = rand(1e7,1); B = f2(A);
function Y = f2(X) X = X.*1.1; % X is an independent copy of A Y = X; % Y is a shared copy of X end
入力の MATLAB 式としての受け渡し
関数から返された値を別の関数への入力引数として使用することができます。たとえば、関数 rand
を使用して関数 f2
の入力を直接作成できます。
B = f2(rand(1e7,1));
rand
から返された値を保持する唯一の変数は、関数 f2
のワークスペースの一時変数 X
です。呼び出し元のワークスペースにはこれらの値の共有コピーまたは独立したコピーはありません。関数出力を直接渡すと、呼び出された関数の入力値のコピーの作成に要する時間とメモリが節約されます。このアプローチは、入力値が再び使用されない場合に意味があります。
インプレース代入
元の入力値を保持する必要がないときに、関数の出力を入力として指定した同じ変数に代入することができます。
A = f2(A);
インプレース代入は、前に説明したコピーオンライト動作に従います。つまり入力変数値を変更すると、それらの値の一時コピーが作成されます。
MATLAB では、特定の条件下でメモリの最適化を適用できます。次の例を考えます。関数 canBeOptimized
によって、大きい乱数配列を変数 A
に作成します。次に、ローカル関数 fLocal
が呼び出され、A
が入力として渡されて、ローカル関数の出力が同じ変数名に代入されます。
function canBeOptimized A = rand(1e7,1); A = fLocal(A); end function X = fLocal(X) X = X.*1.1; end
ローカル関数 A = fLocal(A)
の呼び出しによって出力が変数 A
に代入されるため、MATLAB では関数実行時に A
の元の値を保持する必要がありません。fLocal
内の X
を変更してもデータのコピーは作成されません。代入 X = X.*1.1
は X
をインプレースで変更し、乗算の結果の新しい配列は代入しません。ローカル関数のコピーを排除することによりメモリが節約され、大きい配列の実行速度が改善されます。
ただし、ローカル関数内での代入に配列のインデックス付けが必要な場合、MATLAB はこの最適化を適用できません。たとえば、updateCells
で作成された cell 配列を変更するには、ローカル関数 gLocal
で X
へのインデックス付けが必要です。各ループ反復 i
で、X{i} = X{i}*1.1
形式のループ化された代入により、X{i}*1.1
の値を評価して保存するために X{i}
と同じサイズの一時変数が作成されます。MATLAB はその値を X{i}
に代入した後にその一時変数を破棄します。
function updateCells C = num2cell(rand(1e7,1)); C = gLocal(C); end function X = gLocal(X) for i = 1:length(X) X{i} = X{i}*1.1; end end
追加の制限がいくつか適用されます。MATLAB では、関数がエラーをスローした後に変数を使用できる場合、メモリの最適化を適用できません。したがって、この最適化はスクリプト、コマンド ライン、eval
の呼び出し、または try/catch
ブロック内のコードには適用されません。また、MATLAB では、呼び出された関数の実行中に元の変数に直接アクセスできる場合、メモリの最適化を適用しません。たとえば、fLocal
が入れ子関数であった場合、変数は親関数と共有されるため、MATLAB では最適化は適用できなくなります。最後に、代入された変数はグローバル変数または永続変数として宣言された場合、MATLAB ではメモリの最適化を適用しません。
インプレース代入を使用するコードのデバッグ
MATLAB が代入ステートメントにインプレース最適化を適用するときに、代入の左辺にある変数は、MATLAB が代入ステートメントの右辺を実行するまでアクセスできない状態に一時的に設定されます。デバッガーで、ステートメントの右辺の実行結果が変数に代入される前に MATLAB が停止した場合、左辺の変数を調べると、変数が使用できないことを示すエラーが発生することがあります。
たとえば、次の関数では変数 A
および B
の次元が一致しません。
function A = inPlace A = rand(100); B = rand(99); dbstop if error A = A.*B; end
デバッガーで関数を実行すると、エラーがスローされて停止します。
inPlace
Matrix dimensions must agree. Error in inPlace (line 5) A = A.*B; 5 A = A.*B;
デバッグ モードで変数 A
の値を表示しようとすると、この変数は一時的に使用できないため、エラーになります。
K>> A
Variable "A" is inaccessible. When a variable appears on both sides of an assignment statement, the variable may become temporarily unavailable during processing.
デバッグ時に柔軟性をより高めるには、コードのリファクタリングを行ってインプレース代入を削除します。たとえば、結果を他の変数に代入します。
function A = inPlace A = rand(100); B = rand(99); dbstop if error % Assign result to C instead of A C = A.*B; A = C; end
これにより、デバッガーで変数 A が表示されます。
値受け渡しセマンティクスを使用する理由
MATLAB では、引数を関数に渡すときおよび値を関数から返すときに値受け渡しセマンティクスを使用します。場合によっては、値受け渡しにより、呼び出された関数で元の値のコピーが作成されます。ただし、値受け渡しセマンティクスには一定のメリットがあります。
関数を呼び出すときに、入力変数は呼び出し元のワークスペースで変更されていないことが分かります。したがって、これらの値が変更されるのを防ぐためだけに、関数内または呼び出しサイトで入力のコピーを作成する必要はありません。返される値に代入された変数のみ変更されます。
また、参照により変数が渡された関数内でエラーが発生した場合にワークスペース変数が破損するのを防ぎます。
ハンドル オブジェクト
"ハンドル" と呼ばれる特殊なオブジェクトがあります。同じハンドルのコピーを保持するすべての変数は、基になる同じオブジェクトにアクセスし、変更できます。ハンドル オブジェクトは、オブジェクトが数値や行列などの数学オブジェクトではなく、ウィンドウ、プロット、デバイス、人などの物理オブジェクトを表す特殊な環境で役に立ちます。
ハンドル オブジェクトは handle
クラスから派生します。このクラスはイベントやリスナーなどの機能、デストラクター メソッド、および動的なプロパティのサポートを提供します。
値とハンドルの詳細については、ハンドル クラスと値クラスの比較と使用するクラスの種類を参照してください。