MATLAB OOP运行缓慢还是我做错了什么?

Is MATLAB OOP slow or am I doing something wrong?

我正在尝试使用MATLAB OOP,作为一个开始,我模仿我的C ++的Logger类,并且我将所有的字符串辅助函数放在String类中,认为能够执行像a + ba.find( b )代替
of strcat( a b )strcmp( a, b ),检索strfind( a, b )的第一个元素,等等。

问题:减速

我把上面的东西用上,并立即注意到一个急剧减速。我做错了(这当然有可能,因为我有相当有限的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次调用的时间为frpintf( 1, 'test
' )
,7(!)秒(好吧,它有更多的逻辑,但要与C ++进行比较:我的系统使用std::string("blah" )的开销输出端的std::cout与普通的std::cout <<"blah"大约为1毫秒。)

在查找类/包函数时,它只是开销吗?

由于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类几乎同时引入。它的行为可能是相关的。

一些初步结论:

  • 方法比函数慢。
  • 新样式(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已经出局,性能图片已经改变,足以更新。

    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

    我认为这样做的结果是:

  • MCOS / classdef方法更快。只要使用foo(obj)语法,成本现在与旧样式类相当。因此,在大多数情况下,方法速度不再是坚持使用旧式类的理由。 (Kudos,MathWorks!)
  • 将函数放在命名空间中会使它们变慢。 (在R2011b中不是新的,在我的测试中是新的。)
  • 更新: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明显更快,现在obj.method()语法与method(obj)一样快,并且比传统的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下编写,在早期版本下根本不会运行。