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?
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:
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)
.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.
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:
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!)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
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
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
Aucun changement significatif. Je ne prends pas la peine d'inclure les résultats du test.
J'ai mis le code source de ces benchmarks sur GitHub, publié sous la licence MIT. https://github.com/apjanke/matlab-bench
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.
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
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.