149

私はMATLAB OOPを試しています。最初は C++ の Logger クラスを模倣し、すべての文字列ヘルパー関数を String クラスにa + b入れてa == 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 秒、システムへの 1000 回の呼び出しfrpintf( 1, 'test\n' )で 7 (!) 秒 (String クラスを内部で使用する場合) (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 実装の定義を検索するのに時間を費やすことによるものであり、このオーバーヘッドは直接パスにある関数には存在しないのでしょうか?

4

4 に答える 4

233

私はしばらく OO MATLAB を使用してきましたが、最終的に同様のパフォーマンスの問題を調べました。

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

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

>> call_nops
コンピュータ: PCWIN リリース: 2009b
各関数/メソッドを 100000 回呼び出す
nop() 関数: 呼び出しごとに 0.02261 秒 0.23 usec
nop1-5() 関数: 呼び出しごとに 0.02182 秒 0.22 秒
nop() サブ関数: 呼び出しごとに 0.02244 秒 0.22 usec
@()[] 匿名関数: 呼び出しごとに 0.08461 秒 0.85 秒
nop(obj) メソッド: 呼び出しごとに 0.24664 秒 2.47 usec
nop1-5(obj) メソッド: 呼び出しごとに 0.23469 秒 2.35 秒
nop() プライベート関数: 呼び出しごとに 0.02197 秒 0.22 usec
classdef nop(obj): 呼び出しごとに 0.90547 秒 9.05 usec
classdef obj.nop(): 呼び出しごとに 1.75522 秒 17.55 秒
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 秒
C nop(): 呼び出しごとに 0.00001 秒 0.00 usec

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

「Java nop()」は、M コード ループ内から呼び出される何もしない Java メソッドであり、呼び出しごとに MATLAB から Java へのディスパッチ オーバーヘッドが含まれます。「Java からの Java nop()」は、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
各操作を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
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
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 クラス.static_nop(): 0.35357 3.54
Java からの Java obj.nop(): 0.00010 0.00
MEX mexnop(): 0.08709 0.87
いいえ(): 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 仮想プラットフォーム)
nIters = 100000

動作時間 (マイクロ秒)  
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
クラス定義定数: 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
eval ('いいえ'): 59.46
Java obj.nop(): 26.07
Java nop(obj): 3.72
Java feval('nop',obj): 9.25
Java クラス.staticNop(): 10.54
Java からの Java obj.nop(): 0.01
MEX 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) / PCWIN64 Windows 8 6.2 上の Java 1.7.0_60 (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
classdef 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('いいえ'): 8.26
feval(@nop): 0.63
eval ('いいえ'): 21.22
Java obj.nop(): 14.15
Java nop(obj): 2.50
Java feval('nop',obj): 10.30
Java クラス.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  
Matlab 9.4.0.813654 (R2018a) / MACI64 Mac OS X 10.13.5 (eilonwy) 上の Java 1.8.0_144
マシン: Core i7-3615QM CPU @ 2.30GHz、16 GB RAM
nIters = 100000

動作時間 (マイクロ秒)  
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
クラス定義定数: 0.16
classdef プロパティ: 0.13
getter を使用した classdef プロパティ: 0.39
+pkg.nop() 関数: 0.02
+pkg.nop() 内部から +pkg: 0.02
feval('いいえ'): 15.62
feval(@nop): 0.43
eval ('いいえ'): 32.08
Java obj.nop(): 28.77
Java nop(obj): 8.02
Java feval('nop',obj): 21.85
Java クラス.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: 変更なし

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

更新: R2021a: さらに高速なオブジェクト!

classdef オブジェクトが再び大幅に高速化されたようです。しかし、構造体は遅くなりました。

MACI64 上の Matlab R2021a  
Matlab 9.10.0.1669831 (R2021a) Update 2 / MACI64 Mac OS X 10.14.6 (eilonwy) 上の Java 1.8.0_202
マシン: Core i7-3615QM CPU @ 2.30GHz、4 コア、16 GB RAM
nIters = 100000

動作時間(μsec)  
nop() 関数: 0.03
nop() サブ関数: 0.04
@()[] 無名関数: 0.14
nop(obj) メソッド: 6.65
@class の nop() プライベート fcn: 0.02
classdef nop(obj): 0.03
classdef obj.nop(): 0.04
classdef pivate_nop(obj): 0.03
classdef class.static_nop(): 0.03
クラス定義定数: 0.16
classdef プロパティ: 0.12
getter を使用した classdef プロパティ: 0.17
+pkg.nop() 関数: 0.02
+pkg.nop() 内部から +pkg: 0.02
feval('いいえ'): 14.45
feval(@nop): 0.59
eval ('いいえ'): 23.59
Java obj.nop(): 30.01
Java nop(obj): 6.80
Java feval('nop',obj): 18.17
Java クラス.staticNop(): 16.77
Java からの Java obj.nop(): 0.02
MEX mexnop(): 2.51
ビルトイン j(): 0.21
struct s.foo フィールド アクセス: 0.29
isempty (永続的): 0.26

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

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

于 2009-11-16T23:58:22.537 に答える
4

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

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

于 2009-11-10T21:57:30.110 に答える
2

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

于 2014-03-09T17:12:30.997 に答える