我已经使用面向对象的MATLAB工作了一段时间,最终发现存在类似的性能问题。
简短回答是:是的,MATLAB的面向对象有点慢。方法调用的开销很大,比主流的面向对象语言更高,并且你不能做太多关于它的事情。部分原因可能是惯用的MATLAB使用“向量化”的代码来减少方法调用的数量,而每个调用的开销并不是一个高优先级的问题。
我通过编写“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 微秒每次调用
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 微秒每次调用
classdef private_nop(obj): 0.84738 秒 8.47 微秒每次调用
classdef nop(obj) (m-file): 0.90560 秒 9.06 微秒每次调用
classdef class.staticnop(): 1.16361 秒 11.64 微秒每次调用
Java nop(): 2.43035 秒 24.30 微秒每次调用
Java static_nop(): 0.87682 秒 8.77 微秒每次调用
Java nop() from Java: 0.00014 秒 0.00 微秒每次调用
MEX mexnop(): 0.11409 秒 1.14 微秒每次调用
C nop(): 0.00001 秒 0.00 微秒每次调用
类似的结果在R2008a到R2009b上。这是在运行32位MATLAB的Windows XP x64上。
“Java nop()”是一个什么都不做的Java方法,在M代码循环内部调用,并包括每个调用时的MATLAB到Java派遣开销。“Java nop() from Java”是相同的方法,在Java for()循环中调用,不会产生边界惩罚。对于Java和C的计时,请保持警觉;聪明的编译器可以完全优化调用掉。包作用域机制是新的机制,与classdef类大约同时引入。它的行为可能相关。
一些初步结论:
•方法比函数慢。
•新样式(classdef)方法比旧样式方法慢。
•新的 obj.nop()语法比相同方法在classdef对象上使用nop(obj)语法慢。Java对象也是如此(未显示)。如果想要快速运行,请调用nop(obj)。
•64位MATLAB在Windows上的方法调用开销更高(约为2倍)。 (未显示。)
•MATLAB方法分发比其他一些语言要慢。
为什么是这样的纯属个人猜测。MATLAB引擎的面向对象内部机制不是公开的。这不是解释和编译问题本身 - MATLAB具有JIT - 但MATLAB的宽松类型和语法可能意味着运行时需要更多的工作。(例如,仅从语法无法确定“f(x)”是函数调用还是数组索引;它取决于运行时工作区的状态。)这可能是因为MATLAB的类定义以某种与许多其他语言不同的方式与文件系统状态绑定。
那么,该怎么办?
这方面的一种典型的MATLAB方法是通过将您的类定义结构化以便对象实例包装数组来“向量化”您的代码;也就是说,它的每个字段都包含并行数组(称为“平面”组织在MATLAB文档中)。而不是具有带标量值的字段的对象数组,定义本身是数组的对象,并使方法接受数组作为输入,并对字段和输入进行向量化调用。这减少了所做的方法调用次数,希望足够减少分发开销。
在MATLAB中模仿C ++或Java类可能不是最优的。 Java / C++类通常构建为对象是最小的构建块,尽可能具体(即,有很多不同的类),并在其中使用数组、集合对象等进行组成,并使用循环对其进行迭代。要创建快速的MATLAB类,请将该方法颠倒过来。具有大型字段数组的较大类,并在这些数组上调用向量化方法。
重点是安排代码以发挥语言的优势 - 数组处理,向量化数学 - 并避免弱点。
编辑:自原始帖子发布以来,已经推出了R2010b和R2011a。总体情况相同,MCOS调用变得更快,而Java和旧样式方法调用变得更慢。
编辑:我曾在这里放置有关“路径敏感性”的一些注释,附加了一张功能调用时间表,在该时间表中,函数时间受Matlab路径配置方式的影响,但那似乎是我特定网络设置的异常情况在那个时候。上面的表反映了我多年测试中典型的时间。
更新:R2011b
编辑(2012年2月13日):R2011b已经发布,性能已经得到了足够的改善以便进行更新。
计算机信息:PCWIN,版本:2011b
设备信息:R2011b,Windows XP,8核心i7-2600 @ 3.40GHz,3 GB RAM,NVIDIA NVS 300
每个操作执行100000次
样式 总时间 每次调用消耗的µ秒数
nop()函数: 0.01578 0.16
nop()函数,10倍循环展开: 0.01477 0.15
nop()函数,100倍循环展开: 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
classdef 带getter的属性: 1.39912 13.99
+pkg.nop()函数: 0.87345 8.73
+pkg.nop()从+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()从Java中调用: 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
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11,在PCWIN64 Windows 7 6.1 (eilonwy-win7)上运行
设备: Core 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
nop()@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
classdef getter属性: 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 obj.nop()从Java中: 0.01
MEX mexnop(): 0.91
builtin j(): 0.02
结构体s.foo字段访问: 0.14
isempty(persistent): 0.00
更新:R2015b:对象变快了!
这是由@Shaked友情提供的R2015b结果。 这是一个重大的改变:面向对象编程显着更快,现在obj.method()语法与method(obj)一样快,并且比旧的面向对象程序要快得多。在PCWIN64上的Matlab R2015b
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60在PCWIN64 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() 在@class上的私有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
classdef 具有getter的属性: 0.64
+pkg.nop() 函数: 0.04
+pkg.nop() 在+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
builtin j(): 0.15
结构体s.foo字段访问: 0.25
isempty(persistent): 0.13
更新:R2018a
这是R2018a的结果。它并没有像R2015b中引入新执行引擎时那样有很大的跳跃,但仍然是一年一度的可观提升。值得注意的是,匿名函数处理速度大大提高。
在MACI64的Matlab R2018a上,
Matlab版本为9.4.0.813654(R2018a)/ Java 1.8.0_144,
操作系统为Mac OS X 10.13.5(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私有nop(obj) 0.16
classdef class.static_nop() 0.03
classdef常量 0.16
classdef属性 0.13
classdef带getter的属性 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
结构体s.foo字段访问 0.16
isempty(persistent) 0.07
更新:R2018b和R2019a:没有改变
更新:R2021a:对象速度更快了!
似乎classdef对象又变得显着更快了,但结构体变慢了。在 MACI64 上运行 Matlab R2021a
Matlab 9.10.0.1669831 (R2021a) Update 2 / Java 1.8.0_202 on MACI64 Mac OS X 10.14.6 (eilonwy)
机器: Core i7-3615QM CPU @ 2.30GHz, 4 核心, 16 GB RAM
nIters = 100000
操作 时间 (μsec)
nop() 函数: 0.03
nop() 子函数: 0.04
@()[] 匿名函数: 0.14
nop(obj) 方法: 6.65
nop() 私有函数在@class中: 0.02
classdef nop(obj): 0.03
classdef obj.nop(): 0.04
classdef pivate_nop(obj): 0.03
classdef class.static_nop(): 0.03
classdef 常量: 0.16
classdef 属性: 0.12
classdef 具有getter的属性: 0.17
+pkg.nop() 函数: 0.02
+pkg.nop() 在+pkg内部: 0.02
feval('nop'): 14.45
feval(@nop): 0.59
eval('nop'): 23.59
Java obj.nop(): 30.01
Java nop(obj): 6.80
Java feval('nop',obj): 18.17
Java Klass.staticNop(): 16.77
Java obj.nop() 来自Java: 0.02
MEX mexnop(): 2.51
builtin j(): 0.21
struct s.foo 字段访问: 0.29
isempty(persistent): 0.26
基准测试的源代码已发布在 GitHub 上,采用 MIT 许可证发布。
https://github.com/apjanke/matlab-bench
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秒,快了两个数量级。 - Jose Ospina