比较BSXFUN和REPMAT

在比较bsxfunrepmat的performance之前,很less有人问到这个问题。

  • 其中之一是: Matlab - bsxfun no longer faster than repmat? 。 这个试图调查repmatbsxfun之间的性能比较,特别是从input数组本身沿着input数组的平均值减去input数组的平均值,并且因此将仅探索@minus部分与其等效的repmat
  • 另一个是: In Matlab, when is it optimal to use bsxfun? 。 那个人试图做同样的操作减去平均沿列,并没有扩大到其他内置操作。

在这篇文章中,我试图调查bsxfunrepmat之间的性能数字,以覆盖所有的bsxfun内置bsxfun ,从而为这两个提供了更好的vector化解决scheme。

具体来说,我的这个职位的问题是:

  1. bsxfun的各种内置操作bsxfun执行对应的repmatbsxfun支持@plus@minus@times等浮点操作, @minus@plus@minus @times等关系和逻辑操作。那么,是否有特定的内置bsxfun可以使bsxfun加速比使用他们的repmat等值?

  2. 在她的blog post Loren bsxfun@() A - repmat(mean(A),size(A,1),1)@() bsxfun(@minus,A,mean(A))bsxfun进行了基准testing。 如果我需要覆盖所有内置的基准testing,我可以使用一些其他的比较模型来处理浮点,关系和逻辑运算吗?

介绍

关于bsxfunrepmat还是反过来辩论一直持续下去。 在这篇文章中,我们将尝试比较一下,随MATLAB运行的不同内置插件如何在运行时间方面与repmat等效,并希望从中得出一些有意义的结论。

开始了解BSXFUN内置插件

如果官方文档是从MATLAB环境或通过Mathworks网站提取的,可以看到bsxfun支持的完整内置函数列表。 该列表具有浮点,关系和逻辑运算的function。

MATLAB 2015A ,支持的按元素的浮点操作是:

  • @plus(总结)
  • @minus(减法)
  • @times(乘法)
  • @didivide(右分)
  • @ldivide(左分)
  • @pow(力量)
  • @rem(余数)
  • @mod(模数)
  • @ atan2(四象限反正切)
  • @ atan2d(四象限反正切度)
  • @ hypypot(平方和的平方根)。

第二组由元素关系操作组成,它们是:

  • @eq(等于)
  • @ne(不等于)
  • @lt(小于)
  • @le(小于或等于)
  • @gt(大于)
  • @ge(大于或等于)。

第三套也是最后一套包含如下所列的逻辑运算:

  • @and(逻辑和)
  • @or(逻辑或)
  • @xor(逻辑xor)。

请注意,我们已经从比较testing中排除了两个内置的@max (maximum)@min (minimum) ,因为可以有许多方法来实现他们的repmat等价物。

比较模型

为了真正比较repmatbsxfun之间的performance,我们需要确保时间只需要覆盖预期的操作。 因此,像bsxfun(@minus,A,mean(A))这样的东西不是理想的,因为它必须计算在bsxfun调用中的mean(A) ,然而这个时间可能是微不足道的。 相反,我们可以使用与mean(A)相同大小的另一个inputB

因此,我们可以使用: A = rand(m,n)B = rand(1,n) ,其中mn是我们可以根据它们改变和研究性能的尺寸参数。 这正是我们在下一节列出的基准testing中完成的。

对这些input进行操作的repmatbsxfun版本看起来像这样 –

 REPMAT: A + repmat(B,size(A,1),1) BSXFUN: bsxfun(@plus,A,B) 

标杆

最后,我们正在关注这个post,看这两个家伙争先恐后。 我们已经将基准分为三组,一组用于浮点运算,另一组用于关系逻辑运算,第三组用于逻辑运算。 我们已经将前面讨论的比较模型扩展到所有这些操作。

Set1:浮点运算

下面是第一组使用repmatbsxfun进行浮点运算的基准testing代码 –

 datasizes = [ 100 100; 100 1000; 100 10000; 100 100000; 1000 100; 1000 1000; 1000 10000; 10000 100; 10000 1000; 10000 10000; 100000 100; 100000 1000]; num_funcs = 11; tsec_rep = NaN(size(datasizes,1),num_funcs); tsec_bsx = NaN(size(datasizes,1),num_funcs); for iter = 1:size(datasizes,1) m = datasizes(iter,1); n = datasizes(iter,2); A = rand(m,n); B = rand(1,n); fcns_rep= {@() A + repmat(B,size(A,1),1),@() A - repmat(B,size(A,1),1),... @() A .* repmat(B,size(A,1),1), @() A ./ repmat(B,size(A,1),1),... @() A.\repmat(B,size(A,1),1), @() A .^ repmat(B,size(A,1),1),... @() rem(A ,repmat(B,size(A,1),1)), @() mod(A,repmat(B,size(A,1),1)),... @() atan2(A,repmat(B,size(A,1),1)),@() atan2d(A,repmat(B,size(A,1),1)),... @() hypot( A , repmat(B,size(A,1),1) )}; fcns_bsx = {@() bsxfun(@plus,A,B), @() bsxfun(@minus,A,B), ... @() bsxfun(@times,A,B),@() bsxfun(@rdivide,A,B),... @() bsxfun(@ldivide,A,B), @() bsxfun(@power,A,B), ... @() bsxfun(@rem,A,B), @() bsxfun(@mod,A,B), @() bsxfun(@atan2,A,B),... @() bsxfun(@atan2d,A,B), @() bsxfun(@hypot,A,B)}; for k1 = 1:numel(fcns_bsx) tsec_rep(iter,k1) = timeit(fcns_rep{k1}); tsec_bsx(iter,k1) = timeit(fcns_bsx{k1}); end end speedups = tsec_rep./tsec_bsx; 

Set2:关系操作

时间关系操作的基准testing代码将取代之前的基准testing代码中的fcns_repfcns_bsx

 fcns_rep = { @() A == repmat(B,size(A,1),1), @() A ~= repmat(B,size(A,1),1),... @() A < repmat(B,size(A,1),1), @() A <= repmat(B,size(A,1),1), ... @() A > repmat(B,size(A,1),1), @() A >= repmat(B,size(A,1),1)}; fcns_bsx = { @() bsxfun(@eq,A,B), @() bsxfun(@ne,A,B), @() bsxfun(@lt,A,B),... @() bsxfun(@le,A,B), @() bsxfun(@gt,A,B), @() bsxfun(@ge,A,B)}; 

Set3:逻辑运算

最后一组基准代码将使用此处列出的逻辑操作 –

 fcns_rep = { @() A & repmat(B,size(A,1),1), @() A | repmat(B,size(A,1),1), ... @() xor(A,repmat(B,size(A,1),1))}; fcns_bsx = { @() bsxfun(@and,A,B), @() bsxfun(@or,A,B), @() bsxfun(@xor,A,B)}; 

请注意,对于这个特定的集合,input数据A和B是逻辑数组。 所以,我们必须在早期的基准testing代码中进行这些编辑来创build逻辑arrays –

 A = rand(m,n)>0.5; B = rand(1,n)>0.5; 

运行时和观察

基准代码在这个系统configuration上运行:

 MATLAB Version: 8.5.0.197613 (R2015a) Operating System: Windows 7 Professional 64-bit RAM: 16GB CPU Model: Intel® Core i7-4790K @4.00GHz 

bsxfun ,在运行基准testing之后用bsxfunrepmat获得的bsxfun被绘制为三组。

A.浮点操作:

在这里输入图像描述

加速计划可以得出很less的观察结果:

  • 特别是bsxfun两个很好的加速例子是atan2atan2d
  • 接下来在这个列表中,右侧和左侧的分割操作相对于repmat等效代码提高了30% - 50%
  • 剩下的7操作的加速似乎非常接近统一,因此需要仔细检查。 加速计划可以缩小到如下所示的那7操作 –

在这里输入图像描述

基于上述情况,可以看出,除了一次性使用@hypot@modbsxfun仍然比这些7操作的repmat好大约10%。

B.关系操作:

这是bsxfun支持的下一个6个内置关系操作的bsxfun基准testing。

在这里输入图像描述

看上面的加速图,忽略bsxfunrepmat之间可比较的运行时间的启动情况,可以很容易地看到bsxfun赢得这些关系运算。 随着10x加速, bsxfun总是会更适合这些情况。

C.逻辑运算:

这是bsxfun支持的其余3个内置逻辑操作的第三组基准testing。

在这里输入图像描述

@xor在开始时忽略了@xor的一次性可比较的运行时间例子,对于这组逻辑操作似乎也占上风。

结论

  1. 在处理关系和逻辑操作时, repmat很容易被bsxfun 。 对于其余情况,如果有一个5 - 7%performance较差的情况是可以容忍的,那么仍然可以坚持bsxfun
  2. 当使用bsxfun进行关系和逻辑运算时,看到这种巨大的性能提升,人们可以考虑使用bsxfun处理具有ragged patterns数据,例如单元arrays以获得性能优势。 我喜欢用bsxfun的遮罩function来称呼这些解决scheme。 这基本上意味着我们创build逻辑数组,即带有bsxfun掩码,它可以用来在单元数组和数值数组之间交换数据。 在数字数组中有效的数据的一个优点是向量化的方法可以用来处理它们。 同样,由于bsxfun是一个很好的向量化工具,你可能会发现自己再一次使用它来解决同样的问题,所以有更多的理由去了解bsxfun 。 在这里,为了读者的利益,我能够探索这些方法的解决scheme案例很less: 1,2,3,4,5 。

未来的工作

目前的工作集中在利用repmat复制一维数据。 现在, repmat可以复制多个维度,所以bsxfun的扩展等同于复制。 因此,使用这两个函数在复制和扩展到多个维度上执行类似的testing将是有趣的。