为了账号安全,请及时绑定邮箱和手机立即绑定

MATLABOOP是慢的还是我做错了什么?

/ 猿问

MATLABOOP是慢的还是我做错了什么?

慕桂英546537 2019-07-01 16:16:52

MATLABOOP是慢的还是我做错了什么?

我在试验MATLAB OOP,作为一个开始,我模仿了我的C+的Logger类,我把我所有的字符串助手函数都放在一个String类中,认为能够这样做是很棒的。a + ba == ba.find( b )而不是strcat( a b )strcmp( a, b )的第一个元素。strfind( a, b )

问题:放缓

我把上面的东西放了起来,立即注意到激烈减速。我是做错了(这当然是可能的,因为我有相当有限的MATLAB经验),还是MATLAB的OOP只是引入了大量的开销?

我的测试用例

下面是我对字符串所做的简单测试,基本上只是追加一个字符串并再次删除附加的部分:

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 );
  endendfunction 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.etLength 0.138,String.plus 0.065,String.Length 0.057)

顶楼0.015

记录器系统的结果同样是:对于1000次调用,结果为0.1秒frpintf( 1, 'test\n' ),7(!)在内部使用String类时对我的系统的1000个调用的秒(好的,它有更多的逻辑,但是与C+相比:我的系统的开销std::string( "blah" )std::cout在输出端对平原std::cout << "blah"是1毫秒左右。)

在查找类/包函数时,它仅仅是开销吗?

由于MATLAB是被解释的,所以它必须在运行时查找函数/对象的定义。因此,我想知道,在查找类或包函数与路径中的函数时,可能涉及到更多的开销。我试过测试这个,结果变得更奇怪了。为了排除类/对象的影响,我将在路径中调用函数与包中的函数进行比较:

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

所得结果与上述相同:

每秒0.004秒,0.001秒

0.060秒,0.014秒

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


查看完整描述

3 回答

?
ibeautiful

我已经和OOMATLAB一起工作了一段时间,最后我发现了类似的性能问题。

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

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

>> call_nops
Computer: PCWIN   Release: 2009b
Calling each function/method 100000 times
nop() function:                 0.02261 sec   0.23 usec per call
nop1-5() functions:             0.02182 sec   0.22 usec per call
nop() subfunction:              0.02244 sec   0.22 usec per call
@()[] anonymous function:       0.08461 sec   0.85 usec per call
nop(obj) method:                0.24664 sec   2.47 usec per call
nop1-5(obj) methods:            0.23469 sec   2.35 usec per call
nop() private function:         0.02197 sec   0.22 usec per call
classdef nop(obj):              0.90547 sec   9.05 usec per call
classdef obj.nop():             1.75522 sec  17.55 usec per call
classdef private_nop(obj):      0.84738 sec   8.47 usec per call
classdef nop(obj) (m-file):     0.90560 sec   9.06 usec per call
classdef class.staticnop():     1.16361 sec  11.64 usec per call
Java nop():                     2.43035 sec  24.30 usec per call
Java static_nop():              0.87682 sec   8.77 usec per call
Java nop() from Java:           0.00014 sec   0.00 usec per call
MEX mexnop():                   0.11409 sec   1.14 usec per call
C nop():                        0.00001 sec   0.00 usec per call

在R2008a到R2009b上也有类似的结果。这是在WindowsXPx64上运行32位MATLAB.

“JavaNOP()”是一个从M代码循环中调用的不需要任何操作的Java方法,它包括每个调用的MATLAB到Java分派开销。“Java NOP()from Java”是Java for()循环中调用的相同内容,不会造成边界损失。以Java和C时间为例;聪明的编译器可以完全优化调用。

包的作用域机制是新的,它是在类def类的同时引入的。它的行为可能与此有关。

以下是一些初步结论:

  • 方法比函数慢。
  • 新样式(Classdef)方法比旧样式方法慢。
  • obj.nop()

    语法比

    nop(obj)

    语法,甚至对于类def对象上的相同方法也是如此。Java对象也是如此(未显示)。如果你想快点,打电话

    nop(obj).

  • 在Windows环境下,64位MATLAB中的方法调用开销较高(约为2x).(未显示)
  • MATLAB方法调度速度比其他语言慢。

说出为什么会这样只是我的猜测。MATLAB引擎的OO内部不是公开的。这不是一个解释与编译问题本身-MATLAB有一个JIT-但MATLAB的松散类型和语法可能意味着更多的工作在运行时。(例如,不能仅从语法上判断“f(X)”是函数调用还是数组中的索引;它取决于运行时工作区的状态。)这可能是因为MATLAB的类定义与文件系统状态联系在一起,而许多其他语言的情况并非如此。

那么,该怎么办呢?

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

在MATLAB中模仿C+或Java类可能不是最优的。Java/C+类通常是这样构建的,即对象是最小的构建块,尽可能具体(即,许多不同的类),然后将它们组合在数组、集合对象等中,并使用循环对它们进行迭代。要快速创建MATLAB类,请将该方法内翻。拥有更大的类,其字段为数组,并在这些数组上调用向量化方法。

重点是安排您的代码发挥语言的优势-数组处理,矢量化的数学-并避免弱点。

编辑:自从最初的帖子,R2010b和R2011a已经出来。总体情况是一样的,mcos调用变得更快了,而java和旧风格的方法调用变得越来越快。慢点.

编辑:我在这里有一些关于“路径敏感性”的注释,还有一个额外的函数调用时间表,其中函数时间受Matlab路径配置方式的影响,但这似乎是我当时特定网络设置的一个异常。上面的图表反映了典型的时间优势的我的测试随着时间的推移。

最新情况:R2011 b

编辑(2/13/2012):R2011b已退出,性能图片已更改到足以更新此功能。

Arch: PCWIN   Release: 2011b 
Machine: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB RAM, NVIDIA NVS 300
Doing each operation 100000 times
style                           total       µsec per call
nop() function:                 0.01578      0.16
nop(), 10x loop unroll:         0.01477      0.15
nop(), 100x loop unroll:        0.01518      0.15
nop() subfunction:              0.01559      0.16
@()[] anonymous function:       0.06400      0.64
nop(obj) method:                0.28482      2.85
nop() private function:         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 constant:              1.51890     15.19
classdef property:              0.12992      1.30
classdef property with getter:  1.39912     13.99
+pkg.nop() function:            0.87345      8.73
+pkg.nop() from inside +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() from Java:       0.00010      0.00
MEX mexnop():                   0.08709      0.87
C nop():                        0.00001      0.00
j() (builtin):                  0.00251      0.03

我认为这样做的结果是:

  • MCoS/classdef方法更快。只要您使用

    foo(obj)

    语法。因此,在大多数情况下,方法速度不再是坚持旧风格类的理由。(荣誉,MathWorks!)
  • 将函数放入命名空间会使它们变慢。(R2011b不是新的,只是我的测试中的新内容。)

最新情况:R2014 a

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

Matlab R2014a on PCWIN64  
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 on PCWIN64 Windows 7 6.1 (eilonwy-win7) 
Machine: Core i7-3615QM CPU @ 2.30GHz, 4 GB RAM (VMware Virtual Platform)
nIters = 100000 

Operation                        Time (µsec)  
nop() function:                         0.14 
nop() subfunction:                      0.14 
@()[] anonymous function:               0.69 
nop(obj) method:                        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 constant:                      4.18 
classdef property:                      1.18 
classdef property with getter:         19.26 
+pkg.nop() function:                    4.03 
+pkg.nop() from inside +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 obj.nop() from Java:               0.01 
MEX mexnop():                           0.91 
builtin j():                            0.02 
struct s.foo field access:              0.14 
isempty(persistent):                    0.00

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

这是R2015b的结果,由@Shaed提供。这是一个大的变更:OOP的速度要快得多,现在obj.method()语法和method(obj),而且比遗留的OOP对象快得多。

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

Operation                        Time (µsec)  
nop() function:                         0.04 
nop() subfunction:                      0.08 
@()[] anonymous function:               1.83 
nop(obj) method:                        3.15 
nop() private 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 constant:                      0.25 
classdef property:                      0.25 
classdef property with getter:          0.64 
+pkg.nop() function:                    0.04 
+pkg.nop() from inside +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 obj.nop() from Java:               0.01 
MEX mexnop():                           0.33 
builtin j():                            0.15 
struct s.foo field access:              0.25 
isempty(persistent):                    0.13

最新情况:R2018a

这是R2018a的结果。这并不是我们在R2015b中引入新的执行引擎时看到的巨大飞跃,但它仍然是一个值得注意的年度改进。值得注意的是,匿名函数句柄变得更快了。

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

Operation                        Time (µsec)  
nop() function:                         0.03 
nop() subfunction:                      0.04 
@()[] anonymous function:               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 constant:                      0.16 
classdef property:                      0.13 
classdef property with getter:          0.39 
+pkg.nop() function:                    0.02 
+pkg.nop() from inside +pkg:            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 obj.nop() from Java:               0.03 
MEX mexnop():                           3.54 
builtin j():                            0.10 
struct s.foo field access:              0.16 
isempty(persistent):                    0.07

更新:R2018b和R2019a:无变动

没有重大变化。我不想把测试结果包括进来。

基准源代码

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


查看完整回答
反对 回复 2019-07-01
?
慕森卡

Handle类在为清理目的跟踪对自身的所有引用时有额外的开销。

在没有使用句柄类的情况下,尝试相同的实验,看看结果如何。


查看完整回答
反对 回复 2019-07-01
?
万千封印

OO性能在很大程度上取决于所使用的MATLAB版本。我不能评论所有版本,但根据经验,2012 a比2010年版本有了很大改进。没有基准,所以没有数字提供。我的代码,只使用句柄类编写,在2012 a下编写,在早期版本中根本不会运行。


查看完整回答
反对 回复 2019-07-01

添加回答

回复

举报

0/150
提交
取消
意见反馈 邀请有奖 帮助中心 APP下载
官方微信