簡體   English   中英

arrayfun可能比matlab中的顯式循環慢得多。為什么?

[英]arrayfun can be significantly slower than an explicit loop in matlab. Why?

考慮以下對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 ......

您可以通過運行其他版本的代碼來實現這個想法。 考慮明確寫出計算,而不是在循環中使用函數

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.

另一個因素是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仍然很糟糕,但至少比矢量化解決方案差三個數量級。 另一方面,具有逐列計算的單個循環與完全矢量化版本一樣快......這都是在單個CPU上完成的。 如果切換到2個核心,Soln5和Soln7的結果不會改變 - 在Soln5中,我必須使用parfor來使其並行化。 忘掉加速... Soln7並不是並行運行的,因為arrayfun並不是並行運行的。 另一方面,Olis矢量化版本:

Oli  5.508085 seconds.

那是因為!!!!

x = randn(T, N); 

不是gpuarray類型;

你需要做的就是

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM