[解決済み] arrayfunはmatlabの明示的なループよりかなり遅くなることがあります. なぜでしょうか?
疑問点
次のような簡単なスピードテストについて考えてみましょう。
arrayfun
:
T = 4000;
N = 500;
x = randn(T, N);
Func1 = @(a) (3*a^2 + 2*a - 1);
tic
Soln1 = ones(T, N);
for t = 1:T
for n = 1:N
Soln1(t, n) = Func1(x(t, n));
end
end
toc
tic
Soln2 = arrayfun(Func1, x);
toc
私のマシン(Linux Mint 12上のMatlab 2011b)では、このテストの出力は次のようになります。
Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.
なんだーーーーーーーーーーーーーーーーーーーーーーーーーーーー
arrayfun
は、確かに見た目はすっきりしていますが、速度は一桁遅くなります。どうなっているのでしょうか?
さらに、私は同じようなスタイルのテストを
cellfun
に対して同様のテストを行ったところ、明示的なループよりも約 3 倍遅くなることがわかりました。繰り返しになりますが、この結果は私が期待したものとは正反対です。
私の質問は
なぜ
arrayfun
と
cellfun
はそんなに遅いのですか?そして、このことを考えると、それらを使用する良い理由はあるのでしょうか(コードの見栄えを良くする以外には)?
注意してください。
私が言っているのは、標準的なバージョンの
arrayfun
ここでは、並列処理ツールボックスのGPUバージョンではなく、標準バージョンについて話しています。
EDITです。
念のため、私が認識しているのは
Func1
はOliさんが指摘されたようにベクトル化することができます。私は、それが実際の質問の目的のための単純な速度テストをもたらすので、それを選んだだけです。
EDITです。
grungettaさんの提案に従って、私はテストを再実行しました。
feature accel off
. 結果は以下の通りです。
Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.
言い換えれば、JITアクセラレータが、明示的な
for
ループを高速化することが
arrayfun
. これは私には奇妙に思えます。
arrayfun
への呼び出しの順番を明らかにするものだからです。
Func1
の呼び出しの順番は重要ではないことがわかります。また、JIT アクセラレーターがオンであろうとオフであろうと、私のシステムは 1 つの CPU しか使用しないことに注目しました。
どのように解決するのですか?
他のバージョンのコードを実行することで、アイデアを得ることができます。ループ内で関数を使用する代わりに、計算を明示的に書き出すことを検討してください。
tic
Soln3 = ones(T, N);
for t = 1:T
for n = 1:N
Soln3(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
end
end
toc
パソコンで計算する時間です。
Soln1 1.158446 seconds.
Soln2 10.392475 seconds.
Soln3 0.239023 seconds.
Oli 0.010672 seconds.
さて、完全に「ベクトル化」されたソリューションが最速であることは明らかですが、すべての x エントリに対して呼び出される関数を定義することは 巨大な オーバーヘッドです。計算を明示的に書き出すだけで、ファクター5のスピードアップが得られました。これは、MATLABのJITコンパイラが はインライン関数をサポートしない . そこにあるgnoviceの答えによると、実際には無名関数よりも普通の関数を書いた方が良いそうです。試してみてください。
次のステップ - 内部ループを削除(ベクトル化)します。
tic
Soln4 = ones(T, N);
for t = 1:T
Soln4(t, :) = 3*x(t, :).^2 + 2*x(t, :) - 1;
end
toc
Soln4 0.053926 seconds.
もうひとつの高速化要因5:MATLABではループを避けるべきという記述があるのですが...。それとも本当にあるのでしょうか?では、これを見てください。
tic
Soln5 = ones(T, N);
for n = 1:N
Soln5(:, n) = 3*x(:, n).^2 + 2*x(:, n) - 1;
end
toc
Soln5 0.013875 seconds.
完全に」ベクトル化されたバージョンにかなり近いです。Matlabは行列を列方向に格納します。常に(可能な限り)「列方向に」ベクトル化されるように計算を構成する必要があります。
Soln3に戻ることができます。そこでのループの順序は'row-wise'です。これを変更します。
tic
Soln6 = ones(T, N);
for n = 1:N
for t = 1:T
Soln6(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
end
end
toc
Soln6 0.201661 seconds.
良くなったが、まだ非常に悪い。シングルループ - 良い。2重ループ - 悪い。MATLABはループのパフォーマンスを向上させるための適切な作業を行ったと思いますが、それでもループのオーバーヘッドは存在します。しかし、ループのオーバーヘッドは依然として存在しています。しかし、この計算はメモリバンド幅に制限されているので、ループのオーバーヘッドが見えてしまうのです。そして、あなたは は そこで Func1 を呼び出すことによるオーバーヘッドがさらにはっきりと見えます。
では、arrayfunはどうなっているのでしょうか?そこにも関数はありませんし、多くのオーバーヘッドがあります。しかし、なぜ二重にネストされたループよりも悪いのでしょうか?実は、cellfun/arrayfunを使うというトピックは何度も広く議論されてきました(たとえば、以下のようなものです)。 ここで , ここ , ここ そして はこちら ). これらの関数は単純に遅いので、このような細かい計算には使えません。コードを簡潔にするためや、セルと配列の間の派手な変換をするためには使えます。しかし、その関数はあなたが書いたものよりも重くなければならない。
tic
Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false);
toc
Soln7 0.016786 seconds.
Soln7がセルであることに注意してください...時にはそれは便利です。コードのパフォーマンスはかなり良くなり、出力としてセルが必要な場合は、完全にベクトル化されたソリューションを使用した後に、行列を変換する必要はありません。
では、なぜarrayfunは単純なループ構造より遅いのでしょうか?残念ながら、ソースコードが公開されていないため、はっきりとしたことは言えません。arrayfun はあらゆる種類のデータ構造と引数を扱う汎用関数なので、ループのネストとして直接表現できるような単純なケースでは、必ずしも非常に高速であるとは言えないと推測できます。このオーバーヘッドがどこから来るのかはわかりません.このオーバーヘッドは、より良い実装によって回避できるのでしょうか?そうではないかもしれません。しかし、残念ながら、私たちにできることは、パフォーマンスを調査して、うまく機能するケースとそうでないケースを特定することだけです。
更新 このテストの実行時間は短いので、信頼できる結果を得るために、私はテストの周りにループを追加しました。
for i=1:1000
% compute
end
以下にいくつかの時間を示す。
Soln5 8.192912 seconds.
Soln7 13.419675 seconds.
Oli 8.089113 seconds.
arrayfunはまだ悪いですが、少なくともベクトル化されたソリューションより3桁も悪いわけではないことがわかると思います。一方、列ごとの計算を行う単一のループは、完全にベクトル化されたバージョンと同じくらい高速です...。これはすべて1つのCPUで行われたものです。Soln5とSoln7の結果は、2コアに切り替えても変わりません。Soln5では、パーフォーを使って並列化する必要がありますね。高速化なんて忘れて...。Soln7はarrayfunが並列に動かないので、並列にはなりません。一方、Olisベクトル化版。
Oli 5.508085 seconds.
関連
-
[解決済み] MIPSで配列を作る(アクセスする)方法
-
[解決済み] Rで3D行列をセットアップし、特定の要素にアクセスする
-
[解決済み] 最大和サブアレイのブルートフォースはなぜO(n^2)なのか?
-
[解決済み] SQLiteのINSERT/per-secondのパフォーマンスを向上させる
-
[解決済み] B "の印刷が "#"の印刷より劇的に遅いのはなぜですか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] なぜJavaでは2 * (i * i)の方が2 * i * iより速いのですか?
-
[解決済み] なぜ[]はlist()よりも速いのですか?
-
[解決済み] Javaにおける例外処理によるパフォーマンスへの影響とは?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Luaで2次元配列を作成するには?
-
[解決済み] Rで3D行列をセットアップし、特定の要素にアクセスする
-
[解決済み] 配列をヒープ化するためのヒープにおけるsiftUp, siftDown操作
-
[解決済み] GCCです。配列型に不完全な要素型がある
-
[解決済み] Ruby: ハッシュの配列で Enumerator を取得しようとすると nil:NilClass の未定義メソッド `[]' が発生する。
-
[解決済み] Javascript/Typescriptで配列のクローンを作成する
-
[解決済み] Postgres の配列の NOT
-
[解決済み] 数百万のピクセルを持つ2Dの非ボックス化ピクセル配列にはどのようなHaskell表現が推奨されますか?
-
[解決済み] 並べ替えられた2つの配列の和で、k番目に小さい要素を見つけるにはどうすればよいですか?
-
[解決済み] 配列のインデックスやキーをチェックする最も簡単な方法とは?