MATLAB OOPは遅いですか、それとも何か問題がありますか?


144

私が試してるMATLAB OOP私はC ++のロガークラスを模倣し始めると、私はのようなものを行うことができることは素晴らしいことだろうと考え、Stringクラスにすべての私の文字列ヘルパー関数を入れているa + ba == ba.find( b )代わりにstrcat( a b )strcmp( a, b )strfind( a, b )などの最初の要素を取得します。

問題:減速

私は上記のものを使用して、急激な減速にすぐに気づきました。私はそれを間違っていますか?

私のテストケース

これは私が文字列に対して行った簡単なテストです。基本的には文字列を追加し、追加された部分をもう一度削除します。

注:実際のコードでは、このような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

ロガーシステムの結果も同様frpintf( 1, 'test\n' )です。内部でStringクラスを使用している場合、システムへの1000回の呼び出しで0.1秒、システムへの1000回の呼び出しで7秒(!)秒(OK、それにはより多くのロジックがありますが、C ++と比較すると:使用している私のシステムのオーバーヘッドstd::string( "blah" )std::cout無地VS出力側の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

上記と同じ方法で収集された結果:

atest 0.004秒、ctestで0.001秒

util.ctestのbtest 0.060秒、0.014秒

それで、このオーバーヘッドはすべて、MATLABがOOP実装の定義の検索に時間を費やしていることに起因していますが、このオーバーヘッドは、直接パスにある関数にはありませんか?


5
この質問をありがとう!Matlabヒープ(OOP / closures)のパフォーマンスが何年もの間私を悩ませてきました。stackoverflow.com/questions/1446281/ matlabs-garbage-collectorを参照してください。MatlabDoug / Loren / MikeKatzがあなたの投稿にどのように反応するか本当に知りたいです。
ミハイル、

1
^興味深い読み物でした。
stijn 2009年

1
@MatlabDoug:同僚のMike KarrがOPにコメントできますか?
ミハイル、

4
読者はまた、最新のR2012aバージョンでOOPのパフォーマンスを議論(デイブFOTIによる)この最近のブログ記事をチェックする必要があります:オブジェクト指向MATLABコードでのパフォーマンスを考えると
アムロ

1
サブエレメントのメソッドの呼び出しがループから取り出されるコード構造の機密性の簡単な例。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 endMAG 0.01、二桁をとり
ホセOspina

回答:


223

私はしばらくOO MATLABを使用しており、同様のパフォーマンスの問題を調べていました。

簡単に言えば、そうです。MATLABのOOPは少し遅いです。メソッドコールのオーバーヘッドはかなり大きく、主流のOO言語よりも高く、それに対してできることはあまりありません。理由の一部は、慣用的なMATLABが「ベクトル化された」コードを使用してメソッド呼び出しの数を減らし、呼び出しごとのオーバーヘッドが高い優先順位ではないためです。

何もしない「nop」関数をさまざまなタイプの関数およびメソッドとして記述することで、パフォーマンスのベンチマークを行いました。ここにいくつかの典型的な結果があります。

>> call_nops
コンピューター:PCWINリリース:2009b
各関数/メソッドを100000回呼び出す
nop()関数:呼び出しごとに0.02261秒0.23 usec
nop1-5()関数:呼び出しごとに0.02182秒0.22 usec
nop()サブ関数:呼び出しごとに0.02244秒0.22 usec
@()[]匿名関数:呼び出しごとに0.08461秒0.85 usec
nop(obj)メソッド:0.24664秒2.47呼び出しごとのusec
nop1-5(obj)メソッド:呼び出しごとに0.23469秒2.35 usec
nop()プライベート関数:呼び出しごとに0.02197秒0.22 usec
classdef nop(obj):呼び出しあたり0.90547秒9.05 usec
classdef obj.nop():1.75522秒17.55コールごとのusec
classdef private_nop(obj):呼び出しあたり0.84738秒8.47 usec
classdef nop(obj)(m-file):呼び出しあたり0.90560秒9.06 usec
classdef class.staticnop():呼び出しごとに1.16361秒11.64 usec
Java nop():2.43035秒24.30呼び出しごとのusec
Java static_nop():呼び出しあたり0.87682秒8.77 usec
JavaからのJava nop():呼び出しごとに0.00014秒0.00 usec
MEX mexnop():呼び出しあたり0.11409秒1.14 usec
C nop():呼び出しごとに0.00001秒0.00 usec

R2008aからR2009bまでの同様の結果。これは、32ビットのMATLABを実行しているWindows XP x64上にあります。

"Java nop()"は、Mコードループ内から呼び出される何もしないJavaメソッドであり、呼び出しごとにMATLABからJavaへのディスパッチオーバーヘッドが含まれています。「Java nop()from Java」は、Java for()ループで呼び出されるものと同じものであり、その境界ペナルティは発生しません。JavaとCのタイミングを詳細に検討してください。賢いコンパイラーは、呼び出しを完全に最適化することができます。

パッケージスコーピングメカニズムは新しく、classdefクラスとほぼ同時に導入されました。その動作は関連している可能性があります。

いくつかの暫定的な結論:

  • メソッドは関数よりも低速です。
  • 新しいスタイル(classdef)メソッドは、古いスタイルのメソッドよりも低速です。
  • classdefオブジェクトの同じメソッドであってobj.nop()も、新しい構文は構文よりも低速ですnop(obj)。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がリリースされ、パフォーマンスの図はこれを更新するために十分に変更されました。

アーチ:PCWINリリース:2011b 
マシン:R2011b、Windows XP、8x Core i7-2600 @ 3.40GHz、3 GB RAM、NVIDIA NVS 300
各操作を10万回行う
呼び出しごとのスタイル合計µsec
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
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定数:1.51890 15.19
classdefプロパティ:0.12992 1.30
ゲッター付きのclassdefプロパティ:1.39912 13.99
+ pkg.nop()関数:0.87345 8.73
+ pkg内から+ pkg.nop():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  
Matlab 8.3.0.532(R2014a)/ PCWIN64 Windows 7 6.1(eilonwy-win7)上のJava 1.7.0_11 
マシン:Core i7-3615QM CPU @ 2.30GHz、4 GB RAM(VMware Virtual Platform)
nIters = 100000 

動作時間(µsec)  
nop()関数:0.14 
nop()サブ関数:0.14 
@()[]匿名関数:0.69 
nop(obj)メソッド:3.28 
@classのnop()プライベート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 
classdef定数:4.18 
classdefプロパティ:1.18 
ゲッター付きのclassdefプロパティ:19.26 
+ pkg.nop()関数:4.03 
+ pkg内から+ pkg.nop():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のJava obj.nop():0.01 
MEX mexnop():0.91 
組み込みj():0.02 
構造体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)/ PCWIN64 Windows 8 6.2上のJava 1.7.0_60(nanit-shaked) 
マシン:Core i7-4720HQ CPU @ 2.60GHz、16 GB RAM(20378)
nIters = 100000 

動作時間(µsec)  
nop()関数:0.04 
nop()サブ関数:0.08 
@()[]匿名関数:1.83 
nop(obj)メソッド:3.15 
@classのnop()プライベートfcn: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定数:0.25 
classdefプロパティ:0.25 
ゲッター付きのclassdefプロパティ:0.64 
+ pkg.nop()関数:0.04 
+ pkg.nop()+ 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のJava obj.nop():0.01 
MEX 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上のMatlab 9.4.0.813654(R2018a)/ Java 1.8.0_144(eilonwy) 
マシン:Core i7-3615QM CPU @ 2.30GHz、16 GB RAM 
nIters = 100000 

動作時間(µsec)  
nop()関数:0.03 
nop()サブ関数:0.04 
@()[]匿名関数: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定数:0.16 
classdefプロパティ:0.13 
ゲッター付きのclassdefプロパティ:0.39 
+ pkg.nop()関数:0.02 
+ pkg.nop()内部から+ 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のJava obj.nop():0.03 
MEX mexnop():3.54 
組み込みj():0.10 
struct s.fooフィールドアクセス:0.16 
isempty(永続的):0.07 

更新:R2018bおよびR2019a:変更なし

大きな変更はありません。私はテスト結果を含めることを気にしていません。

ベンチマークのソースコード

これらのベンチマークのソースコードは、MITライセンスの下でリリースされたGitHubに公開しました。https://github.com/apjanke/matlab-bench


5
@AndrewJanke R2012aで​​再びベンチマークを実行できると思いますか?これは本当に面白いです。
Dang Khoa

7
皆さんこんにちは。それでもソースコードに興味がある場合は、再構築してGitHubでオープンソース化しました。github.com/apjanke/matlab-bench
Andrew Janke

2
@Seeda:これらの結果では、静的メソッドは「classdef class.static_nop()」としてリストされます。関数に比べるとかなり遅いです。頻繁に呼び出されなくても問題ありません。
Andrew Janke、2015

2
@AndrewJankeここにあります:gist.github.com/ShakedDovrat/62db9e8f6883c5e28fc0
シェイク

2
うわー!これらの結果が続く場合は、この回答全体を修正する必要があるかもしれません。追加しました。ありがとう!
Andrew Janke

3

ハンドルクラスには、クリーンアップのためにそれ自体へのすべての参照を追跡することによる追加のオーバーヘッドがあります。

ハンドルクラスを使用せずに同じ実験を試して、結果を確認してください。


1
Stringとまったく同じ実験ですが、今度は値クラスとして(ただし別のマシン上で)です。atest:0.009、btest:o.356。これは基本的にハンドルと同じ違いなので、参照の追跡が主な答えだとは思いません。また、関数のオーバーヘッドとパッケージの関数のオーバーヘッドについても説明していません。
2009

どのバージョンのmatlabを使用していますか?
MikeEL 2009

1
ハンドルクラスと値クラスの間でいくつかの同様の比較を実行しましたが、2つの間のパフォーマンスの違いに気づきませんでした。
RjOllos 2010年

私も違いに気づきません。
MikeEL 2010年

理にかなっています。Matlabでは、ハンドルオブジェクトだけでなく、すべての配列が参照カウントされます。これは、それらがコピーオンライトと共有の基になる生データを使用するためです。
Andrew Janke

1

オブジェクト指向のパフォーマンスは、使用するMATLABのバージョンに大きく依存します。すべてのバージョンについてコメントすることはできませんが、2012aは2010バージョンよりも大幅に改善されていることが経験からわかります。ベンチマークがないため、提示する数値はありません。ハンドルクラスを使用して排他的に記述され、2012aで​​記述された私のコードは、以前のバージョンではまったく実行できません。


1

実際にはコードに問題はありませんが、Matlabに問題があります。まるで遊んでいるような感じです。クラスコードをコンパイルするのはオーバーヘッドに過ぎません。単純なクラスポイント(ハンドルとして1回)とその他(値クラスとして1回)でテストを実行しました

    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の代わりに構造を使用しないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.