[英]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倍。 同樣,這個結果與我的預期相反。
我的問題是:為什么arrayfun
和cellfun
這么慢? 鑒於此,有沒有充分的理由使用它們(除了使代碼看起來很好)?
注意:我在這里談的是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.