MATLAB Function ブロック内の行優先のデータとのインターフェイス
配列レイアウトは、統合、有用性、およびパフォーマンスにとって重要であることがあります。既定では、Simulink® は列優先のレイアウトを使用します。MATLAB Function ブロックも同様です。しかし、多くのデバイス、センサー、ライブラリは、データに行優先の配列レイアウトを使用します。行優先のレイアウトをもつ関数 coder.ceval
を MATLAB Function ブロック内で使用すると、モデルをこのデータに直接適用できます。
配列レイアウトは、パフォーマンスに影響を与えることもあります。多くのアルゴリズムは、ある特定の 1 つの配列レイアウトにおいてメモリ アクセスの効率が向上します。
シミュレーションとコード生成における行優先のレイアウト
MATLAB Function ブロックでは、行優先の配列レイアウトをブロック内で指定できます。この指定は関数レベルで行われ、関数外のモデルの配列レイアウトは変更されません。MATLAB Function ブロック内で指定する配列レイアウトは、シミュレーションと C/C++ コード生成の両方に適用されます。関数とクラスにおける配列レイアウトの指定を参照してください。
Simulink Coder™ および Embedded Coder® ソフトウェアを使用した C/C++ コード生成では、配列レイアウトをモデル レベルで指定できます。これは、MATLAB Function ブロックでサポートされています。モデル レベルでの配列レイアウトの制御の詳細については、行列および配列のコード生成 (Simulink Coder)を参照してください。配列レイアウトに関するモデル コード生成設定はシミュレーションに影響を与えません。配列のレイアウト (Simulink Coder)を参照してください。コード生成のために MATLAB Function ブロックで行優先のレイアウトを使用する例については、MATLAB Function ブロックを含むモデル用の行優先のコードの生成 (Simulink Coder)を参照してください。
MATLAB Function ブロックでは、モデルの配列レイアウト指定よりも、関数レベルでの配列レイアウト指定の方が優先されます。ただし、グローバル変数と永続変数に関しては、モデルの配列レイアウト指定が優先されます。
配列レイアウトの変換
既定の設定では、MATLAB® および Simulink は、データを列優先のレイアウトで格納します。さまざまな関数や境界で指定されるさまざまな配列レイアウトで、必要に応じた配列レイアウトの変換が自動的に挿入されます。
たとえば、モデルをシミュレートするか列優先のレイアウトを使用するモデルのコードを生成するときに、行優先のレイアウトを使用する MATLAB Function ブロックがモデルに含まれている場合、ソフトウェアは必要に応じてブロックの入力データを行優先に変換し、ブロックの出力データを列優先に戻します。配列レイアウトの変換は、パフォーマンスに影響を与えることがあります。配列レイアウトのパフォーマンスに関する考慮事項の詳細については、行優先のレイアウトのコード設計を参照してください。
配列レイアウトとアルゴリズム効率
一部のアルゴリズムでは、行優先のレイアウトの方がメモリ アクセスの効率が向上します。2 つの行列を加算する、次の関数を考えてみます。このアルゴリズムは、行と列の明示的なトラバーサルによって、加算を実行します。
function [S] = addMatrix(A,B) coder.rowMajor; S = zeros(size(A)); for row = 1:size(A,1) for col = 1:size(A,2) S(row,col) = A(row,col) + B(row,col); end end
このコードを MATLAB Function ブロックで使用する場合、コード生成によって関数用に次の C コードが生成されます。
... /* generated code for addMatrix using row-major */ for (row = 0; row < 20; row++) { for (col = 0; col < 10; col++) { S[col + 10 * row] = A[col + 10 * row] + B[col + 10 * row]; } } ...
配列は、以下の式を使用して、生成されたコードによってインデックスが付けられます。
[col + 10 * row]
配列は行優先のレイアウトで格納されるため、隣接するメモリ要素は 1 列のインクリメントで区切られます。アルゴリズムの "ストライド長" は 1 になります。ストライド長とは、連続するメモリ アクセス間のメモリ要素の距離です。ストライド長が短いほど、メモリ アクセスの効率が向上します。
データに列優先のレイアウトを使用すると、ストライド長は長くなり、メモリ アクセスの効率が低下します。この比較を確認するために、列優先のレイアウトを使用する addMatrix
用に生成された C コードについて考えます。
... /* generated code for addMatrix using column-major */ for (row = 0; row < 20; row++) { for (col = 0; col < 10; col++) { S[row + 20 * col] = A[row + 20 * col] + B[row + 20 * col]; } } ...
列優先のレイアウトでは、生成されたコードのメモリ内で、列要素は連続しています。隣接するメモリ要素は、1 行のインクリメントで区切られ、次の式に従ってインデックスが付けられます。
[row + 20 * col]
しかし、アルゴリズムは内側の for ループの列を反復します。そのため、列優先の C コードは、連続する各メモリ アクセスに 20 要素のストライドを要します。
最も効率的なメモリ アクセスを提供する配列レイアウトは、アルゴリズムによって異なります。このアルゴリズムでは、データを行優先のレイアウトにした方が、メモリ アクセスの効率が向上します。アルゴリズムは、データを行ごとに移動します。そのため、行優先の格納の方が効率的です。
N 次元配列のための行優先のレイアウト
N 次元配列に行優先のレイアウトを使用できます。配列が行優先のレイアウトで格納されると、最後 (右端) の次元またはインデックスの要素がメモリ内で連続します。列優先のレイアウトでは、最初 (左端) の次元またはインデックスの要素が連続します。
3 次元の入力を受け入れるサンプル関数 addMatrix3D
を考えます。
function [S] = addMatrix3D(A,B) coder.rowMajor; S = zeros(size(A)); for i = 1:size(A,1) for j = 1:size(A,2) for k = 1:size(A,3) S(i,j,k) = A(i,j,k) + B(i,j,k); end end end end
コード ジェネレーターは、以下の C コードを生成します。
... /* row-major layout */ for (i = 0; i < 20; i++) { for (j = 0; j < 10; j++) { for (k = 0; k < 5; k++) { S[(k + 5 * j) + 50 * i] = A[(k + 5 * j) + 50 * i] + B[(k + 5 * j) + 50 * i]; } } } ...
行優先のレイアウトでは、隣接するメモリは、最後のインデックスである k
の 1 のインクリメントによって区切られます。内側の for ループは、わずか 1 つのメモリ位置で区切られた隣接する要素を反復します。
coder.rowMajor
の呼び出しを削除し、列優先のレイアウトを使用する C コードを生成します。
... /* column-major layout */ for (i = 0; i < 20; i++) { for (j = 0; j < 10; j++) { for (k = 0; k < 5; k++) { S[(i + 20 * j) + 200 * k] = A[(i + 20 * j) + 200 * k] + B[(i + 20 * j) + 200 * k]; } } } ...
列優先のレイアウトでは、隣接する要素は、最初のインデックス i
の 1 のインクリメントで区切られます。このとき、内側の for ループは、200 のメモリ位置によって区切られた隣接する要素を反復します。ストライド長が長いため、キャッシュ ミスによってパフォーマンスが低下することがあります。
内側の for ループでは、アルゴリズムは最後のインデックス k
で反復するため、列優先のレイアウトを使用して生成されたコードのストライド長はさらに長くなります。このアルゴリズムでは、データを行優先のレイアウトにした方が、メモリ アクセスの効率が向上します。
外部関数の呼び出しにおける配列レイアウトの指定
データが特定のレイアウトで格納されていることを期待する外部 C/C++ 関数を呼び出すには、coder.ceval
を layout
構文とともに使用します。この構文を使用しないと、外部関数の入力と出力は、既定で列優先のレイアウトとみなされます。
行優先のレイアウトを使用するよう設計された外部 C 関数、myCFunctionRM
を考えます。この関数をコードに統合するには、'-layout:rowMajor'
または '-row'
オプションを使用して関数を呼び出します。このオプションは、入力と出力の配列が行優先の順番で格納されるようにします。コード ジェネレーターが、必要に応じて、配列レイアウト変換を挿入します。
coder.ceval('-layout:rowMajor','myCFunctionRM',coder.ref(in),coder.ref(out))
行優先のレイアウトを使用する MATLAB 関数の中で、列優先のレイアウトを使用するよう設計された外部関数を呼び出す必要があることもあります。その場合、'-layout:columnMajor'
または '-col'
オプションを使用します。
coder.ceval('-layout:columnMajor','myCFunctionCM',coder.ref(in),coder.ref(out))
同じコードの中で、行優先の関数呼び出しと列優先の関数呼び出しを実行できます。例として、次の関数 myMixedFn1
を考えます。
function [E] = myMixedFn1(x,y) %#codegen coder.rowMajor; % specify type of return arguments for ceval calls D = zeros(size(x)); E = zeros(size(x)); % include external C functions that use row-major & column-major coder.cinclude('addMatrixRM.h'); coder.updateBuildInfo('addSourceFiles', 'addMatrixRM.c'); coder.cinclude('addMatrixCM.h'); coder.updateBuildInfo('addSourceFiles', 'addMatrixCM.c'); % call C function that uses row-major order coder.ceval('-layout:rowMajor','addMatrixRM', ... coder.rref(x),coder.rref(y),coder.wref(D)); % call C function that uses column-major order coder.ceval('-layout:columnMajor','addMatrixCM', ... coder.rref(x),coder.rref(D),coder.wref(E)); end
外部ファイルは次のとおりです。
参考
coder.rowMajor
| coder.columnMajor
| coder.isColumnMajor
| coder.isRowMajor
| coder.ceval
関連するトピック
- 関数とクラスにおける配列レイアウトの指定
- 行優先のレイアウトのコード設計
- 行列および配列のコード生成 (Simulink Coder)