我使用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