web-dev-qa-db-ja.com

MATLAB OOP遅いですか、何か間違っていますか?

私は [〜#〜] matlab [〜#〜][〜#〜] oop [〜#〜] を試しています。最初はC++のLoggerを模倣しましたクラスと私はすべての文字列ヘルパー関数を文字列クラスに入れています。a.find( b )strcat( a b )の代わりにa + ba == bstrcmp( 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回の反復の合計時間(秒):

btest 0.550(String.SetLength 0.138、String.plus 0.065、String.Length 0.057を使用)

atest 0.015

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

btest 0.060秒、util.ctestで0.014秒

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

133
stijn

私は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 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 
 classdef nop(obj)(mファイル):0.90560秒9.06コールごとのusec 
 classdef class.staticnop():1.16361秒11.64 usec per call 
 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までの同様の結果。これは、Windows XP x64 32ビットMATLABを実行している場合です。

「Java nop()」は、Mコードループ内から呼び出されるdo-nothing Javaメソッドであり、各呼び出しでのMATLABからJavaへのディスパッチオーバーヘッドを含みます。 )from Java」は、Java for()ループで呼び出されるものと同じであり、その境界ペナルティは発生しません。JavaおよびCタイミングを洗練されたコンパイラーは、呼び出しを完全に最適化できます。

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

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

  • メソッドは関数よりも低速です。
  • 新しいスタイル(classdef)メソッドは、古いスタイルメソッドよりも低速です。
  • 新しいobj.nop()構文は、classdefオブジェクトの同じメソッドであっても、nop(obj)構文よりも低速です。 Javaオブジェクト(図示せず)についても同じです。高速にしたい場合は、nop(obj)を呼び出します。
  • メソッド呼び出しのオーバーヘッドは、Windows上の64ビットMATLABで高くなります(約2倍)。 (表示されていません。)
  • MATLABメソッドのディスパッチは、他のいくつかの言語よりも遅くなります。

これがなぜそうなのかを言うのは、私の側の憶測にすぎない。 MATLABエンジンのOO内部は公開されていません。それ自体は解釈された問題とコンパイルされた問題ではありません-MATLABにはJITがあります-しかし、MATLABの緩やかな入力と構文は実行時により多くの作業を意味する場合があります(例: 「f(x)」が関数呼び出しであるか、配列へのインデックスであるかを構文だけから判断することはできません。これは、実行時のワークスペースの状態に依存します。)MATLABのクラス定義がファイルシステムに関連付けられている可能性があります他の多くの言語がそうでないように述べてください。

じゃあ何をすればいいの?

これに対する慣用的なMATLABアプローチは、オブジェクトインスタンスが配列をラップするようにクラス定義を構造化することにより、コードを「ベクトル化」することです。つまり、各フィールドは並列配列を保持します(MATLABドキュメンテーションでは「平面」構成と呼ばれます)。それぞれがスカラー値を保持するフィールドを持つオブジェクトの配列を持つのではなく、それ自体が配列であるオブジェクトを定義し、メソッドが配列を入力として受け取り、フィールドと入力に対してベクトル化された呼び出しを行うこれにより、実行されるメソッド呼び出しの数が減ります。これは、ディスパッチのオーバーヘッドがボトルネックにならないようにすることを願っています。

C++またはJavaクラスを模倣することはおそらく最適ではありません。Java/ C++クラスは通常、オブジェクトができる限り具体的な(つまり、多くのさまざまなクラスの)、それらを配列、コレクションオブジェクトなどで構成し、ループで繰り返します。高速のMATLABクラスを作成するには、そのアプローチを裏返します。フィールドが配列である大きなクラスを持ち、それらでベクトル化されたメソッドを呼び出します配列。

要点は、言語の長所(配列処理、ベクトル化された数学)に合わせてコードを調整し、弱点を回避することです。

編集:元の投稿以来、R2010bとR2011aが出てきました。全体像は同じで、MCOS呼び出しは少し速くなり、Javaと古いスタイルのメソッド呼び出しはslower

編集:私はここで「パスの感度」に関するいくつかのメモを関数呼び出しタイミングの追加テーブルと共に持っていました。関数時間はMatlabパスの設定方法によって影響されましたが、それは私の特定のネットワーク設定の異常でした時間。上記のチャートは、時間の経過に伴う私のテストの優勢の典型的な時間を反映しています。

更新:R2011b

編集(2012年2月13日):R2011bがリリースされ、パフォーマンスの状況はこれを更新するのに十分に変更されました。

アーチ:PCWINリリース:2011b 
マシン:R2011b、Windows XP、8 x 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 
 classdef定数:1.51890 15.19 
 classdefプロパティ:0.12992 1.30 
 classdefプロパティw i番目のゲッター: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で実行しました。

 Matlab R2014a on PCWIN64 
 Matlab 8.3.0.532(R2014a)/ Java 1.7.0_11 on PCWIN64 Windows 7 6.1(eilonwy-win7)
マシン: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 
 nop()@classのプライベート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.nop()内部から+ 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からのJava obj.nop():0.01 
 MEX mexnop():0.91 
 builtin j():0.02 
 struct s.fooフィールドアクセス:0.14 
 isempty(persistent):0.00 

更新:R2015b:オブジェクトが高速になりました!

@Shakedから親切に提供されたR2015bの結果を次に示します。これは、bigの変更です:OOPは大幅に高速になり、現在obj.method()構文はmethod(obj)と同じくらい高速で、従来のOOPオブジェクトよりもはるかに高速です。

 Matlab R2015b on PCWIN 
 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 
 
動作時間(µsec)
 nop()関数:0.04 
 nop()サブ関数:0.08 
 @()[]無名関数:1.83 
 nop(obj)メソッド:3.15 
 nop()@classのプライベート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.no内部からのp()+ 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():0.01 
 MEX mexnop():0.33 
 builtin j():0.15 
 struct s.fooフィールドアクセス: 0.25 
 isempty(永続):0.13 

更新:R2018a

R2018aの結果は次のとおりです。新しい実行エンジンがR2015bに導入されたときに見たような大きなジャンプではありませんが、それでも、前年比でかなりの改善が見られます。特に、匿名関数ハンドルはずっと高速になりました。

 Matlab R2018a on MACI64 
 Matlab 9.4.0.813654(R2018a)/ Java 1.8.0_144 on MACI64 Mac OS X 10.13.5(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 
 builtin j():0.10 
 struct s.fooフィールドアクセス:0.16 
 isempty(persistent):0.07 

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

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

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

MIT License。 https://github.com/apjanke/matlab-bench の下でリリースされたGitHubにこれらのベンチマークのソースコードを掲載しました。

213
Andrew Janke

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

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

3
MikeEL

実際にはコードには問題ありませんが、Matlabには問題があります。私はそれのように見えるように遊んでいるようなものだと思います。クラスコードをコンパイルするのはオーバーヘッドに他なりません。単純なクラスポイント(一度ハンドルとして)ともう一方(値クラスとして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代わりに構造体が変数をグループ化するための適切な選択です

1
Ahmad

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

1
HG Bruce