Is MATLAB OOP slow or am I doing something wrong?
我正在尝试使用MATLAB OOP,作为一个开始,我模仿我的C ++的Logger类,并且我将所有的字符串辅助函数放在String类中,认为能够执行像
of
问题:减速
我把上面的东西用上,并立即注意到一个急剧减速。我做错了(这当然有可能,因为我有相当有限的MATLAB经验),还是MATLAB的OOP只是引入了很多开销?
我的测试用例
这是我为字符串做的简单测试,基本上只是附加一个字符串并再次删除附加部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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 (with String.SetLength 0.138, String.plus 0.065, String.Length 0.057)
atest 0.015
记录器系统的结果同样为:1000次呼叫的0.1秒
在内部使用String类时,对我的系统进行1000次调用的时间为
' )
在查找类/包函数时,它只是开销吗?
由于MATLAB被解释,它必须在运行时查找函数/对象的定义。所以我想知道在查找类或包函数与路径中的函数时可能会有更多的开销。我试着测试一下,它只是变得陌生。为了排除类/对象的影响,我比较了调用路径中的函数与函数中的函数:
1 2 3 4 5 | 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 |
结果与上述方式相同:
atest 0.004 sec, 0.001 sec in ctest
btest 0.060 sec, 0.014 sec in util.ctest
那么,所有这些开销只是来自MATLAB花费时间查找它的OOP实现的定义,而这种开销不适用于直接在路径中的函数吗?
我已经和OO MATLAB合作了一段时间,最后看到了类似的性能问题。
简短的回答是:是的,MATLAB的OOP有点慢。有大量的方法调用开销,高于主流的OO语言,并且你可以做的事情并不多。部分原因可能是惯用的MATLAB使用"向量化"代码来减少方法调用的数量,并且每次调用开销不是高优先级。
我通过编写do-nothing"nop"函数作为各种类型的函数和方法来对性能进行基准测试。这是一些典型的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | >> 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类几乎同时引入。它的行为可能是相关的。
一些初步结论:
说这就是为什么这只是我的猜测。 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已经出局,性能图片已经改变,足以更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 |
我认为这样做的结果是:
更新:R2014a
我重新构建了基准测试代码并在R2014a上运行它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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明显更快,现在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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中引入新执行引擎时所看到的巨大跳跃,但它仍然是一年中可观的改善。值得注意的是,匿名函数处理速度更快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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:没有变化
没有重大变化。我不打算包括测试结果。
基准源代码
我已将这些基准测试的源代码放在GITHub上,并在MIT License下发布。 https://github.com/apjanke/matlab-bench
好。
句柄类有一个额外的开销,即跟踪所有对自身的引用以进行清理。
不使用句柄类尝试相同的实验,看看你的结果是什么。
实际上你的代码没有问题,但这是Matlab的一个问题。我认为这是一种玩耍的样子。编译类代码不过是开销。
我用简单的类点(一次作为句柄)和另一个(一次作为值类)完成了测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
这是测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | %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 =
1 | 0.5489 % vector |
t4 =
1 | 0.0707 % structure |
因此,为了有效的性能避免使用OOP而不是结构是组变量的好选择
OO性能在很大程度上取决于所使用的MATLAB版本。我不能对所有版本发表评论,但从经验中可以看出,2012a比2010版本有了很大改进。没有基准,所以没有数字呈现。我的代码,使用句柄类编写并在2012a下编写,在早期版本下根本不会运行。