arrayfunは、MATLABの明示的なループよりも大幅に遅くなる可能性があります。どうして?


105

の次の簡単な速度テストを考えます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倍遅いことがわかりました。繰り返しますが、この結果は私が期待したものとは正反対です。

私の質問は:なぜarrayfuncellfunそんなに遅く?これが与えられた場合、それらを使用する正当な理由はありますか(コードを美しく見せるため以外に)?

注:arrayfunここでは、並列処理ツールボックスのGPUバージョンではなく、標準バージョンについて説明します。

編集:明確にFunc1するために、Oliによって指摘されたように、上記はベクトル化できることを認識しています。実際の質問のために簡単な速度テストができるので、私はそれを選択しました。

編集: grungettaの提案に従って、私はでテストをやり直しましたfeature accel off。結果は次のとおりです。

Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.

言い換えれば、違いの大きな部分は、JITアクセラレータが明示的なforループを高速化するよりもはるかに優れていることarrayfunです。arrayfun実際にはより多くの情報を提供するため、これは私には奇妙に思えます。つまり、その使用により、呼び出しの順序はFunc1重要ではないことが明らかになります。また、JITアクセラレータのスイッチがオンかオフかに関係なく、私のシステムはCPUを1つしか使用していません...


10
幸いなことに、「標準的な解決策」は依然として最速です。3 * x。^ 2 + 2 * x-1; toc経過時間は0.030662秒です。
Oli、2012

4
@オリ私は誰かがこれを指摘し、ベクトル化できない関数を使用することを予想していたはずです:-)
Colin T Bowers

3
JITアクセラレータをオフにしたときにこのタイミングがどのように変化するかを知りたいと思います。コマンド「feature accel off」を実行してから、テストを再実行してください。
grungetta 2012

@grungetta興味深い提案。結果をいくつかのコメントとともに質問に追加しました。
Colin T Bowers

回答:


101

コードの他のバージョンを実行することで、アイデアを得ることができます。ループで関数を使用する代わりに、計算を明示的に書き込むことを検討してください

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の速度が向上しました。これは、MATLABs 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.

もう1つの要因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に戻ります。ループの順序は「行単位」です。変えましょう

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.

良いが、それでも非常に悪い。シングルループ-良い。二重ループ-悪い。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桁ほど悪いことはありません。一方、列単位の計算を使用する単一のループは、完全にベクトル化されたバージョンと同じくらい高速です...これはすべて単一のCPUで実行されました。2コアに切り替えても、Soln5とSoln7の結果は変わりません-Soln5では、並列化するためにparforを使用する必要があります。高速化について忘れてください... arrayfunは並列実行されないため、Soln7は並列実行されません。一方、オリスのベクトル化バージョン:

Oli  5.508085 seconds.

9
正解です。そして、Matlab Centralへのリンクはすべて非常に興味深い読み物を提供します。どうもありがとう。
Colin T Bowers 2012

これは素晴らしい分析です。
H.Muster 2012

そして興味深いアップデート!この答えは、与え続けるだけです:-)
Colin T Bowers

3
ほんの少しのコメント。MATLAB 6.5に戻るcellfunと、MEXファイルとして実装されました(Cソースコードが横にあります)。それは実際には非常に簡単でした。もちろん、6つのハードコードされた関数の1つのみを適用することをサポートしていました(関数ハンドルを渡すことができず、関数名を1つだけ持つ文字列のみ)
Amro

1
arrayfun +関数ハンドル=遅い!重いコードではそれらを避けてください。
イヴォン2014

-8

それは!!!!

x = randn(T, N); 

ではありません gpuarrayタイプで。

あなたがする必要があるのは

x = randn(T, N,'gpuArray');

2
@angainorの質問と優れた回答をもう少し注意深く読む必要があると思います。とは何の関係もありませんgpuarray。これがほぼ確実にこの回答が反対投票された理由です。
Colin T Bowers 2014

@Colin-私は投資家の方がより徹底的であることに同意しますが、答えは「gpuArray」について言及していません。「gpuArray」はここで良い貢献だと思います(正しい場合)。また、質問は「ここで何が起こっているのですか?」と少しずさんになりました。なので、データのベクトル化やGPUへの送信などの追加の方法への扉を開いたと思います。将来の訪問者にとっての価値を高めることができるので、私はこの答えを乗せさせます。私が間違った電話をした場合、私の謝罪。
JWW

1
またgpuarray、nVidiaグラフィックカードでのみサポートされていることも忘れてしまいます。彼らがそのようなハードウェアを持っていない場合、あなたのアドバイス(または欠如)は意味がありません。-1
rayryeng 2015年

一方、gpuarrayは、MATLABのベクトル化プログラミングの軽いサーベルです。
MrIO 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.