arrayfun可以明显慢于matlab中的显式循环。 为什么?

考虑arrayfun的以下简单的速度testing:

 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),这个testing的输出是:

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

什么?!? arrayfun ,虽然看起来更清洁的解决scheme,是慢了一个数量级。 这里发生了什么?

此外,我为cellfun做了类似的testing,发现它比显式循环慢大约3倍。 再一次,这个结果与我所期望的相反。

我的问题是:为什么arrayfuncellfun这么慢? 考虑到这一点,有什么好的理由使用它们(除了使代码看起来不错)?

注意:我在这里讨论的是arrayfun的标准版本,而不是来自并行处理工具箱的GPU版本。

编辑:只是要清楚,我知道上面的Func1可以像奥利指出的vector化。 我只select它,因为它为实际问题的目的提供了一个简单的速度testing。

编辑:根据grungetta的build议,我重新做feature accel offtesting。 结果是:

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

换句话说,看起来差别很大一部分是JIT加速器在加速显式循环方面比在arrayfun 。 对我来说这似乎很奇怪,因为arrayfun实际上提供了更多的信息,也就是说,它的使用表明了对Func1调用的顺序并不重要。 另外,我注意到JIT加速器是否打开或closures,我的系统只使用一个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. 

现在,虽然完全“vector化”的解决scheme显然是最快的,但您可以看到,为每个x条目定义一个要调用的函数是一个巨大的开销。 只是明确写出计算得到了我们的因子5加速。 我想这表明,MATLAB的JIT编译器不支持内联函数 。 根据gnovice的回答,写一个正常的函数而不是匿名函数会更好。 尝试一下。

下一步 – 删除(vector化)内部循环:

 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. 

更接近“完全”vector化版本。 Matlab按列存储matrix。 你应该总是(如果可能的话)把你的计算结构按照列的方式进行vector化。

我们现在可以回到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怎么了? 没有任何functioninlinig,所以很多开销。 但为什么比双重嵌套循环更糟? 实际上,使用cellfun / arrayfun的话题已经被广泛讨论了很多次(例如在 这里 , 在这里和这里 )。 这些function非常慢,你不能用它们进行细粒度的计算。 您可以将它们用于单元和数组之间的代码简洁性和花式转换。 但function需要比你写的更重:

 tic Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false); toc Soln7 0.016786 seconds. 

请注意,Soln7现在是一个单元格..有时这是有用的。 代码性能现在非常好,如果您需要单元格作为输出,则在使用完全vector化解决scheme之后,不需要转换matrix。

那么为什么arrayfun比一个简单的循环结构慢呢? 不幸的是,我们不可能肯定地说,因为没有可用的源代码。 你只能猜测,因为arrayfun是一个通用函数,它处理各种不同的数据结构和参数,在简单的情况下不一定非常快,你可以直接表示为循环嵌套。 天花板从哪里来,我们无法知道。 可以通过更好的实施来避免开销? 也许不会。 但不幸的是,我们唯一能做的就是研究performance,以确定案件,哪些案件行之有效,哪些案件不行。

更新由于这个testing的执行时间很短,为了得到可靠的结果,我现在添加了一个围绕testing的循环:

 for i=1:1000 % compute end 

有时给出以下几点:

 Soln5 8.192912 seconds. Soln7 13.419675 seconds. Oli 8.089113 seconds. 

你看到arrayfun仍然不好,但是至less比vector化解决scheme差三个数量级。 另一方面,按列计算的单循环与完全vector化的版本一样快…这一切都是在单个CPU上完成的。 Soln5和Soln7的结果不会改变,如果我切换到2个核心 – 在Soln5中,我将不得不使用parfor来并行化。 忘了加速… Soln7不能并行运行,因为arrayfun不能并行运行。 另一方面,Olisvector化版本:

 Oli 5.508085 seconds. 

那是因为!!!!

 x = randn(T, N); 

不是gpuarraytypes;

所有你需要做的是

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