136

我注意到很多关于 Stack Overflow 上 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

5 回答 5

153

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

  1. bsxfunrepmat(见下文)更快
  2. bsxfun需要更少的打字
  3. Usingbsxfun和 using 一样accumarray,让我对自己对 MATLAB 的理解感觉良好。

bsxfun将沿着它们的“单一维度”复制输入数组,即数组大小为 1 的维度,以便它们与另一个数组的相应维度的大小相匹配。这就是所谓的“单例扩展”。顺便说一句,如果您调用squeeze.

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

我在我相当快的笔记本电脑上运行了 MATLAB R2012b之间repmat的速度比较。bsxfun

在此处输入图像描述

对我来说,bsxfun大约比repmat. 如果数组变大,差异会变得更加明显:

在此处输入图像描述

运行时的跳跃repmat发生在 1 MB 的数组大小附近,这可能与我的处理器缓存的大小有关 -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
于 2012-10-18T13:19:42.440 回答
41

就我而言,我使用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,我的错误更少并且我的编程速度更快

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

于 2012-10-18T19:54:27.287 回答
17

非常有趣的问题!我最近在回答这个问题时偶然发现了这种情况。考虑以下代码,它通过向量计算大小为 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-10-18T09:45:29.577 回答
16

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

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

Loren 的博客上有关于隐式扩展及其性能的详细讨论。引用MathWorks的 Steve Eddins 的话:

在 R2016b 中,隐式扩展的工作速度与大多数情况下一样快或更快bsxfun隐式扩展的最佳性能增益是使用较小的矩阵和数组大小。对于大矩阵大小,隐式扩展的速度往往与bsxfun.

于 2016-10-07T20:27:53.040 回答
9

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

比较

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

于 2013-10-19T10:52:31.927 回答