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

sti*_*ijn 136 oop benchmarking matlab profiling matlab-class

我与实验MATLAB OOP,因为一开始我模仿我的C++的记录器类,我把我所有的字符串辅助函数在String类,以为这将是巨大的,能够做的事情一样a + b,a == b,a.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 );
  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' );
Run Code Online (Sandbox Code Playgroud)

结果

1000次迭代的总时间(秒):

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

atest 0.015

记录器系统的结果同样是:1000次调用时为0.1秒,frpintf( 1, 'test\n' )内部使用String类时对系统进行1000次调用时为7(!)秒(好吧,它有更多的逻辑,但要与C++进行比较:使用std::string( "blah" )std::cout输出端vs plain的系统开销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
Run Code Online (Sandbox Code Playgroud)

结果与上述方式相同:

atest 0.004秒,ctest为0.001秒

btest 0.060秒,在util.ctest中0.014秒

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

And*_*nke 218

我已经和OO MATLAB合作了一段时间,最后看到了类似的性能问题.

简短的回答是:是的,MATLAB的OOP有点慢.有大量的方法调用开销,高于主流的OO语言,并且你可以做的事情并不多.部分原因可能是惯用的MATLAB使用"向量化"代码来减少方法调用的数量,并且每次调用开销不是高优先级.

我通过编写do-nothing"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上的类似结果.这是在运行32位MATLAB的Windows XP x64上.

"Java nop()"是在M代码循环中调用的无操作Java方法,包括每次调用的MATLAB-to-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

编辑(2012年2月13日):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)语法,成本现在与旧样式类相当.因此,在大多数情况下,方法速度不再是坚持使用旧式类的理由.(Kudos,MathWorks!)
  • 将函数放在命名空间中会使它们变慢.(在R2011b中不是新的,在我的测试中是新的.)

更新:R2014a

我重新构建了基准测试代码并在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的结果,由@Shaked友情提供.这是一个很大的变化: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 

基准源代码

我已将这些基准测试的源代码放在GITHub上,并在MIT License下发布.https://github.com/apjanke/matlab-bench

  • 嗨伙计.如果您仍然对源代码感兴趣,我会重新构建它并在GitHub上开源.https://github.com/apjanke/matlab-bench (7认同)
  • @AndrewJanke你认为你可以再次使用R2012a运行基准测试吗?这真的很有趣. (5认同)
  • @Seeda:静态方法在这些结果中列为"classdef class.static_nop()".与功能相比,它们相当慢.如果不经常打电话,那没关系. (2认同)
  • @AndrewJanke这是:https://gist.github.com/ShakedDovrat/62db9e8f6883c5e28fc0 (2认同)
  • 哇!如果这些结果持续下去,我可能需要修改这整个答案.添加.谢谢! (2认同)
  • 我真的很喜欢这些更新,请继续更新! (2认同)