web-dev-qa-db-fra.com

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

J'expérimente avec MATLAB OOP , pour commencer j'ai imité le Logger de mon C++ et je mets toutes mes fonctions d'aide de chaîne dans une classe String, pensant que ce serait génial de pouvoir faire des choses comme a + b, a == b, a.find( b ) à la place de strcat( a b ), strcmp( a, b ), récupérer le premier élément de strfind( a, b ), etc.

Le problème: le ralentissement

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

Mon scénario de test

Voici le test simple que j'ai fait pour la chaîne, simplement ajouter une chaîne et supprimer à 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 String.SetLength 0,138, String.plus 0,065, String.Length 0,057)

au moins 0,015

Les résultats pour le système d'enregistrement sont également: 0,1 seconde pour 1000 appels à frpintf( 1, 'test\n' ), 7 (!) Secondes pour 1000 appels à mon système lors de l'utilisation interne de la classe String (OK, il y a beaucoup plus de logique dedans , mais pour comparer avec C++: la surcharge de mon système qui utilise std::string( "blah" ) et std::cout du côté de la sortie par rapport à la simple std::cout << "blah" est de l'ordre de 1 milliseconde.)

Est-ce juste une surcharge lors de la recherche de fonctions de classe/package?

Puisque MATLAB est interprété, il doit rechercher la définition d'une fonction/d'un objet au moment de l'exécution. Je me demandais donc peut-être que beaucoup plus de frais généraux sont impliqués dans la recherche de fonctions de classe ou de package par rapport aux fonctions qui se trouvent sur le chemin. J'ai essayé de tester ça, et ça devient juste plus étrange. Pour exclure l'influence des classes/objets, j'ai comparé l'appel d'une fonction dans le chemin par rapport à une fonction dans un package:

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, rassemblés de la même manière que ci-dessus:

au moins 0,004 s, 0,001 s en test

btest 0,060 sec, 0,014 sec en util.ctest

Donc, tous ces frais généraux viennent-ils uniquement du fait que MATLAB passe du temps à rechercher des définitions pour son implémentation OOP, alors que ces frais généraux ne sont pas là pour les fonctions qui se trouvent directement dans le chemin?

133
stijn

Je travaille avec OO MATLAB depuis un certain temps, et j'ai fini par examiner des problèmes de performances similaires.

La réponse courte est: oui, MATLAB OOP est un peu lent. Il y a une surcharge substantielle d'appels de méthode, plus élevée que les langages traditionnels OO langues, et il n'y a pas grand-chose que vous Une partie de la raison peut être que MATLAB idiomatique utilise du code "vectorisé" pour réduire le nombre d'appels de méthode, et la surcharge par appel n'est pas une priorité élevée.

J'ai comparé les performances en écrivant les fonctions "nop" de ne rien faire comme les différents types de fonctions et de méthodes. Voici quelques résultats typiques.

 >> call_nops 
 Ordinateur: PCWIN Release: 2009b 
 Appel de chaque fonction/méthode 100 000 fois 
 fonction nop (): 0,02261 s 0,23 usec par appel 
 Fonctions nop1-5 (): 0,02182 s 0,22 usec par appel 
 sous-fonction nop (): 0,02244 s 0,22 usec par appel 
 @ () [] fonction anonyme: 0,08461 s 0,85 usec par appel 
 méthode nop (obj): 0,24664 s 2,47 usec par appel 
 méthodes nop1-5 (obj): 0,23469 s 2,35 usec par appel 
 nop () fonction privée: 0,02197 s 0,22 usec par appel 
 classdef nop (obj): 0,90547 s 9,05 usec par appel 
 classdef obj.nop (): 1,75522 s 17,55 usec par appel 
 classdef private_nop (obj): 0,84738 sec 8.47 usec par appel 
 classdef nop (obj) (fichier m): 0.90560 sec 9.06 usec par appel 
 classdef class.staticnop (): 1.16361 sec 11.64 usec par appel 
 Java nop (): 2,43035 sec 24,30 usec par appel 
 Java static_nop (): 0,87682 sec 8,77 usec par appel 
 Java nop () depuis Java: 0,00014 sec 0,00 usec par appel 
 MEX mexnop (): 0,11409 s 1,14 usec par appel 
 C nop (): 0,00001 s 0,00 usec par appel 

Résultats similaires sur R2008a à R2009b. C'est sur Windows XP x64 exécutant MATLAB 32 bits.

Le "Java nop ()" est une méthode de ne rien faire Java appelée à partir d'une boucle de code M, et inclut la surcharge de répartition MATLAB-vers-Java à chaque appel. "Java nop ( ) de Java "est la même chose appelée dans une boucle Java for () et n'encourt pas cette pénalité de limite. Prenez les timings Java et C avec un grain de sel, un compilateur intelligent pourrait optimiser complètement les appels.

Le mécanisme de définition de la portée des packages est nouveau, introduit à peu près en même temps que les classes classdef. Son comportement peut être lié.

Quelques conclusions provisoires:

  • Les méthodes sont plus lentes que les fonctions.
  • Les nouvelles méthodes de style (classdef) sont plus lentes que les anciennes méthodes de 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 Java (non représentés). Si vous voulez aller vite, appelez nop(obj).
  • La surcharge des appels de méthode est plus élevée (environ 2x) dans MATLAB 64 bits sous Windows. (Pas montré.)
  • L'envoi de la méthode MATLAB est plus lent que certains autres langages.

Dire pourquoi il en est ainsi ne serait que de la spéculation de ma part. Le OO internes du moteur MATLAB n'est pas public. Ce n'est pas un problème interprété vs compilé en soi - MATLAB a un JIT - mais le typage et la syntaxe plus lâches de MATLAB peuvent signifier plus de travail au moment de l'exécution. (Par exemple vous ne pouvez pas dire à partir de la syntaxe seule 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.) Cela peut être dû au fait que les définitions de classe de MATLAB sont liées au système de fichiers énoncer d'une manière que beaucoup d'autres langues ne le sont pas.

Alors que faire?

Une approche MATLAB idiomatique de ceci est de "vectoriser" votre code en structurant vos définitions de classe de telle sorte qu'une instance d'objet enveloppe un tableau; c'est-à-dire que chacun de ses champs contient des tableaux parallèles (appelés organisation "planaire" dans la documentation MATLAB). Plutôt que d'avoir un tableau d'objets, chacun avec des champs contenant des valeurs scalaires, définissez des objets qui sont eux-mêmes des tableaux et demandez aux méthodes de prendre des tableaux en entrée et d'effectuer des appels vectorisés sur les champs et les entrées. Cela réduit le nombre d'appels de méthode effectués, espérons-le suffisamment pour que la surcharge de répartition ne soit pas un goulot d'étranglement.

Imiter un C++ ou Java dans MATLAB ne sera probablement pas optimale. Les classes Java/C++ sont généralement construites de telle sorte que les objets soient les plus petits blocs de construction, aussi spécifiques que possible (c'est-à-dire, beaucoup de classes différentes), et vous les composez dans des tableaux, des objets de collection, etc., et vous les parcourez avec des boucles. Pour créer des classes MATLAB rapides, retournez cette approche à l'envers. Ayez des classes plus grandes dont les champs sont des tableaux, et appelez des méthodes vectorisées sur celles-ci. tableaux.

Le but est d'arranger votre code pour jouer sur les points forts du langage - la gestion des tableaux, les mathématiques vectorisées - et éviter les points faibles.

EDIT: Depuis la publication d'origine, R2010b et R2011a sont sortis. L'image globale est la même, avec des appels MCOS un peu plus rapides, et Java et les appels de méthode ancienne deviennent plus lents .

EDIT: J'avais l'habitude d'avoir quelques notes ici sur la "sensibilité du chemin" avec un tableau supplémentaire de temporisations des appels de fonction, où les temps de fonction étaient affectés par la façon dont le chemin Matlab était configuré, mais cela semble avoir été une aberration de ma configuration réseau particulière à le temps. Le tableau ci-dessus reflète les temps typiques de la prépondérance de mes tests dans le temps.

Mise à jour: R2011b

EDIT (13/02/2012): R2011b est sorti et l'image des performances a suffisamment changé pour la mettre à jour.

 Arch: PCWIN Release: 2011b 
 Machine: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB RAM, NVIDIA NVS 300 
 Effectuant chaque opération 100000 fois 
 style µsec total par appel 
 fonction nop (): 0,01578 0,16 
 nop (), déroulement de boucle 10x: 0,01477 0,15 
 nop (), déroulement de boucle 100x: 0,01518 0,15 
 Nop () sous-fonction: 0,01559 0,16 
 @ () [] Fonction anonyme: 0,06400 0,64 
 Méthode nop (obj): 0,28482 2,85 
 Nop () privé fonction: 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 
 constante classdef: 1,51890 15,19 
 propriété classdef: 0,12992 1,30 
 propriété classdef w avec getter: 1,39912 13,99 
 + fonction pkg.nop (): 0,87345 8,73 
 + pkg.nop () de l'intérieur + 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 () de Java: 0,00010 0,00 
 MEX mexnop (): 0,08709 0,87 
 C nop (): 0,00001 0,00 
 J () (intégré): 0,00251 0,03 

Je pense que le résultat est que:

  • Les méthodes MCOS/classdef sont plus rapides. Le coût est maintenant à peu près au même niveau que les anciennes classes de style, tant que vous utilisez la syntaxe foo(obj). La vitesse de la méthode n'est donc plus une raison de s'en tenir aux anciennes classes de style dans la plupart des cas. (Bravo, MathWorks!)
  • Mettre des fonctions dans des espaces de noms les ralentit. (Pas nouveau dans R2011b, juste nouveau dans mon test.)

Mise à jour: R2014a

J'ai reconstruit le code d'analyse comparative et l'exécute sur R2014a.

 Matlab R2014a sur PCWIN64 
 Matlab 8.3.0.532 (R2014a)/Java 1.7.0_11 sur PCWIN64 Windows 7 6.1 (eilonwy-win7) 
 Machine: CPU Core i7-3615QM à 2,30 GHz, 4 Go RAM (VMware Virtual Platform) 
 NIters = 100000 
 
 Durée de fonctionnement (µsec ) 
 fonction nop (): 0,14 
 sous-fonction nop (): 0,14 
 @ () [] fonction anonyme: 0,69 
 méthode nop (obj): 3,28 
 nop () fcn privé sur @class: 0,14 
 classdef nop (obj): 5,30 
 classdef obj.nop (): 10,78 
 classdef pivate_nop (obj): classdef pivate_nop (obj): 4,88 
 Classdef class.static_nop (): 11,81 
 Constante classdef: 4,18 
 Propriété classdef: 1,18 
 Propriété classdef avec getter: 19,26 
 + Pkg Fonction .nop (): 4.03 
 + pkg.nop () de l'intérieur + 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 () de Java: 0,01 
 MEX mexnop (): 0,91 
 Intégré j (): 0,02 
 accès au champ struct s.foo: 0,14 
 vide (persistant): 0,00 

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

Voici les résultats du R2015b, aimablement fournis par @Shaked. Il s'agit d'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 sur PCWIN64 
 Matlab 8.6.0.267246 (R2015b)/Java 1.7.0_60 sur PCWIN64 Windows 8 6.2 (à agitation nanométrique) 
 Machine: processeur Core i7-4720HQ à 2,60 GHz, 16 Go RAM (20378) 
 NIters = 100000 
 
 Durée de fonctionnement (µsec) 
 fonction nop (): 0,04 
 sous-fonction nop (): 0,08 
 @ () [] fonction anonyme: 1,83 
 méthode nop (obj): 3,15 
 nop () fcn privé sur @class: 0,04 
 classdef nop (obj): 0,28 
 classdef obj.nop (): 0,31 
 classdef pivate_nop (obj): 0,34 
 classdef pivotate_nop (obj): 0,34 
 classdef .____.] classdef class.static_nop (): 0,05 
 constante classdef: 0,25 
 propriété classdef: 0,25 
 propriété classdef avec getter: 0,64 
 + pkg.nop () fonction: 0,04 
 + pkg.no p () de l'intérieur + 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 () de Java: 0,01 
 MEX mexnop (): 0,33 
 Builtin j (): 0,15 
 Struct s.foo accès au champ: 0,25 
 Est vide (persistant): 0,13 

Mise à jour: R2018a

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

 Matlab R2018a sur MACI64 
 Matlab 9.4.0.813654 (R2018a)/Java 1.8.0_144 sur MACI64 Mac OS X 10.13.5 (eilonwy) 
 Machine: CPU Core i7-3615QM @ 2,30 GHz, 16 Go RAM 
 NIters = 100000 
 
 Durée de fonctionnement (µsec) 
 fonction nop (): 0,03 
 sous-fonction nop (): 0,04 
 @ () [] fonction anonyme: 0,16 
 classdef nop (obj): 0,16 
 classdef obj.nop (): 0,17 
 classdef pivate_nop (obj): 0,16 
 classdef class.static_nop (): 0,03 
 constante classdef: 0,16 
 propriété classdef: 0.13 
 Propriété classdef avec getter: 0.39 
 + Fonction pkg.nop (): 0.02 
 + Pkg.nop () de l'intérieur + 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 () de Java: 0,03 
 MEX mexnop (): 3,54 
 intégré j (): 0,10 
 struct s.foo accès au champ: 0,16 
 vide (persistant): 0,07 

Mise à jour: R2018b et R2019a: aucun changement

Aucun changement significatif. Je ne prends pas la peine d'inclure les résultats du test.

Code source des références

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

213
Andrew Janke

La classe handle a une surcharge supplémentaire de suivi de toutes les références à elle-même à des fins de nettoyage.

Essayez la même expérience sans utiliser la classe handle et voyez quels sont vos résultats.

3
MikeEL

En fait aucun problème avec votre code mais c'est un problème avec Matlab. Je pense que c'est une sorte de jeu à ressembler. Ce n'est rien d'autre que des frais généraux pour compiler le code de la classe. J'ai fait le test avec un 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% poignée

t2 =

12,0042% valeur

t3 =

0.5489  % vector

t4 =

0.0707 % structure 

Par conséquent, pour des performances efficaces, évitez d'utiliser OOP à la place, la structure est un bon choix pour regrouper les variables

1
Ahmad

Les performances OO dépendent considérablement de la version MATLAB utilisée. Je ne peux pas commenter toutes les versions, mais sachez par expérience que 2012a est bien meilleur que les versions 2010. Pas de repères et donc pas de chiffres à présenter. Mon code, écrit exclusivement à l'aide de classes de descripteurs et écrit sous 2012a, ne fonctionnera pas du tout sous les versions antérieures.

1
HG Bruce