MATLAB的OOP速度慢还是我做错了什么?
我正在做MATLAB OOP的试验,作为一个开始,我模仿了我的C ++的Logger类,我将所有的string帮助器函数都放在一个String类中,认为能够做一些事情是很棒的,比如a + b
, a == b
, a.find( b )
而不是strcat( ab )
, strcmp( a, b )
,检索strfind( a, b )
第一个元素strfind( a, b )
等。
问题:放缓
我把上面的东西都用上了,马上注意到一个急剧的放缓。 我做错了(这当然是可能的,因为我有相当有限的MATLAB经验),还是MATLAB的OOP只是引入了大量的开销?
我的testing案例
这里是我为string做的简单的testing,基本上只是附加一个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 = [ ab ]; 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)
atest 0.015
logging器系统的结果同样如此:在1000次调用frpintf( 1, 'test\n' )
为0.1秒,在内部使用String类时调用我的系统1000次(7秒)(确定,它有更多逻辑,但是与C ++比较:我的系统在输出端使用std::string( "blah" )
和std::cout
的开销与普通std::cout << "blah"
是一样的1毫秒)。
查找类/包函数时是否只是开销?
由于MATLAB被解释了,它必须在运行时查找函数/对象的定义。 所以我想知道,查找类或包函数与path中的函数可能涉及更多的开销。 我试图testing这个,只是变得陌生。 为了排除类/对象的影响,我比较了在path中调用函数与包中函数的关系:
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
结果,收集了与上面相同的方式:
testing0.004秒,ctest为0.001秒
btest 0.060秒,在util.ctest中0.014秒
所以,这只是MATLAB花费时间来查找OOP实现的定义的开销,而这个开销并不是直接在path中的函数?
我一直在使用OO MATLAB一段时间,最后看到类似的性能问题。
简单的回答是:是的,MATLAB的面向对象是一种缓慢。 有相当大的方法调用开销,高于主stream的OO语言,并且你可以做的事情不多。 部分原因可能是惯用的MATLAB使用“向量化”代码来减less方法调用的次数,并且每个通话开销并不是高优先级。
我通过写作什么都不做的“nop”函数作为各种函数和方法的基准。 以下是一些典型的结果。
>> call_nops 计算机:PCWIN发布:2009b 调用每个函数/方法100000次 nop()函数:0.02261秒每次调用0.23次 nop1-5()函数:0.02182秒每次调用0.22次 nop()子函数:0.02244秒每次调用0.22次 @()[]匿名函数:0.08461秒每次调用0.85次 nop(obj)方法:0.24664秒2.47每次调用usec nop1-5(obj)方法:0.23469秒每次调用2.35次 nop()私有函数:0.02197秒每次调用0.22次 classdef nop(obj):0.90547秒每次调用9.05次 classdef obj.nop():1.75522秒17.55每次调用usec classdef private_nop(obj):0.84738 sec 8.47每次调用usec classdef nop(obj)(m文件):0.90560秒每次调用9.06次 classdef class.staticnop():1.16361 sec 11.64每次调用usec Java nop():2.43035秒每次调用24.30次 Java static_nop():0.87682秒8.77每次调用的usec 来自Java的Java nop():0.00014秒每次调用0.00次 墨西哥mexnop():0.11409秒每次通话1.14美元 C nop():0.00001秒每次调用0.00次
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内部结构是不公开的。 这本身不是一个解释的vs编译的问题 – MATLAB有一个JIT–但是MATLAB的宽松input和语法可能意味着更多的运行时间。 (例如,从句法本身来看,“f(x)”是一个函数调用还是一个数组中的索引,它在运行时依赖于工作空间的状态)。这可能是因为MATLAB的类定义是绑定的到许多其他语言的方式不是文件系统状态。
那么,该怎么办?
一个惯用的MATLAB方法就是通过构造你的类定义来“vector化”你的代码,使得一个对象实例包装一个数组; 也就是说,它的每个字段都保持平行的数组(在MATLAB文档中称为“平面”组织)。 而不是有一个对象数组,每个对象都有标量值的字段,定义它们本身是数组的对象,并让这些方法将数组作为input,并对字段和input进行vector化调用。 这减less了方法调用的次数,希望足以使调度开销不是瓶颈。
在MATLAB中模仿C ++或Java类可能不是最佳的。 Java / C ++类通常被构build为使得对象是最小的构build块(尽可能具体)(也就是,许多不同的类),然后将它们组合到数组,集合对象等中,并用循环遍历它们。 要做出快速的MATLAB类,把这个方法从里面翻出来。 有更大的类,其字段是数组,并在这些数组上调用vector化方法。
关键是要安排你的代码发挥语言的优势 – 数组处理,vectormath – 避免弱点。
编辑:由于原来的职位,R2010b和R2011a已经出来。 整体情况是一样的,MCOS调用速度更快,而Java和老式的方法调用越来越慢 。
编辑:我曾经有一些笔记这里的“path灵敏度”与一个额外的函数调用时间表,function时间受到如何configurationMatlabpath的影响,但似乎是我的特定networking设置的像差时间。 上面的图表反映了我的testing随着时间的推移典型的时间。
更新:R2011b
编辑(2/13/2012):R2011b出来了,而且performance图片已经改变了足以更新这个。
Arch:PCWIN发布:2011b 机器:R2011b,Windows XP,8x Core i7-2600 @ 3.40GHz,3 GB RAM,NVIDIA NVS 300 每次操作10万次 每通电话总共为μ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.nop()from + pkg: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的obj.nop():0.00010 0.00 MEX mexnop():0.08709 0.87 C nop():0.00001 0.00 j()(内build值):0.00251 0.03
我认为这个结果是:
- MCOS / classdef方法更快。 现在,只要使用
foo(obj)
语法,成本就可以与旧式类相提并论。 所以方法速度不再是在大多数情况下坚持使用旧式类的理由。 (荣誉,MathWorks!) - 将函数放入命名空间会使它们变慢。 (在R2011b中不是新的,在我的testing中只是新的。)
更新:R2014a
我重build了基准代码并在R2014a上运行。
PCWIN64上的Matlab R2014a PCWIN64上的Matlab 8.3.0.532(R2014a)/ Java 1.7.0_11 Windows 7 6.1(eilonwy-win7) 机器:Core i7-3615QM CPU @ 2.30GHz,4GB内存(VMware Virtual Platform) nIters = 100000 操作时间(微秒) nop()函数:0.14 nop()子函数:0.14 @()[]匿名函数:0.69 nop(obj)方法:3.28 nop()private @ fcn on @class: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.nop()from + pkg: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 内buildj():0.02 结构s.foo字段访问:0.14 isempty(持久性):0.00
更新:R2015b:物体变快了!
这是R1515b的结果,由@Shaked友情提供。 这是一个很大的变化:OOP速度明显加快,现在obj.method()
语法和method(obj)
一样快,比传统的OOP对象快得多。
PCWIN64上的Matlab R2015b Matlab 8.6.0.267246(R2015b)/ PCWIN64上的Java 1.7.0_60 Windows 8 6.2(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 nop()私人fcn on @class: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 classdef属性与getter:0.64 + pkg.nop()函数:0.04 + pkg.nop()from + pkg: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 结构s.foo字段访问:0.25 isempty(持续):0.13
“基准源代码”
我已经把这些基准testing的源代码放在GitHub上,在MIT许可下发布。 https://github.com/apjanke/matlab-bench
处理类有一个额外的开销,跟踪所有的引用自身的清理目的。
尝试不使用句柄类的相同实验,看看你的结果是什么。
OO的性能在很大程度上取决于所使用的MATLAB版本。 我不能评论所有版本,但从经验中知道2012a比2010版本有了很大的改进。 没有基准,所以没有数字来呈现。 我的代码,完全使用句柄类编写,并在2012a下编写,根本不会在早期版本下运行。
其实你的代码没有问题,但它是一个与Matlab的问题。 我觉得这是一种玩耍的样子。 编译类代码只是开销。 我已经做了简单的类的testing(一次作为处理)和其他(一次作为价值课)
classdef Pointh < handle properties X Y end methods function p = Pointh (x,y) pX = x; pY = y; end function d = dist(p,p1) d = (pX - p1.X)^2 + (pY - p1.Y)^2 ; end end end
这里是testing
%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代替结构是组variables的好select