行優先の配列レイアウトを使用するコードの生成
配列レイアウトは、統合、有用性、およびパフォーマンスにとって重要であることがあります。コード ジェネレーターは、既定で、列優先のレイアウトを使用するコードを生成します。しかし、多くのデバイス、センサー、ライブラリは、データに行優先の配列レイアウトを使用します。行優先のレイアウトを使用するコードを生成することで、そのデータにコードを直接適用できます。配列レイアウトは、パフォーマンスに影響を与えることもあります。多くのアルゴリズムは、ある特定の 1 つの配列レイアウトにおいてメモリ アクセスの効率が向上します。
行優先の配列レイアウトを指定するには、コマンド ライン、コード生成構成プロパティ、または MATLAB® Coder™ アプリを使用できます。また、行優先のレイアウトまたは列優先のレイアウトを、個々の関数やクラスに指定することもできます。エントリポイント (最上位) 関数の入力および出力には、すべて同じ配列レイアウトを使用しなければなりません。
行優先のレイアウトの指定
2 つの行列を加算する、次の関数を考えてみます。このアルゴリズムは、行と列の明示的なトラバーサルによって、加算を実行します。
function [S] = addMatrix(A,B) %#codegen 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
-rowmajor
オプションを使用して、addMatrix
の C コードを生成します。-args
オプションを使用して入力パラメーターの形式を指定し、コード生成レポートを起動します。
codegen addMatrix -args {ones(20,10),ones(20,10)} -config:lib -launchreport -rowmajor
もしくは、コード生成構成オブジェクト内の RowMajor
パラメーターを変更して、行優先のレイアウト用にコードを構成します。このパラメーターは、lib
、mex
、dll
、または exe
の任意の構成オブジェクトで使用できます。
cfg = coder.config('lib'); cfg.RowMajor = true; codegen addMatrix -args {ones(20,10),ones(20,10)} -config cfg -launchreport
この 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]; } } ...
MATLAB Coder アプリで行優先のレイアウトを指定するには、以下のようにします。
[生成] ダイアログ ボックスを開きます。[コード生成] ページで、[生成] 矢印 をクリックします。
[詳細設定] をクリックします。
[メモリ] タブで、[配列のレイアウト] を
[行優先]
に設定します。
生成されたコードが行優先のレイアウトを使用していることを検証するには、生成されたコードの配列のインデックスを、列優先のレイアウトを使用するコードの配列のインデックスと比較します。また、N 次元のインデックスを使用するコードを生成することもできます。N 次元のインデックスにより、配列レイアウトの違いを際立たせることができます。詳細については、N 次元インデックスを使用するコードの生成を参照してください。
既定の設定では、MATLAB は、データを列優先のレイアウトで格納します。生成された行優先のレイアウトを使用する MEX 関数を呼び出すと、ソフトウェアは自動的に入力データを列優先のレイアウトから行優先のレイアウトに変換します。MEX 関数から返された出力データは、列優先のレイアウトに再変換されます。スタンドアロンの lib
、dll
、exe
コード生成では、コード ジェネレーターは、エントリポイント関数の入力と出力が、その関数と同じ配列レイアウトで格納されているものと想定します。
配列レイアウトとアルゴリズム効率
一部のアルゴリズムでは、行優先のレイアウトの方がメモリ アクセスの効率が向上します。行優先のレイアウトを使用する addMatrix
の C コードを考えます。配列は、以下の式を使用して、生成されたコードによってインデックスが付けられます。
[col + 10 * row]
配列は行優先のレイアウトで格納されるため、隣接するメモリ要素は 1 列のインクリメントで区切られます。アルゴリズムの "ストライド長" は 1 になります。ストライド長とは、連続するメモリ アクセス間のメモリ要素の距離です。ストライド長が短いほど、メモリ アクセスの効率が向上します。
データに列優先のレイアウトを使用すると、ストライド長は長くなり、メモリ アクセスの効率が低下します。その比較を確かめるには、以下のように、列優先のレイアウトを使用するコードを生成します。
codegen addMatrix -args {ones(20,10),ones(20,10)} -config:lib -launchreport
コード生成により、次の 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) %#codegen 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
次のように、行優先のレイアウトを使用するコードを生成します。
codegen addMatrix3D -args {ones(20,10,5),ones(20,10,5)} -config:lib -launchreport -rowmajor
コード ジェネレーターは、以下の 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 つのメモリ位置で区切られた隣接する要素を反復します。列優先のレイアウトを使用して生成された次のコードとの違いを比べてください。
... /* 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 % 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
外部ファイルは次のとおりです。
コードを生成するには、次を入力します。
codegen -config:lib myMixedFn1 -args {ones(20,10),ones(20,10)} -rowmajor -launchreport
参考
coder.columnMajor
| coder.rowMajor
| coder.ceval
| coder.isRowMajor
| coder.isColumnMajor
| codegen