【问题标题】:Is MATLAB OOP slow or am I doing something wrong?MATLAB OOP 是慢还是我做错了什么?
【发布时间】:2010-12-14 04:28:16
【问题描述】:

我正在尝试MATLABOOP,作为开始,我模仿了我的 C++ 的 Logger 类,我将我所有的字符串辅助函数放在一个 String 类中,我认为能够做一些事情会很棒比如a + ba == ba.find( b ) strcat( a b )strcmp( a, b )、检索strfind( a, b ) 的第一个元素等

问题:速度变慢

我使用了上面的东西,并立即注意到急剧减速。我做错了吗(这当然是可能的,因为我的 MATLAB 经验相当有限),还是 MATLAB 的 OOP 只是引入了很多开销?

我的测试用例

这是我对字符串做的简单测试,基本上只是附加一个字符串并再次删除附加的部分:

注意:不要在实际代码中编写这样的 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.SetLength 0.138,String.plus 0.065,String.Length 0.057)

测试 0.015

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

结果,收集方式同上:

在 ctest 中测试 0.004 秒,0.001 秒

btest 0.060 秒,util.ctest 中的 0.014 秒

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

【问题讨论】:

  • 感谢您提出这个问题! Matlab 堆(OOP/闭包)的性能多年来一直困扰着我,请参阅stackoverflow.com/questions/1446281/matlabs-garbage-collector。我真的很好奇 MatlabDoug/Loren/MikeKatz 会对你的帖子做出什么回应。
  • ^ 读起来很有趣。
  • @MatlabDoug:也许你的同事 Mike Karr 可以评论 OP?
  • 读者还应该查看这篇最近的博客文章(由 Dave Foti 撰写),讨论最新 R2012a 版本中的 OOP 性能:Considering Performance in Object-Oriented MATLAB Code
  • 一个简单的代码结构敏感性示例,其中子元素的方法调用被移出循环。 for i = 1:this.get_n_quantities() if(strcmp(id,this.get_quantity_rlz(i).get_id())) ix = i; end end 需要 2.2 秒,而nq = this.get_n_quantities(); a = this.get_quantity_realizations(); for i = 1:nq c = a{i}; if(strcmp(id,c.get_id())) ix = i; end end 需要 0.01,两个数量级 mag

标签: matlab oop profiling benchmarking matlab-class


【解决方案1】:

句柄类有额外的开销,因为它会跟踪对自身的所有引用以进行清理。

在不使用句柄类的情况下尝试相同的实验,看看结果如何。

【讨论】:

  • 与 String 完全相同的实验,但现在作为值类(虽然在另一台机器上); atest:0.009,btest:o.356。这与句柄的区别基本相同,所以我不认为跟踪引用是关键答案。它也没有解释函数与包中的函数的开销。
  • 你用的是什么版本的matlab?
  • 我在句柄和值类之间进行了一些类似的比较,但没有注意到两者之间的性能差异。
  • 我也不再注意到差异。
  • 有道理:在 Matlab 中,所有数组,不仅仅是句柄对象,都是引用计数的,因为它们使用写时复制和共享底层原始数据。
【解决方案2】:

我使用 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

【讨论】:

  • @AndrewJanke 你认为你可以用 R2012a 再次运行基准测试吗?这真的很有趣。
  • 大家好。如果您仍然对源代码感兴趣,我已经对其进行了重构并在 GitHub 上开源。 github.com/apjanke/matlab-bench
  • @Seeda:静态方法在这些结果中被列为“classdef class.static_nop()”。与函数相比,它们非常慢。如果他们不经常打电话,那没关系。
  • 哇!如果这些结果成立,我可能需要修改整个答案。添加。谢谢!
【解决方案3】:

OO 性能很大程度上取决于所使用的 MATLAB 版本。我不能对所有版本发表评论,但从经验中知道,2012a 比 2010 版本有了很大改进。没有基准,所以没有数字可以呈现。我的代码,专门使用句柄类编写并在 2012a 下编写,在早期版本下根本无法运行。

【讨论】:

    【解决方案4】:

    其实你的代码没有问题,但是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 
    

    因此,为了提高性能,避免使用 OOP 代替结构是对变量进行分组的好选择

    【讨论】:

      猜你喜欢
      • 2011-05-02
      • 1970-01-01
      • 2017-08-23
      • 1970-01-01
      • 1970-01-01
      • 2011-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多