106

次の簡単な速度テストについて考えてみます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 Mint12上のMatlab2011b)では、このテストの出力は次のとおりです。

Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.

なに?!?arrayfun、確かに見た目がすっきりしたソリューションですが、桁違いに遅くなります。ここで何が起こっているのですか?

さらに、同様のスタイルのテストを行ったcellfunところ、明示的なループよりも約3倍遅いことがわかりました。繰り返しますが、この結果は私が期待したものとは逆です。

私の質問は:なぜarrayfunそしてcellfunそんなに遅いのですか?そしてこれを考えると、(コードの見栄えを良くする以外に)それらを使用する正当な理由はありますか?

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

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

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

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

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

4

2 に答える 2

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のスピードアップが得られました。これは、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.

もう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で実行されました。Soln5とSoln7の結果は、2コアに切り替えても変わりません-Soln5では、並列化するためにparforを使用する必要があります。スピードアップを忘れてください...arrayfunは並列で実行されないため、Soln7は並列で実行されません。一方、Olisベクトル化バージョン:

Oli  5.508085 seconds.
于 2012-09-21T08:33:59.010 に答える
-8

それは!!!!

x = randn(T, N); 

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

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

x = randn(T, N,'gpuArray');
于 2014-08-12T10:39:41.030 に答える