在 MATLAB,什么时候使用 bsxfun 最合适?

我注意到很多关于堆栈溢出的 MATLAB 问题的好答案经常使用函数 bsxfun。为什么?

动机: bsxfun的 MATLAB 文档中提供了以下例子:

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

当然,我们也可以使用以下方法进行同样的操作:

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

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

此外,这个问题的最后一个元素,同样来自于 bsxfun的 MATLAB 文档: “ C = bsxfun (fun,a,b)将函数 handle fun 指定的元素逐个二元运算应用到数组 a 和 b,启用了单例展开。”.“启用单例扩展”是什么意思?

28519 次浏览

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

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几乎快了两倍!它是有用的和快速的,因为它 避免显式分配内存的矩阵 idx0idx1,保存到内存,然后再读取它们只是添加它们。由于内存带宽是一项宝贵的资产,而且常常是当今架构的瓶颈,因此您希望明智地使用它,并减少代码的内存需求,以提高性能。

bsxfun允许您这样做: 基于对两个向量的所有元素对应用任意运算符创建一个矩阵,而不是对通过复制向量获得的两个矩阵进行显式操作。这是 单体扩展。你也可以把它想象成 布拉斯中的 外部产品:

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

将两个向量相乘得到一个矩阵。只是外部产品只执行乘法运算,而 bsxfun可以应用任意的运算符。顺便说一句,看到 bsxfun和 BLAS 外部产品一样快是非常有趣的。BLAS 通常被认为是提供 性能..。

感谢丹的评论,这里是一个伟大的 文章作者: Loren讨论正是这一点。

我使用 bsxfun(文件博客链接)有三个原因

  1. bsxfunrepmat快(见下文)
  2. bsxfun需要较少的输入
  3. 使用 bsxfun,就像使用 accumarray一样,使我对 MATLAB 的理解感觉良好。

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

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

我已经运行了 repmatbsxfun之间的速度比较与 MATLAB R2012b 在我的体面快的笔记本电脑。

Enter image description here

对我来说,bsxfun的速度是 repmat的三倍。如果数组变大,这种差异就会变得更加明显:

Enter image description here

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

在我的例子中,我使用 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时,我有 更少的虫子更快的程序

此外,它更短,这改善了 打字速度可读性

事情并不总是与3种常用的方法一致: repmat、通过索引进行扩展和 bsxfun。当你进一步增加向量的大小时,它会变得更加有趣。参见剧情:

comparison

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

从 R2016b 开始,MATLAB 为各种操作符支持 隐式展开,因此在大多数情况下不再需要使用 bsxfun:

以前,这个功能是通过 bsxfun函数提供的。 现在建议您将 bsxfun的大多数用法替换为 direct 对支持 隐性扩张的函数和运算符的调用。 与使用 bsxfun相比,隐性扩张提供 更快的速度, 更好的内存使用 提高代码的可读性

在罗兰的博客上有一个 隐式扩张详细讨论节目和它的表演:

在 R2016b 中,在大多数情况下,隐性扩张的工作速度和 bsxfun一样快或者更快。最好的性能增益为 隐性扩张的小矩阵和阵列大小。对于较大的矩阵大小,隐式扩展的速度往往与 bsxfun大致相同。