MATLAB 面向对象程序运行缓慢还是我做错了什么?

我正在尝试使用 MATLAB ,作为一个开始,我模仿了 C + + 的 Logger 类,并将所有的字符串助手函数放在一个 String 类中,我认为能够做像 a + ba == ba.find( b )这样的事情会很棒 strcat( a b )strcmp( a, b ),检索 strfind( a, b )的第一个元素等。

问题是: 经济放缓

我把以上的东西使用,并立即注意到一个 激烈减速。是我做错了(这当然是可能的,因为我有相当有限的 MATLAB 经验) ,还是 MATLAB 的面向对象程序只是引入了很多开销?

我的测试案例

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

注意: 不要在实际代码中编写这样的 String 类!Matlab 现在有一个本地 string数组类型,您应该改用它。

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' );

结果

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

Btest 0.550(使用 String. SetLlength 0.138,String.plus 0.065,String. Llength 0.057)

最高0.015

Logger 系统的结果也是类似的: 1000次调用的结果是0.1秒 到 frpintf( 1, 'test\n' ),7(!)当在内部使用 String 类时,1000次调用需要花费1毫秒的时间(好吧,它有更多的逻辑,但是与 C + + 相比: 在输出端使用 std::string( "blah" )std::cout的系统的开销与纯 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

结果,收集方式与上述相同:

至少0.004秒,0.001秒

Btest 在 util.ctest 中为0.060秒,0.014秒

那么,所有这些开销是否都来自 MATLAB 花费时间查找其面向对象程序实现的定义,而对于直接在路径中的函数则不存在这些开销呢?

29860 次浏览

为了清理目的,句柄类跟踪所有对其自身的引用,从而产生额外的开销。

尝试相同的实验而不使用句柄类,看看你的结果是什么。

我已经使用 OO MATLAB 工作了一段时间,最终看到了类似的性能问题。

简短的回答是: 是的,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 上,这是在运行32位 MATLAB 的 WindowsXPx64上。

“ Javanop ()”是从 M 代码循环中调用的一个不做任何事情的 Java 方法,包括每次调用时 MATLAB 到 Java 的调度开销。“ Javanop () from Java”与 Javafor ()循环中调用的是同一个东西,并且不会产生边界损失。对 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 已经出来了,而且性能状况已经发生了足够的改变,可以更新它了。

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)语法,开销与旧样式类相当。因此,在大多数情况下,方法速度不再是坚持使用旧样式类的理由。(荣誉,数学工作室!)
  • 将函数放在名称空间中会使它们变慢(在 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: 对象变得更快了!

这是由@Shaked 提供的 R2015b 结果。这是一个 很大变化: 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: 没有变化

没有明显的变化,我不想把测试结果也包括进去。

更新: R2021a: 更快的对象!

看起来 classdef 对象又变得明显更快了,但是结构变得更慢了。

Matlab R2021a on MACI64
Matlab 9.10.0.1669831 (R2021a) Update 2 / Java 1.8.0_202 on MACI64 Mac OS X 10.14.6 (eilonwy)
Machine: Core i7-3615QM CPU @ 2.30GHz, 4 cores, 16 GB RAM
nIters = 100000


Operation                        Time (μsec)
nop() function:                         0.03
nop() subfunction:                      0.04
@()[] anonymous function:               0.14
nop(obj) method:                        6.65
nop() private fcn on @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 constant:                      0.16
classdef property:                      0.12
classdef property with getter:          0.17
+pkg.nop() function:                    0.02
+pkg.nop() from inside +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() from Java:               0.02
MEX mexnop():                           2.51
builtin j():                            0.21
struct s.foo field access:              0.29
isempty(persistent):                    0.26

基准的源代码

我已经把这些基准测试的源代码放在 GitHub 上,它是根据 MIT 许可证发布的

面向对象的性能在很大程度上取决于所使用的 MATLAB 版本。我不能评论所有的版本,但从经验中知道,2012a 比2010版本有了很大的改进。没有基准,因此没有数字可以呈现。我的代码,完全使用句柄类编写并且是在2012a 下编写的,在早期版本下根本不会运行。

实际上你的代码没有问题,但是 Matlab 有问题。我认为在它里面有一种玩耍的样子。编译类代码只是开销而已。 我用简单的类点(一个作为句柄)和另一个(一个作为值类)完成了测试

    classdef Pointh < handle
properties
X
Y
end
methods
function p = Pointh (x,y)
p.X = x;
p.Y = y;
end
function  d = dist(p,p1)
d = (p.X - p1.X)^2 + (p.Y - p1.Y)^2 ;
end


end
end

测试是这样的

%handle points
ph = Pointh(1,2);
ph1 = Pointh(2,3);


%values  points
p = Pointh(1,2);
p1 = Pointh(2,3);


% vector points
pa1 = [1 2 ];
pa2 = [2 3 ];


%Structur points
Ps.X = 1;
Ps.Y = 2;
ps1.X = 2;
ps1.Y = 3;


N = 1000000;


tic
for i =1:N
ph.dist(ph1);
end
t1 = toc


tic
for i =1:N
p.dist(p1);
end
t2 = toc


tic
for i =1:N
norm(pa1-pa2)^2;
end
t3 = toc


tic
for i =1:N
(Ps.X-ps1.X)^2+(Ps.Y-ps1.Y)^2;
end
t4 = toc

结果 T1 =

12.0212% 句柄

T2 =

12.0042% 的价值

T3 =

0.5489  % vector

T4 =

0.0707 % structure

因此,为了提高性能,避免使用面向对象的结构来分组变量是一个不错的选择