是MATLAB OOP运行缓慢还是我做错了什么?


144

我与实验MATLAB OOP,因为一开始我模仿我的C ++的记录器类和我把我所有的字符串辅助函数在String类,以为这将是巨大的,能够做的事情一样a + ba == ba.find( b )而不是strcat( a b )strcmp( a, b ),检索的第一个元素strfind( a, b ),等等。

问题:减速

我把以上这些东西都使用了,立即注意到速度急剧下降。我做错了吗(由于我有限的MATLAB经验,这肯定是可能的),还是MATLAB的OOP只是引入了很多开销?

我的测试用例

这是我对字符串所做的简单测试,基本上只是添加一个字符串,然后再次删除添加的部分:

注意:实际不要在实际代码中编写这样的String类!Matlab现在具有本机string数组类型,您应该使用它。

classdef String < handle
  ....
  properties
    stringobj = '';
  end
  function o = plus( o, b )
    o.stringobj = [ o.stringobj b ];
  end
  function n = Length( o )
    n = length( o.stringobj );
  end
  function o = SetLength( o, n )
    o.stringobj = o.stringobj( 1 : n );
  end
end

function atest( a, b ) %plain functions
  n = length( a );
  a = [ a b ];
  a = a( 1 : n );

function btest( a, b ) %OOP
  n = a.Length();
  a = a + b;
  a.SetLength( n );

function RunProfilerLoop( nLoop, fun, varargin )
  profile on;
  for i = 1 : nLoop
    fun( varargin{ : } );
  end
  profile off;
  profile report;

a = 'test';
aString = String( 'test' );
RunProfilerLoop( 1000, @(x,y)atest(x,y), a, 'appendme' );
RunProfilerLoop( 1000, @(x,y)btest(x,y), aString, 'appendme' );

结果

1000次迭代的总时间(以秒为单位):

btest 0.550(使用String.SetLength 0.138,String.plus 0.065,String.Length 0.057)

验证0.015

记录器系统的结果也是如此:frpintf( 1, 'test\n' )内部使用String类时,对我的系统的1000次调用需要0.1秒,对我的系统的1000次调用需要7(!)秒(确定,它具有更多逻辑,但是与C ++相比:使用std::string( "blah" )std::cout在输出端与普通端相比,我的系统的开销std::cout << "blah"约为1毫秒。)

查找类/包函数时是否只是开销?

由于对MATLAB进行了解释,因此它必须在运行时查找功能/对象的定义。所以我想知道查找类或包函数与路径中的函数可能涉及更多的开销。我试图对此进行测试,但它变得陌生。为了排除类/对象的影响,我比较了在路径中调用函数与在包中调用函数:

function n = atest( x, y )
  n = ctest( x, y ); % ctest is in matlab path

function n = btest( x, y )
  n = util.ctest( x, y ); % ctest is in +util directory, parent directory is in path

结果与上述相同:

约0.004秒,约0.001秒

btest 0.060秒,util.ctest中0.014秒

那么,所有这些开销仅仅是来自MATLAB花费时间查找其OOP实现的定义,而对于直接在路径中的函数却没有这种开销吗?


5
谢谢你这个问题!Matlab堆的性能(OOP /关闭)使我困扰了很多年,请参阅stackoverflow.com/questions/1446281/matlabs-garbage-collector。我真的很好奇MatlabDoug / Loren / MikeKatz将如何回应您的帖子。
米哈伊尔,

1
^读起来很有趣。
stijn

1
@MatlabDoug:也许您的同事Mike Karr可以评论OP?
米哈伊尔,

4
读者还应该查看最近的博客文章(由Dave Foti撰写),讨论最新R2012a版本中的OOP性能:考虑面向对象MATLAB代码的性能
Amro

1
一个简单的代码结构敏感性示例,其中子元素方法的调用从循环中删除。for i = 1:this.get_n_quantities() if(strcmp(id,this.get_quantity_rlz(i).get_id())) ix = i; end end需要2.2秒,而nq = this.get_n_quantities(); a = this.get_quantity_realizations(); for i = 1:nq c = a{i}; if(strcmp(id,c.get_id())) ix = i; end end需要0.01 秒,需要2点魔力
Jose Ospina 2013年

Answers:


223

我使用OO MATLAB已有一段时间了,最​​终发现了类似的性能问题。

简短的答案是:是的,MATLAB的OOP有点慢。方法调用的开销很大,比主流OO语言要高,并且您无能为力。部分原因可能是惯用的MATLAB使用“矢量化”代码来减少方法调用的次数,并且每次调用的开销并不是很高的优先级。

我通过编写空操作“ nop”函数作为各种类型的函数和方法来对性能进行基准测试。这是一些典型的结果。

>> call_nops
计算机:PCWIN版本:2009b
调用每个函数/方法100000次
nop()函数:0.02261秒每次调用0.23 usc
nop1-5()函数:0.02182秒每次调用0.22 usc
nop()子函数:0.02244秒每次调用0.22 usc
@()[]匿名函数:每次通话0.08461秒0.85微秒
nop(obj)方法:0.24664秒2.47 usc每个调用
nop1-5(obj)方法:每次调用0.23469秒2.35 usc
nop()私有函数:0.02197秒每次调用0.22 usc
classdef nop(obj):每个调用0.90547秒9.05 usc
classdef obj.nop():1.75522秒17.55 usc每个调用
classdef private_nop(obj):0.84738秒8.47 usc每个调用
classdef nop(obj)(m文件):每次调用0.90560秒9.06 usc
classdef class.staticnop():每次调用1.16361秒11.64微秒
Java nop():2.43035秒每个调用24.30 usc
Java static_nop():每个调用0.87682秒8.77 usc
Java中的Java nop():0.00014秒每次调用0.00 usc
MEX mexnop():0.11409秒1.14 usc每次通话
C nop():0.00001秒每次调用0.00 usc

R2008a到R2009b的结果相似。这是在运行32位MATLAB的Windows XP x64上。

“ Java nop()”是在M代码循环内调用的虚无Java方法,并且每次调用都包括从MATLAB到Java的调度开销。“来自Java的Java nop()”是在Java for()循环中调用的同一件事,不会造成边界损失。用一点时间来了解Java和C的计时;一个聪明的编译器可以完全优化调用。

包作用域机制是新的,与classdef类几乎同时引入。它的行为可能是相关的。

一些初步的结论:

  • 方法比函数慢。
  • 新样式(classdef)方法比旧样式方法慢。
  • obj.nop()语法比语法慢nop(obj),即使对于classdef对象使用相同的方法也是如此。与Java对象相同(未显示)。如果您想快速前进,请致电nop(obj)
  • 在Windows上的64位MATLAB中,方法调用开销较高(约为2倍)。(未显示。)
  • MATLAB方法分派比某些其他语言慢。

说出为什么会这样只是我的猜测。MATLAB引擎的OO内部不是公开的。从本质上来说,这不是一个解释还是编译的问题-MATLAB有一个JIT-但MATLAB较宽松的类型和语法可能意味着在运行时需要进行更多工作。(例如,不能仅从语法上确定“ f(x)”是函数调用还是数组索引;它取决于运行时工作空间的状态。)这可能是因为MATLAB的类定义已绑定以其他许多语言所没有的方式来设置文件系统状态。

那么该怎么办?

MATLAB的一种惯用方法是通过构造类定义以使对象实例包装数组来“矢量化”代码。也就是说,其每个字段都包含并行数组(在MATLAB文档中称为“平面”组织)。而不是拥有一个对象数组,每个对象都具有保存标量值的字段,定义对象本身就是数组,并让方法将数组作为输入,并对字段和输入进行矢量化调用。这减少了方法调用的数量,希望足以使调度开销不会成为瓶颈。

在MATLAB中模仿C ++或Java类可能不是最佳选择。Java / C ++类通常是这样构建的,即对象是最小的构建块,即尽可能具体的对象(即许多不同的类),您可以将它们组成数组,集合对象等,并使用循环对其进行迭代。要制作快速的MATLAB类,请将这种方法完全颠倒。拥有更大的类,其字段为数组,并在这些数组上调用向量化方法。

关键是安排您的代码以发挥语言的优势-数组处理,向量化数学-并避免薄弱环节。

编辑:自原始发布以来,R2010b和R2011a已经问世。总体情况是一样的,MCOS调用变得更快,而Java和旧方法调用变得越来越

编辑:我以前在“路径敏感性”上有一些注释,并附有函数调用时序的附加表,其中函数时间受Matlab路径配置方式的影响,但这似乎是我的特定网络设置的异常时间。上面的图表反映了一段时间以来我的测试占优势的典型时间。

更新:R2011b

编辑(2/13/2012):R2011b退出了,性能图片已更改为足以对其进行更新。

拱门:PCWIN版本:2011b 
机器:R2011b,Windows XP,8x Core i7-2600 @ 3.40GHz,3 GB RAM,NVIDIA NVS 300
每次操作100000次
每次通话的样式总计µsec
nop()函数:0.01578 0.16
nop(),10次循环展开:0.01477 0.15
nop(),100x循环展开:0.01518 0.15
nop()子函数:0.01559 0.16
@()[]匿名函数:0.06400 0.64
nop(obj)方法:0.28482 2.85
nop()私有函数:0.01505 0.15
classdef nop(obj):0.43323 4.33
classdef obj.nop():0.81087 8.11
classdef private_nop(obj):0.32272 3.23
classdef class.staticnop():0.88959 8.90
classdef常数:1.51890 15.19
classdef属性:0.12992 1.30
使用getter的classdef属性:1.39912 13.99
+ pkg.nop()函数:0.87345 8.73
内部+ pkg + pkg.nop():0.80501 8.05
Java obj.nop():1.86378 18.64
Java nop(obj):0.22645 2.26
Java feval('nop',obj):0.52544 5.25
Java Klass.static_nop():0.35357 3.54
来自Java的Java obj.nop():0.00010 0.00
MEX mexnop():0.08709 0.87
C nop():0.00001 0.00
j()(内置):0.00251 0.03

我认为这样做的结果是:

  • MCOS / classdef方法更快。现在,只要使用foo(obj)语法,成本就与旧样式类差不多。因此,在大多数情况下,方法速度不再是坚持使用旧样式类的原因。(荣誉,MathWorks!)
  • 将函数放在名称空间中会使它们变慢。(在R2011b中不是新增功能,在我的测试中只是新增功能。)

更新:R2014a

我已经重构了基准测试代码,并在R2014a上运行了它。

在PCWIN64上的Matlab R2014a  
在PCWIN64 Windows 7 6.1(eilonwy-win7)上的Matlab 8.3.0.532(R2014a)/ Java 1.7.0_11 
机器:核心i7-3615QM CPU @ 2.30GHz,4 GB RAM(VMware Virtual Platform)
nIters = 100000 

操作时间(微秒)  
nop()函数:0.14 
nop()子函数:0.14 
@()[]匿名函数:0.69 
nop(obj)方法:3.28 
@class上的nop()private fcn:0.14 
classdef nop(obj):5.30 
classdef obj.nop():10.78 
classdef pivate_nop(obj):4.88 
classdef class.static_nop():11.81 
classdef常数:4.18 
classdef属性:1.18 
具有getter的classdef属性:19.26 
+ pkg.nop()函数:4.03 
内部+ pkg + pkg.nop():4.16 
feval('nop'):2.31 
feval(@nop):0.22 
eval('nop'):59.46 
Java obj.nop():26.07 
Java nop(obj):3.72 
Java feval('nop',obj):9.25 
Java Klass.staticNop():10.54 
Java中的Java obj.nop():0.01 
MEX mexnop():0.91 
内置j():0.02 
struct s.foo字段访问:0.14 
空(持续):0.00 

更新:R2015b:对象变得更快!

这是R2015b的结果,由@Shaked提供。这是一个很大的变化:OOP显着提高了速度,现在的obj.method()语法与一样快method(obj),并且比传统的OOP对象要快得多。

PCWIN64上的Matlab R2015b  
在PCWIN64 Windows 8 6.2上的Matlab 8.6.0.267246(R2015b)/ Java 1.7.0_60(nanit-shaked) 
机器:Core i7-4720HQ CPU @ 2.60GHz,16 GB RAM(20378)
nIters = 100000 

操作时间(微秒)  
nop()函数:0.04 
nop()子函数:0.08 
@()[]匿名函数:1.83 
nop(obj)方法:3.15 
@class上的nop()private fcn:0.04 
classdef nop(obj):0.28 
classdef obj.nop():0.31 
classdef pivate_nop(obj):0.34 
classdef class.static_nop():0.05 
classdef常数:0.25 
classdef属性:0.25 
具有getter的classdef属性:0.64 
+ pkg.nop()函数:0.04 
内部+ pkg + pkg.nop():0.04 
feval('nop'):8.26 
feval(@nop):0.63 
eval('nop'):21.22 
Java obj.nop():14.15 
Java nop(obj):2.50 
Java feval('nop',obj):10.30 
Java Klass.staticNop():24.48 
Java中的Java obj.nop():0.01 
MEX mexnop():0.33 
内置j():0.15 
struct s.foo字段访问:0.25 
isempty(持续):0.13 

更新:R2018a

这是R2018a的结果。在R2015b中引入新执行引擎时,这并不是我们看到的巨大飞跃,但与去年相比仍是可观的改进。值得注意的是,匿名函数句柄变得更快。

MACI64上的Matlab R2018a  
Matlab 9.4.0.813654(R2018a)/ MACI64 Mac OS X 10.13.5上的Java 1.8.0_144(eilonwy) 
机器:Core i7-3615QM CPU @ 2.30GHz,16 GB RAM 
nIters = 100000 

操作时间(微秒)  
nop()函数:0.03 
nop()子函数:0.04 
@()[]匿名函数:0.16 
classdef nop(obj):0.16 
classdef obj.nop():0.17 
classdef pivate_nop(obj):0.16 
classdef class.static_nop():0.03 
classdef常数:0.16 
classdef属性:0.13 
具有getter的classdef属性:0.39 
+ pkg.nop()函数:0.02 
内部+ pkg + pkg.nop():0.02 
feval('nop'):15.62 
feval(@nop):0.43 
eval('nop'):32.08 
Java obj.nop():28.77 
Java nop(obj):8.02 
Java feval('nop',obj):21.85 
Java Klass.staticNop():45.49 
Java的Java obj.nop():0.03 
MEX mexnop():3.54 
内置j():0.10 
struct s.foo字段访问:0.16 
isempty(永久):0.07 

更新:R2018b和R2019a:不变

没有重大变化。我不愿意包括测试结果。

基准的源代码

我已经将这些基准测试的源代码放在MIT许可下发布的GitHub上。https://github.com/apjanke/matlab-bench


5
@AndrewJanke您认为您可以使用R2012a再次运行基准测试吗?这真的很有趣。
党和

7
嗨伙计。如果您仍然对源代码感兴趣,我将其重构并在GitHub上开源。github.com/apjanke/matlab-bench
Andrew Janke

2
@Seeda:在这些结果中,静态方法列为“ classdef class.static_nop()”。与功能相比,它们非常慢。如果不经常打电话给他们,那没关系。
Andrew Janke


2
哇!如果这些结果成立,我可能需要修改整个答案。添加。谢谢!
Andrew Janke 2015年

3

句柄类具有额外的开销,这是为了进行清理而跟踪所有对其自身的引用。

不使用handle类尝试相同的实验,然后查看结果。


1
与String完全相同的实验,但是现在作为值类(虽然在另一台机器上);测验:0.009,测验:o.356。这与手柄的区别基本上相同,因此我认为跟踪引用不是关键的答案。它也没有解释函数与包函数之间的开销。
stijn

您使用的是哪个版本的matlab?
MikeEL,2009年

1
我在句柄类和值类之间进行了一些类似的比较,但没有注意到两者之间的性能差异。
RjOllos 2010年

我也不再注意到差异。
MikeEL 2010年

很有道理:在Matlab中,所有数组(不仅仅是句柄对象)都是引用计数的,因为它们使用写时复制和共享的基础原始数据。
安德鲁·扬克

1

OO性能在很大程度上取决于所使用的MATLAB版本。我无法评论所有版本,但从经验中知道2012a与2010年版本相比有了很大的改进。没有基准,因此没有数字可提供。我的代码完全是使用句柄类编写的,并且是在2012a下编写的,因此在早期版本中根本无法运行。


1

实际上,您的代码没有问题,但是Matlab有问题。我认为这是一种玩耍的样子。编译类代码无非是开销。我已经用简单的类点(一次作为句柄)和另一个(一次作为值类)完成了测试

    classdef Pointh < handle
    properties
       X
       Y
    end  
    methods        
        function p = Pointh (x,y)
            p.X = x;
            p.Y = y;
        end        
        function  d = dist(p,p1)
            d = (p.X - p1.X)^2 + (p.Y - p1.Y)^2 ;
        end

    end
end

这是测试

%handle points 
ph = Pointh(1,2);
ph1 = Pointh(2,3);

%values  points 
p = Pointh(1,2);
p1 = Pointh(2,3);

% vector points
pa1 = [1 2 ];
pa2 = [2 3 ];

%Structur points 
Ps.X = 1;
Ps.Y = 2;
ps1.X = 2;
ps1.Y = 3;

N = 1000000;

tic
for i =1:N
    ph.dist(ph1);
end
t1 = toc

tic
for i =1:N
    p.dist(p1);
end
t2 = toc

tic
for i =1:N
    norm(pa1-pa2)^2;
end
t3 = toc

tic
for i =1:N
    (Ps.X-ps1.X)^2+(Ps.Y-ps1.Y)^2;
end
t4 = toc

结果t1 =

12.0212%处理

t2 =

价值12.0042%

t3 =

0.5489  % vector

t4 =

0.0707 % structure 

因此,为了获得有效的性能,请避免使用OOP代替结构来分组变量

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.