MATLAB OOP est lent ou je fais quelque chose de mal?

j'expérimente avec MATLAB OOP , comme un début j'ai imité mes classes Logger DE C++et je mets toutes mes fonctions d'assistant de chaîne dans une classe de chaîne, en pensant que ce serait génial d'être capable de faire des choses comme a + b , a == b , a.find( b ) au lieu de cela de strcat( a b ) , strcmp( a, b ) , récupérer le premier élément de strfind( a, b ) , etc.

le problème: ralentissement

j'ai mis les choses ci-dessus à utiliser et immédiatement remarqué un drastique ralentissement. Est-ce que je le fais mal (ce qui est certainement possible car J'ai une expérience assez limitée de MATLAB), ou est-ce que L'OOP de MATLAB introduit juste beaucoup de frais généraux?

Mon cas de test

voici le test simple que j'ai fait pour la chaîne de caractères, en ajoutant simplement une chaîne de caractères et en enlevant à nouveau la partie ajoutée:

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' );

les résultats

temps Total en secondes, pour 1000 itérations:

btest 0.550 (avec de la Ficelle.SetLength 0.138, String.plus de 0,065, Chaîne.Longueur 0,057)

au plus 0,015

résultats pour le système logger sont également: 0.1 secondes pour 1000 appels à frpintf( 1, 'testn' ) , 7 (! secondes pour 1000 appels à mon système quand en utilisant la classe String en interne (OK, il y a beaucoup plus de logique en elle, mais à comparer avec C++: le plafond de mon système qui utilise std::string( "blah" ) et std::cout du côté de la sortie vs plaine std::cout << "blah" est de l'ordre de 1 milliseconde.)

Est-ce que les frais généraux lors de la recherche de classe/fonctions de package?

puisque MATLAB est interprété, il doit chercher la définition d'une fonction/objet au moment de l'exécution. J'ai donc été se demander que peut-être beaucoup plus de frais généraux est impliqué dans la recherche classe ou fonction paquet vs fonctions qui sont dans le chemin. J'ai essayé de le tester, et il est juste parfait inconnu. Pour exclure l'influence des classes / objets, j'ai comparé appeler une fonction dans le chemin vs une fonction dans un paquet:

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

résultats, recueillis de la même manière que ci-dessus:

atest 0.004 sec, 0.001 sec en ctest

btest 0.060 sec, en 0.014 s en util.ctest

donc, tous ces frais généraux viennent-ils tout juste de MATLAB passant du temps à chercher des définitions pour son implémentation OOP, alors que ces frais généraux ne sont pas là pour les fonctions qui sont directement dans le chemin?

121
demandé sur Jonas Stein 2009-11-07 18:36:43

4 réponses

j'ai travaillé avec OO MATLAB pendant un certain temps, et j'ai fini par examiner des problèmes de performance similaires.

la réponse courte est: oui, MATLAB's OOP is kind of slow. Il y a beaucoup de méthode Call overhead, plus élevé que les langues oo traditionnelles, et il n'y a pas grand chose que vous pouvez faire à ce sujet. Une partie de la raison peut être que idiomatic MATLAB utilise le code "vectorisé" pour réduire le nombre d'appels de méthode, et les frais généraux par appel n'est pas une priorité élevée.

j'ai comparé la performance en écrivant des fonctions" nop " comme les différents types de fonctions et de méthodes. Voici quelques résultats typiques.

>> call_nops
Computer: PCWIN   Release: 2009b
Calling each function/method 100000 times
nop() function:                 0.02261 sec   0.23 usec per call
nop1-5() functions:             0.02182 sec   0.22 usec per call
nop() subfunction:              0.02244 sec   0.22 usec per call
@()[] anonymous function:       0.08461 sec   0.85 usec per call
nop(obj) method:                0.24664 sec   2.47 usec per call
nop1-5(obj) methods:            0.23469 sec   2.35 usec per call
nop() private function:         0.02197 sec   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 per call
classdef nop(obj) (m-file):     0.90560 sec   9.06 usec per call
classdef class.staticnop():     1.16361 sec  11.64 usec per call
Java nop():                     2.43035 sec  24.30 usec per call
Java static_nop():              0.87682 sec   8.77 usec per call
Java nop() from Java:           0.00014 sec   0.00 usec per call
MEX mexnop():                   0.11409 sec   1.14 usec per call
C nop():                        0.00001 sec   0.00 usec per call

résultats similaires sur R2008a à R2009b. C'est sur Windows XP x64 qui exécute MATLAB 32 bits.

le "Java nop ()" est une méthode Java dite "do-nothing" appelée à partir d'une boucle de code M, et inclut le "MATLAB-to-Java dispatch overhead" avec chaque appel. "Java nop() de Java" est l' même chose appelée dans une boucle Java pour () et n'encourt pas cette pénalité de limite. Prenez les temps Java et c avec un grain de sel; un compilateur intelligent pourrait optimiser les appels complètement.

le mécanisme de détermination de la portée du paquet est nouveau, introduit à peu près en même temps que les classes classedef. Son comportement peut être lié.

quelques conclusions provisoires:

  • les méthodes sont plus lentes que les fonctions.
  • les méthodes new style (classdef) sont plus lentes que les méthodes old style.
  • la nouvelle syntaxe obj.nop() est plus lente que la syntaxe nop(obj) , même pour la même méthode sur un objet classdef. Idem pour les objets Java (non représentés). Si vous voulez aller vite, appelez nop(obj) .
  • L'appel de méthode
  • est plus élevé (environ 2x) dans le MATLAB 64 bits sur Windows. (Non illustré.)
  • l'expédition par la méthode MATLAB est plus lente que d'autres langue.

dire pourquoi il en est ainsi ne serait que spéculation de ma part. Les internes du moteur MATLAB ne sont pas publics. Il ne s'agit pas d'un problème interprété par rapport à un problème compilé en soi - MATLAB a un JIT - mais la syntaxe et la dactylographie plus lâche de MATLAB peuvent signifier plus de travail au moment de l'exécution. (P. ex. vous ne pouvez pas dire à partir de la seule syntaxe si "f(x)" est un appel de fonction ou un index dans un tableau; cela dépend de l'état de l'espace de travail au moment de l'exécution.) Peut-être parce que la classe de MATLAB les définitions sont liées à l'état du système de fichiers d'une manière que beaucoup d'autres langues ne sont pas.

Alors, que faire?

une approche MATLAB idiomatique est de " vectoriser "votre code en structurant vos définitions de classe de telle sorte qu'une instance objet enveloppe un tableau; c'est-à-dire que chacun de ses champs possède des tableaux parallèles (appelé organisation" planaire " dans la documentation MATLAB). Plutôt que d'avoir un tableau d'objets, chacun avec des champs de portefeuille de valeurs scalaires, définissez des objets qui sont eux-mêmes des tableaux, et ayez les méthodes prennent des tableaux comme entrées, et font des appels vectorisés sur les champs et les entrées. Cela réduit le nombre d'appels de méthode faite, je l'espère assez que l'expédition de frais généraux n'est pas un goulot d'étranglement.

imiter une Classe C++ ou Java dans MATLAB ne sera probablement pas optimal. Les classes Java/C++ sont généralement construites de telle sorte que les objets sont les plus petits blocs de construction, aussi spécifiques que vous le pouvez (c'est-à-dire, beaucoup de classes différentes).), et vous les composez dans des tableaux, des objets de collection, etc, et itérez-les avec des boucles. Pour faire des cours MATLAB rapides, tournez cette approche à l'envers. Ont des classes plus grandes dont les champs sont des tableaux, et appellent des méthodes vectorisées sur ces tableaux.

le point est d'arranger votre code pour jouer aux forces de la manipulation du langage - tableau, mathématiques vectorisées - et d'éviter les points faibles.

EDIT: depuis le post original, R2010b et R2011a sont venus hors. L'image générale est la même, avec les appels de MCOS devenant un peu plus rapides, et les appels de méthodes Java et old-style obtenant plus lent .

EDIT: j'avais l'habitude d'avoir quelques notes ici sur la "sensibilité de chemin" avec une table supplémentaire de fonction Appel timings, où les temps de fonction ont été affectés par la façon dont le chemin de Matlab a été configuré, mais cela semble avoir été une aberration de ma configuration de réseau particulière à l'époque. Le graphique ci-dessus reflète les temps typique de la prépondérance de mes tests au fil du temps.

Mise À Jour: R2011b

MODIFIER (2/13/2012): R2011b et, à l'image de performances a suffisamment changé pour mettre à jour ce.

Arch: PCWIN   Release: 2011b 
Machine: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB RAM, NVIDIA NVS 300
Doing each operation 100000 times
style                           total       µsec per call
nop() function:                 0.01578      0.16
nop(), 10x loop unroll:         0.01477      0.15
nop(), 100x loop unroll:        0.01518      0.15
nop() subfunction:              0.01559      0.16
@()[] anonymous function:       0.06400      0.64
nop(obj) method:                0.28482      2.85
nop() private function:         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 constant:              1.51890     15.19
classdef property:              0.12992      1.30
classdef property with getter:  1.39912     13.99
+pkg.nop() function:            0.87345      8.73
+pkg.nop() from inside +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() from Java:       0.00010      0.00
MEX mexnop():                   0.08709      0.87
C nop():                        0.00001      0.00
j() (builtin):                  0.00251      0.03

je pense que le résultat de ceci est que:

  • les méthodes MCOS / classdef sont plus rapides. Le coût est maintenant à peu près équivalent avec les anciennes classes de style, tant que vous utilisez la syntaxe foo(obj) . Donc la vitesse de la méthode n'est pas une raison plus longue de s'en tenir aux anciennes classes de style dans la plupart des cas. (Bravo, MathWorks!)
  • mettre des fonctions dans les espaces de noms les rend lents. (Pas nouvelle dans R2011b, tout nouveau dans mon test.)

Mise À Jour: R2014a

j'ai reconstruit le code d'analyse comparative et je l'ai lancé sur R2014 a.

Matlab R2014a on PCWIN64  
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 on PCWIN64 Windows 7 6.1 (eilonwy-win7) 
Machine: Core i7-3615QM CPU @ 2.30GHz, 4 GB RAM (VMware Virtual Platform)
nIters = 100000 

Operation                        Time (µsec)  
nop() function:                         0.14 
nop() subfunction:                      0.14 
@()[] anonymous function:               0.69 
nop(obj) method:                        3.28 
nop() private fcn on @class:            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 constant:                      4.18 
classdef property:                      1.18 
classdef property with getter:         19.26 
+pkg.nop() function:                    4.03 
+pkg.nop() from inside +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() from Java:               0.01 
MEX mexnop():                           0.91 
builtin j():                            0.02 
struct s.foo field access:              0.14 
isempty(persistent):                    0.00 

mise à jour : R2015b: les objets sont devenus plus rapides!

Voici les résultats R2015b, s'il vous plaît fourni par @Shaked. C'est un grand changement: OOP est beaucoup plus rapide, et maintenant la syntaxe obj.method() est aussi rapide que method(obj) , et beaucoup plus rapide que les objets hérités OOP.

Matlab R2015b on PCWIN64  
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 on PCWIN64 Windows 8 6.2 (nanit-shaked) 
Machine: Core i7-4720HQ CPU @ 2.60GHz, 16 GB RAM (20378)
nIters = 100000 

Operation                        Time (µsec)  
nop() function:                         0.04 
nop() subfunction:                      0.08 
@()[] anonymous function:               1.83 
nop(obj) method:                        3.15 
nop() private fcn on @class:            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 constant:                      0.25 
classdef property:                      0.25 
classdef property with getter:          0.64 
+pkg.nop() function:                    0.04 
+pkg.nop() from inside +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() from Java:               0.01 
MEX mexnop():                           0.33 
builtin j():                            0.15 
struct s.foo field access:              0.25 
isempty(persistent):                    0.13 

Mise À Jour: R2018a

Voici les résultats de R2018a. Ce n'est pas l'énorme bond que nous avons vu lorsque le nouveau moteur d'exécution a été introduit dans R2015b, mais c'est encore une amélioration appréciable d'année en année. Notamment, fonction anonyme les poignées sont plus rapides.

Matlab R2018a on MACI64  
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 on MACI64 Mac OS X 10.13.5 (eilonwy) 
Machine: Core i7-3615QM CPU @ 2.30GHz, 16 GB RAM 
nIters = 100000 

Operation                        Time (µsec)  
nop() function:                         0.03 
nop() subfunction:                      0.04 
@()[] anonymous function:               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 constant:                      0.16 
classdef property:                      0.13 
classdef property with getter:          0.39 
+pkg.nop() function:                    0.02 
+pkg.nop() from inside +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() from Java:               0.03 
MEX mexnop():                           3.54 
builtin j():                            0.10 
struct s.foo field access:              0.16 
isempty(persistent):                    0.07 

Code Source de Repères

j'ai mis le code source de ces benchmarks sur GitHub, publié sous licence MIT. https://github.com/apjanke/matlab-bench

193
répondu Andrew Janke 2018-06-06 19:06:53

la classe de la poignée comporte un plafond additionnel qui permet de suivre toutes les références à elle-même à des fins de nettoyage.

essayez la même expérience sans utiliser la classe de poignée et voir ce que vos résultats sont.

3
répondu MikeEL 2009-11-10 21:57:30

OO performance dépend fortement de la MATLAB version utilisée. Je ne peux pas commenter toutes les versions, mais je sais par expérience que 2012a est bien meilleure que les versions 2010. Aucun point de référence et donc pas de chiffres à présenter. Mon code, exclusivement écrit en utilisant les classes handle et écrit sous 2012a ne sera pas exécuté du tout sous les versions précédentes.

1
répondu HG Bruce 2014-03-09 17:12:30

en fait pas de problème avec votre code mais C'est un problème avec Matlab. Je pense qu'il est une sorte de jeu autour de ressembler. Ce n'est rien d'autre que des frais généraux pour compiler le code de classe. J'ai fait le test avec le point de classe simple (une fois comme poignée) et l'autre (une fois comme classe de valeur)

    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

voici le test

%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

les résultats t1 =

12.0212 % de la Poignée

t2 =

12.0042% valeur

t3 =

0.5489  % vector

t4 =

0.0707 % structure 

par conséquent, pour une performance efficace, éviter D'utiliser la structure OOP est un bon choix pour grouper les variables

1
répondu Ahmad 2014-04-09 15:14:37