Matlabで「for」ループを記述する最も効率的な方法は何ですか?


12

たとえば、forマトリックスのインデックスを実行するダブルループがある場合、外側のループにインデックスを実行する列を配置する方が効率的であることを読みました。例えば:

a=zeros(1000);
for j=1:1000
 for i=1:1000
  a(i,j)=1;
 end
end

3つ以上のforループがある場合、それをコーディングする最も効率的な方法は何ですか?

例えば:

a=zeros(100,100,100);
for j=1:100
 for i=1:100
  for k=1:100
   a(i,j,k)=1;
  end
 end
end

4
ForMATLABではループが非常に遅くなります。可能な限り、MATLABでの明示的なループは避けてください。代わりに、通常、問題は行列/ベクトル演算の観点から表現できます。これがMATLABの方法です。マトリックスなどを初期化するための多くの組み込み関数もあります。たとえば、マトリックスのすべての要素を1(拡張により、乗算により任意の値に設定する、ones()関数すべて1の行列で乗算されます))。また、3次元配列でも動作します(ここで例を取り上げると思います)。
ピーターモーテンセン

3
@PeterMortensen Matlabのループの効率は、CやPythonに比べて(おおよそ)どんな要因で小さいのですか?なんで?また、ここ数年でMatlabのループの効率は良くなりましたか?
TensoR

3
@PeterMortensen「通常、問題は行列/ベクトル演算の観点から表現できます」-「通常」の特定の値については、はい。IMOは、Matlabなどで働いている人々は、行列/ベクトル演算ではできないすべてのものを無視するという数十年にわたる文化を持っていると言う方が正確です、そのため、すべてがそのハンマーの爪のように見えます。また、「Matlabのforループが遅い」と言うだけでなく、「Matlabが遅い」(CとFortranで書かれたLAプリミティブの高速ライブラリにリンクしているだけです)と言う必要があります。
leftaroundabout

5
forループのパフォーマンスが物議です:matlabtips.com/matlab-is-no-longer-slow-at-for-loops
ohreally

@leftaroundabout True。解釈された(または半解釈された)言語の速度を心配することは、実際の解決策が "この言語を使用しない"であるXY問題があることを示す明確な兆候です。もちろん、Simulinkでコード生成を使用している場合は例外ですが、問題はコードジェネレーターがどのCを構築するか、そしてどのくらい効率的かです。
グラハム

回答:


18

簡単に言えば、最も内側のループに左端のインデックスを配置する必要があります。あなたの例では、ループのインデックスはk、j、iになり、配列のインデックスはi、j、kになります。これは、MATLABがさまざまな次元をメモリに保存する方法に関係しています。詳細については、このreddit投稿の#13を参照してください。


2
または、組み込み関数ones()を使用します。
ピーターモーテンセン

5
@Peter OPの例は、ほぼ確実に、実際のユースケースではなく、何かを行うforループの単なるおもちゃの例です。
マット

@Mattあなたは正しいです。
テンソル

11

左端のインデックスが最も急速に変化する方が効率的である理由を説明するやや長い回答。理解する必要がある重要なことが2つあります。

最初に、MATLAB(およびFortran、ただしCおよびその他のほとんどのプログラミング言語ではない)は、配列を「列優先」でメモリに格納します。たとえば、Aが2 x 3 x 10の行列の場合、エントリは次の順序でメモリに保存されます

A(1,1,1)

A(2,1,1)

A(1,2,1)

A(2,2,1)

A(1,3,1)

A(2,3,1)

A(1,1,2)

A(2,1,2)

...

A(2,3,10)

列の主要な順序のこの選択は任意です。「行の主要な順序」の規則を簡単に採用できます。実際、これはCおよびその他のプログラミング言語で行われます。

理解する必要がある2番目の重要なことは、最新のプロセッサが一度に1つの場所にメモリにアクセスするのではなく、64または128の連続バイト(8または16の倍精度浮動小数点数)の「キャッシュライン」をロードして保存することです一度にメモリから。これらのデータのチャンクは一時的に高速メモリキャッシュに保存され、必要に応じて書き戻されます。(実際には、キャッシュアーキテクチャは3レベルまたは4レベルのキャッシュメモリで非常に複雑になりましたが、基本的な考え方は、若い頃にコンピューターが持っていた1レベルのキャッシュで説明できます。)

A

最も内側のループが行の添字を更新するようにループがネストされている場合、配列エントリはA(1,1)、A(2,1)、A(3,1)、...の順序でアクセスされます最初のエントリA(1,1)にアクセスすると、システムはA(1,1)、A(2,1)、...、A(8,1)を含むキャッシュラインをメインメモリからキャッシュに取り込みます。一番内側のループの次の8回の繰り返しは、追加のメインメモリ転送なしで、このデータに対して機能します。

別の方法で、最も内側のループで列インデックスが変化するようにループを構成する場合、AのエントリはA(1,1)、A(1,2)、A(1,3の順序でアクセスされます)、...この場合、最初のアクセスはA(1,1)、A(2,1)、...、A(8,1)をメインメモリからキャッシュに入れますが、これらのエントリは使用されません。2回目の反復でA(1,2)にアクセスすると、メインメモリから別の8エントリが取り込まれます。コードがマトリックスの行2で機能するようになると、A(2,1)エントリはキャッシュからフラッシュされて、他の必要なデータのための道ができます。その結果、コードは必要なトラフィックの8倍のトラフィックを生成しています。

最適化コンパイラの中には、この問題を回避するためにループを自動的に再構築できるものがあります。

行列の乗算と因数分解のための多くの数値線形代数アルゴリズムは、プログラミング言語に応じて行優先または列優先の順序スキームで効率的に動作するように最適化できます。これを間違った方法で行うと、パフォーマンスに重大な悪影響を与える可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.