在Matlab中,何时最佳使用bsxfun?


135

我的问题:我注意到,关于SO的Matlab问题的很多很好的答案经常使用该函数bsxfun。为什么?

动机:在Matlab文档中bsxfun,提供了以下示例:

A = magic(5);
A = bsxfun(@minus, A, mean(A))

当然,我们可以使用以下方法执行相同的操作:

A = A - (ones(size(A, 1), 1) * mean(A));

实际上,简单的速度测试表明第二种方法的速度提高了约20%。那么为什么要使用第一种方法呢?我猜测在某些情况下使用bsxfun速度会比“手动”方法快得多。我真的很想看到这种情况的示例,并解释为什么它更快。

同样,这个问题的最后一个元素,同样来自Matlab文档,内容为bsxfun:“ C = bsxfun(fun,A,B)将函数句柄fun指定的逐个元素的二进制运算应用于数组A和B,且单例扩展已启用。”。短语“启用单例扩展已启用”是什么意思?


4
请注意,您获得的速度读数取决于您执行的测试。如果您在重新启动Matlab之后运行上述代码并简单地把tic...toc所有代码放在一起,那么代码的速度将取决于必须将函数读入内存中。
乔纳斯(Jonas)2012年

@Jonas是的,我只是通过阅读timeit您/ angainor / Dan提供的链接中的函数来了解这一点的。
科林·T·鲍尔斯

Answers:


152

我使用的三个原因bsxfun文档博客链接

  1. bsxfunrepmat(见下文)快
  2. bsxfun 需要更少的打字
  3. 使用bsxfun和使用一样accumarray,使我对Matlab的理解感到满意。

bsxfun将沿其“单维度”(即数组大小沿其为1的维度)复制输入数组,以便它们与另一个数组的相应维度的大小匹配。这就是所谓的“单子展开”。顺便说一句,如果您调用,则将删除单例尺寸squeeze

对于很小的问题,这种repmat方法可能会更快-但在该阵列大小下,两种操作都非常快,以至于在整体性能方面不会有任何不同。有两个重要的原因bsxfun是速度更快:(1)计算是在编译后的代码中进行的,这意味着数组的实际复制永远不会发生,以及(2)bsxfun是多线程Matlab函数之一。

我已经运行之间的速度对比repmat,并bsxfun在我的体面快速笔记本电脑R2012b。

在此处输入图片说明

对我来说,bsxfun速度比快3倍repmat。如果数组变大,则差异变得更加明显

在此处输入图片说明

的运行时间跳跃repmat发生在大约1Mb的阵列大小上,这可能与处理器缓存的大小有关-bsxfun跳转没有那么糟,因为它只需要分配输出数组。

您可以在下面找到我用于计时的代码:

n = 300;
k=1; %# k=100 for the second graph
a = ones(10,1);
rr = zeros(n,1);
bb=zeros(n,1);
ntt=100;
tt=zeros(ntt,1);
for i=1:n;
   r = rand(1,i*k);
   for it=1:ntt;
      tic,
      x=bsxfun(@plus,a,r);
      tt(it)=toc;
   end;
   bb(i)=median(tt);
   for it=1:ntt;
      tic,
      y=repmat(a,1,i*k)+repmat(r,10,1);
      tt(it)=toc;
   end;
   rr(i)=median(tt);
end

谢谢您的好评+1。我将其标记为答案,因为这是最全面的讨论,并且(在这一点上)也获得了最多的投票。
科林·T·鲍尔斯

40

就我而言,我bsxfun之所以使用它是因为它避免了我考虑列或行问题。

为了编写您的示例:

A = A - (ones(size(A, 1), 1) * mean(A));

我必须解决几个问题:

1)size(A,1)size(A,2)

2)ones(sizes(A,1),1)ones(1,sizes(A,1))

3)ones(size(A, 1), 1) * mean(A)mean(A)*ones(size(A, 1), 1)

4)mean(A)mean(A,2)

使用时bsxfun,我只需要解决最后一个问题:

a)mean(A)mean(A,2)

您可能会认为这是懒惰之类的东西,但是当我使用它时bsxfun,我的bug更少并且编程速度更快

而且,它更短,从而提高了打字速度可读性


1
感谢Oli的回复。+1,因为我认为此答案除了angainor和Jonas的回复之外,也有所贡献。我特别喜欢您列出在给定代码行中需要解决的许多概念性问题的方式。
科林·T·鲍尔斯

16

非常有趣的问题!我最近在回答这个问题时偶然发现了这种情况。考虑以下代码,该代码通过向量计算大小为3的滑动窗口的索引a

a = rand(1e7,1);

tic;
idx = bsxfun(@plus, [0:2]', 1:numel(a)-2);
toc

% equivalent code from im2col function in MATLAB
tic;
idx0 = repmat([0:2]', 1, numel(a)-2);
idx1 = repmat(1:numel(a)-2, 3, 1);
idx2 = idx0+idx1;
toc;

isequal(idx, idx2)

Elapsed time is 0.297987 seconds.
Elapsed time is 0.501047 seconds.

ans =

 1

在这种情况下bsxfun快将近两倍!它非常有用且快速,因为它避免了为矩阵idx0和矩阵显式分配内存idx1保存到内存中,然后再次读取它们只是为了添加它们。由于内存带宽是宝贵的资产,并且通常是当今体系结构的瓶颈,因此您需要明智地使用它,并减少代码的内存需求以提高性能。

bsxfun允许您执行以下操作:基于对两个向量的所有元素对应用任意运算符来创建矩阵,而不是对通过复制向量获得的两个矩阵进行显式运算。那就是单例扩展。您也可以将其视为BLAS 的外部产品

v1=[0:2]';
v2 = 1:numel(a)-2;
tic;
vout = v1*v2;
toc
Elapsed time is 0.309763 seconds.

您将两个向量相乘以获得矩阵。只是外部乘积仅执行乘法运算,并且bsxfun可以应用任意运算符。作为附带说明,非常有趣的bsxfun是它的速度与BLAS外部产品一样快。而BLAS通常被认为提供性能..

编辑感谢Dan的评论,这是Loren撰写的一篇很棒的文章,对此进行了讨论。



@丹感谢您的参考。
2012年

感谢您的答复。+1是第一个清楚地说明bsxfun一个很好的例子的主要优点的方法。
科林·T·鲍尔斯

13

从R2016b开始,Matlab支持多种运算符的隐式扩展,因此在大多数情况下,不再需要使用bsxfun

以前,此功能可通过bsxfun函数获得。现在建议用对bsxfun支持隐式扩展的函数和运算符的直接调用替换大多数的使用。与使用相比bsxfun隐式扩展可提供更快的速度更好的内存使用率以及更高的代码可读性

有一个详细讨论隐式扩张及其对罗兰的博客性能。要引用来自MathWorks的史蒂夫Eddins:

在R2016b中,隐式扩展的速度与bsxfun大多数情况下一样快。隐式扩展的最佳性能提升是矩阵和阵列尺寸较小。对于大型矩阵,隐式展开的速度往往与大致相同bsxfun


8

事物并不总是与3种常见方法一致:repmat,索引扩展和bsxfun。当您进一步增加向量大小时,它会变得更加有趣。见情节:

比较

bsxfun实际上在某个时候变得比其他两个慢一些,但是令我惊讶的是,如果您进一步增加向量大小(> 13E6输出元素),bsxfun突然又变快了约3倍。他们的速度似乎在逐步提高,顺序并不总是一致的。我的猜测是它也可能取决于处理器/内存大小,但总的来说,我认为我会bsxfun尽可能坚持下去。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.