我使用 OO MATLAB 已经有一段时间了,最终看到了类似的性能问题。
简短的回答是:是的,MATLAB 的 OOP 有点慢。方法调用开销很大,高于主流的 OO 语言,而且您无能为力。部分原因可能是惯用的 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 文件):每次调用 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 中的 Java nop():每次调用 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()”是从 M 代码循环中调用的无操作 Java 方法,并且每次调用都包含 MATLAB 到 Java 的调度开销。“Java nop() from Java”与 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 发布:2011b
机器:R2011b,Windows XP,8x Core i7-2600 @ 3.40GHz,3 GB RAM,NVIDIA NVS 300
每次操作 100000 次
每次调用的样式总微秒
nop() 函数:0.01578 0.16
nop(),10x 循环展开: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
类定义 nop(obj):0.43323 4.33
类定义 obj.nop():0.81087 8.11
classdef private_nop(obj):0.32272 3.23
类定义类.staticnop():0.88959 8.90
类定义常量:1.51890 15.19
类定义属性:0.12992 1.30
带有 getter 的 classdef 属性: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 的 Java obj.nop():0.00010 0.00
墨西哥 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 虚拟平台)
nIters = 100000
操作时间(微秒)
nop() 函数:0.14
nop() 子函数:0.14
@()[] 匿名函数:0.69
nop(obj) 方法:3.28
@class 上的 nop() 私有 fcn:0.14
类定义 nop(obj):5.30
类定义 obj.nop():10.78
类定义 pivate_nop(obj): 4.88
类定义类.static_nop():11.81
类定义常量:4.18
类定义属性:1.18
带有 getter 的 classdef 属性:19.26
+pkg.nop() 函数:4.03
+pkg.nop() 从内部 +pkg: 4.16
feval('nop'):2.31
feval(@nop):0.22
评估('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
墨西哥 mexnop(): 0.91
内置 j(): 0.02
struct s.foo 字段访问:0.14
isempty(持久):0.00
更新:R2015b:对象变得更快!
这是由@Shaked 提供的 R2015b 结果。这是一个很大的变化:OOP 明显更快,现在obj.method()
语法与 . 一样快method(obj)
,并且比传统的 OOP 对象快得多。
PCWIN64 上的 Matlab R2015b
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 on 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
@class 上的 nop() 私有 fcn:0.04
类定义 nop(obj):0.28
类定义 obj.nop(): 0.31
类定义 pivate_nop(obj): 0.34
classdef class.static_nop(): 0.05
类定义常量:0.25
类定义属性:0.25
带有 getter 的 classdef 属性:0.64
+pkg.nop() 函数:0.04
+pkg.nop() 从内部 +pkg: 0.04
feval('nop'):8.26
feval(@nop):0.63
评估('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
墨西哥 mexnop(): 0.33
内置 j(): 0.15
struct s.foo 字段访问:0.25
isempty(持久):0.13
更新:R2018a
这是 R2018a 结果。这不是我们在 R2015b 中引入新执行引擎时看到的巨大飞跃,但仍是可观的逐年改进。值得注意的是,匿名函数句柄变得更快了。
MACI64 上的 Matlab R2018a
MACI64 Mac OS X 10.13.5 (eilonwy) 上的 Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144
机器:Core i7-3615QM CPU @ 2.30GHz,16 GB RAM
nIters = 100000
操作时间(微秒)
nop() 函数:0.03
nop() 子函数:0.04
@()[] 匿名函数:0.16
类定义 nop(obj):0.16
类定义 obj.nop():0.17
类定义 pivate_nop(obj): 0.16
classdef class.static_nop(): 0.03
类定义常量:0.16
类定义属性:0.13
带有 getter 的 classdef 属性:0.39
+pkg.nop() 函数:0.02
+pkg.nop() 从内部 +pkg: 0.02
feval('nop'):15.62
feval(@nop):0.43
评估('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() 来自 Java:0.03
墨西哥 mexnop(): 3.54
内置 j(): 0.10
struct s.foo 字段访问:0.16
isempty(持久):0.07
更新:R2018b 和 R2019a:没有变化
没有显着变化。我不打扰包括测试结果。
更新:R2021a:更快的物体!
看起来 classdef 对象又变得更快了。但是结构变得更慢了。
MACI64 上的 Matlab R2021a
MACI64 Mac OS X 10.14.6 (eilonwy) 上的 Matlab 9.10.0.1669831 (R2021a) 更新 2 / Java 1.8.0_202
机器:Core i7-3615QM CPU @ 2.30GHz,4 核,16 GB RAM
nIters = 100000
操作时间(微秒)
nop() 函数:0.03
nop() 子函数:0.04
@()[] 匿名函数:0.14
nop(obj) 方法:6.65
@class 上的 nop() 私有 fcn:0.02
类定义 nop(obj):0.03
类定义 obj.nop(): 0.04
类定义 pivate_nop(obj): 0.03
classdef class.static_nop(): 0.03
类定义常量:0.16
类定义属性:0.12
带有 getter 的 classdef 属性:0.17
+pkg.nop() 函数:0.02
+pkg.nop() 从内部 +pkg: 0.02
feval('nop'):14.45
feval(@nop):0.59
评估('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
墨西哥 mexnop(): 2.51
内置 j(): 0.21
struct s.foo 字段访问:0.29
isempty(持久):0.26
基准测试源代码
我已将这些基准测试的源代码放在 GitHub 上,根据 MIT 许可证发布。https://github.com/apjanke/matlab-bench