我与MATLAB OOP尝试,作为一个开始我模仿我的C++的记录器类和我把我所有的字符串辅助函数在String类,以为这将是巨大的,能够做的事情一样a + ba == ba.find( b )代替strcat( a b ) , strcmp( a, b ) ,检索第一个元素 strfind( a, b ) 等。
问题:减速
我使用了上述东西,并立即注意到急剧放缓。我做错了吗(这当然是可能的,因为我的 MATLAB 经验相当有限),还是 MATLAB 的 OOP 只是引入了很多开销?
我的测试用例
这是我对字符串所做的简单测试,基本上只是附加一个字符串并再次删除附加部分:

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 次迭代的总时间(以秒为单位):

记录器系统的结果同样是:1000 次调用需要 0.1 秒
frpintf( 1, 'test\n' ) ,在内部使用 String 类时,对我的系统进行 1000 次调用需要 7 (!) 秒(好吧,它有更多的逻辑,但与 C++ 相比:使用 std::string( "blah" ) 和 0x251343124 的系统的开销输出端 vs 普通 std::cout 大约为 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
结果,收集方式与上述相同:

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

最佳答案

我已经使用 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() 私有(private)函数:每次调用 0.02197 秒 0.22 微秒
classdef nop(obj):每次调用 0.90547 秒 9.05 微秒
classdef obj.nop():每次调用 1.75522 秒 17.55 微秒
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
Java nop():每次调用 2.43035 秒 24.30 微秒
Java static_nop():每次调用 0.87682 秒 8.77 微秒
Java nop() 来自 Java:每次调用 0.00014 秒 0.00 usec
MEX mexnop():每次调用 0.11409 秒 1.14 微秒
C nop(): 0.00001 sec 0.00 usec per call

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 的类定义是绑定(bind)的以许多其他语言都没有的方式进入文件系统状态。

    那么该怎么办?

    一个惯用的 MATLAB 方法是通过构造类定义来“矢量化”您的代码,以便对象实例包装一个数组;也就是说,它的每个字段都包含并行数组(在 MATLAB 文档中称为“平面”组织)。与其拥有一个对象数组,每个对象的字段都包含标量值,不如定义本身就是数组的对象,并让方法将数组作为输入,并对字段和输入进行矢量化调用。这减少了方法调用的数量,希望足够调度开销不会成为瓶颈。

    在 MATLAB 中模仿 C++ 或 Java 类可能不是最佳选择。 Java/C++ 类通常构建为对象是最小的构建块,尽可能具体(即,许多不同的类),您将它们组合成数组、集合对象等,并使用循环对它们进行迭代。要创建快速的 MATLAB 类,请彻底改变这种方法。拥有字段为数组的更大类,并在这些数组上调用向量化方法。

    重点是安排您的代码以发挥语言的优势 - 数组处理、矢量化数学 - 并避免弱点。

    编辑:自原始帖子以来,R2010b 和 R2011a 已经问世。总体情况是一样的,MCOS 调用变快了一点,Java 和旧式方法调用变慢了。

    编辑:我曾经在这里有一些关于“路径敏感性”的注释以及一个附加的函数调用时序表,其中函数时间受 Matlab 路径配置方式的影响,但这似乎是我的特定网络设置的异常时间。上面的图表反射(reflect)了我的测试随着时间的推移占优势的典型时间。

    更新:R2011b

    编辑 (2/13/2012):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() 私有(private)函数: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
    类定义常量:1.51890 15.19
    classdef 属性: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
    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
    机器: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() 私有(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
    类定义常量:4.18
    classdef 属性: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(对象):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
    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() 私有(private) fcn:0.04
    类定义 nop(obj): 0.28
    classdef obj.nop(): 0.31
    classdef pivate_nop(obj): 0.34
    classdef class.static_nop(): 0.05
    类定义常量:0.25
    classdef 属性: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
    MEX mexnop(): 0.33
    内置 j(): 0.15
    struct s.foo 字段访问:0.25
    空闲(持久):0.13

    更新:R2018a

    这是 R2018a 结果。这不是我们在 R2015b 中引入新执行引擎时看到的巨大飞跃,但它仍然是一个明显的逐年改进。值得注意的是,匿名函数句柄变得更快了。

    MACI64 上的 Matlab R2018a
    Matlab 9.4.0.813654 (R2018a)/Java 1.8.0_144 在 MACI64 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
    类定义 nop(obj): 0.16
    classdef obj.nop(): 0.17
    classdef pivate_nop(obj): 0.16
    classdef class.static_nop(): 0.03
    类定义常量:0.16
    classdef 属性: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(对象):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
    空闲(持久):0.07

    更新:R2018b 和 R2019a:无变化

    无明显变化。我不想把测试结果包括在内。

    基准测试的源代码

    我已将这些基准测试的源代码放在 GitHub 上,并在 MIT 许可下发布。 https://github.com/apjanke/matlab-bench

    关于matlab - MATLAB OOP 速度慢还是我做错了什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/1693429/

    10-16 04:19