1. ホーム
  2. matlab

[解決済み] MATLAB OOPは遅いですか、それとも私が何か間違っていますか?

2022-07-03 07:07:39

質問

私は今 MATLAB OOP のようなことができたらいいなと思い、手始めにC++のLoggerクラスを真似て、文字列ヘルパー関数をすべてStringクラスに入れています。 a + b , a == b , a.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を使用した場合)

atest 0.015

ロガーシステムの結果も同様で、1000コールで0.1秒です。 になります。 frpintf( 1, 'test\n' ) への 1000 回の呼び出しで 0.1 秒、内部で String クラスを使用している私のシステムでは 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実装のために定義を探すのに時間を費やしているだけで、直接パスにある関数にはこのオーバーヘッドはないのでしょうか?

どのように解決するのですか?

私はしばらくの間、OO MATLABで作業していましたが、結局、同じようなパフォーマンスの問題を見ることになりました。

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

私は、さまざまな種類の関数やメソッドとして、何もしない "nop" 関数を記述してパフォーマンスをベンチマークしてみました。以下は典型的な結果です。

>> call_nops
コンピュータ PCWIN Release: 2009b
各関数・メソッドの100000回呼び出し
nop()関数。                0.02261秒 0.23ユーザック/コール
nop1-5()関数             0.02182秒 0.22usec/コール
nop()サブファンクション              0.02244秒 1コールあたり0.22usec
@()[] 匿名関数。      0.08461秒 1コールあたり0.85usec
nop(obj)メソッド。               0.24664秒 2.47usec/コール
nop1-5(obj) メソッド: 0.23469秒 2.35ユーザック/コール
nop() プライベート関数。        0.02197 sec 0.22 usec/コール
classdef nop(obj): 0.90547秒 9.05usec/コール
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秒 9.06usec/コール
クラス定義 class.staticnop(): 1.16361秒 11.64usec/コール
Java nop(): 2.43035秒 24.30usec/コール
Java static_nop(): 0.87682秒 8.77usec/コール
Javaからのnop()。          0.00014秒 0.00usec/コール
MEX mexnop(): 0.11409秒 1.14usec/コール
C nop(): 0.00001秒 0.00usec/コール


R2008a~R2009bでも同様の結果です。Windows XP x64で32bitのMATLABが動作している場合です。

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

パッケージのスコープ機構は新しく、classdef クラスとほぼ同時に導入されました。その動作は関連しているかもしれません。

いくつかの暫定的な結論です。

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

なぜそうなのかを語ることは、私の推測に過ぎません。MATLAB エンジンの OO 内部は公開されていません。MATLABにはJITがありますから、解釈対コンパイルの問題ではありませんが、MATLABの緩い型付けと構文は、実行時に多くの作業を意味するかもしれません。(例えば、"f(x)"が関数呼び出しなのか配列へのインデックスなのか、構文だけではわかりません;実行時のワークスペースの状態に依存します)。これは、MATLABのクラス定義が、他の多くの言語にはない方法でファイルシステムの状態に結びつけられているためかもしれません。

では、どうすればよいのでしょうか。

MATLABの慣用的なアプローチは、オブジェクトのインスタンスが配列をラップするようにクラス定義を構造化することによって、コードをquot;vectorize"することです。オブジェクトの配列を持ち、それぞれがスカラー値を持つフィールドを持つよりも、それ自体が配列であるオブジェクトを定義し、メソッドが配列を入力として取り、フィールドと入力に対してベクトル化された呼び出しを行うようにするのです。これにより、ディスパッチ オーバーヘッドがボトルネックにならない程度に、メソッド呼び出しの数を減らすことができます。

MATLABでC++またはJavaクラスを模倣することは、おそらく最適ではないでしょう。Java/C++ クラスは通常、オブジェクトが最小のビルディング ブロックであり、できる限り具体的で (つまり、多くの異なるクラス)、それらを配列、コレクション オブジェクトなどで構成し、ループで反復するように構築されています。MATLABの高速なクラスを作るには、この方法を裏返せばよいのです。フィールドが配列である大きなクラスを持ち、それらの配列に対してベクトル化されたメソッドを呼び出すのです。

ポイントは、配列処理、ベクトル化された数学など、言語の長所を生かし、短所を避けるようにコードを配置することです。

編集: 最初の投稿以来、R2010b と R2011a が出ています。全体的には同じで、MCOS の呼び出しは少し速くなり、Java や旧式のメソッド呼び出しは 遅くなる .

編集: 私は以前、関数呼び出しのタイミングの追加表とともに、quot;path sensitivity" に関するいくつかのメモをここに持っており、関数時間は Matlab パスの構成方法によって影響を受けていました。上記のグラフは、長期間にわたる私のテストの大部分で典型的な時間を反映しています。

アップデート R2011b

編集 (2012/2/13)。R2011b がリリースされ、これを更新するのに十分なほど性能のイメージが変わりました。

Arch: PCWIN Release: 2011b 
マシン R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB RAM, NVIDIA NVS 300
各操作を100000回実行
1回の呼び出しで合計µsec
nop()関数。                0.01578 0.16
nop()、10xループのアンロール。        0.01477 0.15
nop()、100倍ループ展開。       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
クラス定義 private_nop(obj): 0.32272 3.23
クラス定義class.staticnop(): 0.88959 8.90
クラス定義定数: 1.51890 15.19
classdef プロパティ。             0.12992 1.30
ゲッターを持つクラス定義プロパティ。 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 obj.nop()をJavaから。      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) 構文を使う限り、コストは古いスタイルのクラスと同程度になります。したがって、メソッドの速度は、ほとんどの場合、古いスタイルのクラスに固執する理由にはならなくなりました。(Kudos, MathWorks!) 。
  • 関数をネームスペースに置くと遅くなる。(R2011b では新しくないが、私のテストでは新しいだけ)。

更新されました。R2014a

ベンチマークコードを再構築し、R2014aで実行しました。

PCWIN64上のMatlab R2014a  
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() クラスのプライベートfcn:            0.14 
クラス定義 nop(obj): 5.30 
クラス定義 obj.nop(): 10.78 
classdef pivate_nop(obj): 4.88 
クラス定義class.static_nop(): 11.81 
クラス定義定数: 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からobj.nop()。              0.01 
MEXのmexnop(): 0.91 
組み込みのj(): 0.02 
struct s.foo フィールドアクセス。             0.14 
isempty(persistent): 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 

動作時間(μsec)  
nop()関数。                        0.04 
nop()サブファンクション:                      0.08 
@()[] 匿名関数。              1.83 
nop(obj)メソッド。                       3.15 
nop() クラスのプライベートfcn:            0.04 
クラス定義 nop(obj): 0.28 
クラス定義 obj.nop(): 0.31 
classdef pivate_nop(obj): 0.34 
クラス定義class.static_nop(): 0.05 
クラス定義定数: 0.25 
classdef プロパティ:                      0.25 
ゲッター付きクラス定義プロパティ:          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からobj.nop()。              0.01 
MEXのmexnop(): 0.33 
組み込みのj(): 0.15 
struct s.foo フィールドアクセス。             0.25 
isempty(persistent): 0.13 

更新内容 R2018a

R2018a の結果です。R2015b で新しい実行エンジンが導入されたときのような大きなジャンプではありませんが、それでも前年比では評価できる改善です。注目すべきは、匿名関数のハンドルが非常に速くなったことです。

MACI64上のMatlab R2018a  
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 on MACI64 Mac OS X 10.13.5 (eilonwy)です。
マシン コアi7-3615QM CPU @ 2.30GHz、16GB RAM 
nIters = 100000 

動作時間 (µsec)  
nop()関数。                        0.03 
nop()サブファンクション:                      0.04 
@()[] 匿名関数。              0.16 
クラス定義 nop(obj): 0.16 
クラス定義 obj.nop(): 0.17 
クラス定義pivate_nop(obj): 0.16 
クラス定義class.static_nop(): 0.03 
クラス定義定数: 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からobj.nop()。              0.03 
MEXのmexnop(): 3.54 
組み込みのj(): 0.10 
struct s.fooのフィールドアクセス。             0.16 
isempty(persistent): 0.07 


アップデートしました。R2018b と R2019a: 変更なし

大きな変更はありません。わざわざテスト結果を載せているわけではありません。

更新しました。R2021a: オブジェクトがさらに高速化!

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

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

動作時間 (μsec)  
nop()関数。                        0.03 
nop()サブファンクション:                      0.04 
匿名関数 @()[] を実行。              0.14 
nop(obj)メソッド。                       6.65 
nop() クラスのプライベートfcn:            0.02 
クラス定義 nop(obj): 0.03 
クラス定義 obj.nop(): 0.04 
classdef pivate_nop(obj): 0.03 
クラス定義class.static_nop(): 0.03 
クラス定義定数: 0.16 
classdef プロパティ:                      0.12 
ゲッター付き classdef プロパティ:          0.17 
+pkg.nop()関数。                   0.02 
+pkg.nop()を+pkg内部から実行: 0.02 
feval('nop'): 14.45 
feval(@nop): 0.59 
eval('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()。              0.02 
MEXのmexnop(): 2.51 
組み込みのj(): 0.21 
struct s.fooのフィールドアクセス。             0.29 
isempty(persistent): 0.26 

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

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