J'expérimente avec Matlab POO , comme un début j'imité ma classes C ++ Logger s et je suis en train de toutes mes fonctions d'aide à cordes dans une classe String, pensant que ce serait génial de pouvoir faire des choses comme a + b
, a == b
, au a.find( b )
lieu 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 mis les choses ci-dessus à profit et j'ai immédiatement remarqué un ralentissement drastique . Est-ce que je fais mal (ce qui est certainement possible car j'ai une expérience MATLAB plutôt limitée), ou est-ce que la POO de MATLAB introduit simplement beaucoup de frais généraux?
Mon cas de test
Voici le test simple que j'ai fait pour la chaîne, en ajoutant simplement une chaîne et en supprimant à nouveau la partie ajoutée:
Remarque: n'écrivez pas réellement une classe String comme celle-ci dans du code réel! Matlab a
string
maintenant un type de tableau natif , et vous devriez l'utiliser à la place.
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 resultats
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 plus 0,015
Les résultats pour le système de journalisation sont les mêmes: 0,1 seconde pour 1000 appels à frpintf( 1, 'test\n' )
, 7 (!) Secondes pour 1000 appels à mon système lors de l'utilisation de la classe String en interne (OK, il y a beaucoup plus de logique, mais à comparer avec C ++: la surcharge de mon système qui utilise std::string( "blah" )
et std::cout
du côté sortie vs plain 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 que peut-être beaucoup plus de frais généraux sont impliqués dans la recherche de la fonction de classe ou de package par rapport aux fonctions qui se trouvent dans le chemin. J'ai essayé de tester ça, et ça devient plus étrange. Pour exclure l'influence des classes / objets, j'ai comparé l'appel d'une fonction dans le chemin à 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 plus 0,004 s, 0,001 s au test
btest 0,060 sec, 0,014 sec dans le test util.
Alors, est-ce que toute cette surcharge vient juste du temps passé par MATLAB à rechercher des définitions pour son implémentation POO, alors que cette surcharge n'est pas là pour les fonctions qui sont directement dans le chemin?
for i = 1:this.get_n_quantities() if(strcmp(id,this.get_quantity_rlz(i).get_id())) ix = i; end end
prend 2,2 sec, tandis quenq = this.get_n_quantities(); a = this.get_quantity_realizations(); for i = 1:nq c = a{i}; if(strcmp(id,c.get_id())) ix = i; end end
prend 0,01, deux ordres de magRéponses:
Je travaille avec OO MATLAB depuis un moment et j'ai fini par examiner des problèmes de performances similaires.
La réponse courte est: oui, la POO de MATLAB est plutôt lente. Il y a une surcharge d'appel de méthode substantielle, plus élevée que les langages OO traditionnels, et vous ne pouvez pas faire grand-chose à ce sujet. 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 le surdébit par appel n'est pas une priorité élevée.
J'ai comparé les performances en écrivant des fonctions «nop» à ne rien faire comme les différents types de fonctions et de méthodes. Voici quelques résultats typiques.
Résultats similaires sur R2008a à R2009b. Ceci est sur Windows XP x64 exécutant MATLAB 32 bits.
Le "Java nop ()" est une méthode Java à ne rien faire 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'entraîne 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 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()
syntaxe est plus lente que lanop(obj)
syntaxe, 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, appeleznop(obj)
.Dire pourquoi il en est ainsi ne serait que spéculation de ma part. Les internes OO du moteur MATLAB ne sont pas publics. Ce n'est pas un problème interprété ou 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.) Il se peut que les définitions de classe de MATLAB soient liées à l'état du système de fichiers d'une manière que de nombreuses autres langues ne le sont pas.
Alors que faire?
Une approche MATLAB idiomatique consiste à "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 comme entrées 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, suffisamment, espérons-le, que la surcharge de répartition ne constitue 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 manière à ce que les objets soient les plus petits blocs de construction, aussi spécifiques que possible (c'est-à-dire, de nombreuses classes différentes), et vous les composez dans des tableaux, des objets de collection, etc., et les parcourez avec des boucles. Pour créer des classes MATLAB rapides, retournez cette approche à l'envers. Avoir des classes plus grandes dont les champs sont des tableaux et appeler des méthodes vectorisées sur ces tableaux.
Le but est d'arranger votre code pour qu'il joue sur les forces du langage - gestion des tableaux, mathématiques vectorisées - et éviter les points faibles.
EDIT: Depuis le post original, R2010b et R2011a sont sortis. L'image globale est la même, les appels MCOS devenant un peu plus rapides, et les appels aux méthodes Java et à l'ancienne devenant plus lents .
EDIT: J'avais l'habitude d'avoir quelques notes ici sur la "sensibilité du chemin" avec un tableau supplémentaire des horaires 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 graphique 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 mettre à jour cela.
Je pense que le résultat est que:
foo(obj)
syntaxe. La vitesse des méthodes n'est donc plus une raison de s'en tenir aux anciennes classes dans la plupart des cas. (Félicitations, MathWorks!)Mise à jour: R2014a
J'ai reconstruit le code d'analyse comparative et l'ai exécuté sur R2014a.
Mise à jour: R2015b: les objets sont devenus plus rapides!
Voici les résultats de R2015b, aimablement fournis par @Shaked. C'est un grand changement: la POO est beaucoup plus rapide, et maintenant la
obj.method()
syntaxe est aussi rapidemethod(obj)
et beaucoup plus rapide que les objets OOP hérités.Mise à jour: R2018a
Voici les résultats de 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'une année à l'autre. Notamment, les poignées de fonctions anonymes sont devenues beaucoup plus rapides.
Mise à jour: R2018b et R2019a: aucun changement
Aucun changement significatif. Je ne prends pas la peine d'inclure les résultats des tests.
Code source pour les benchmarks
J'ai mis le code source de ces benchmarks sur GitHub, publié sous la licence MIT. https://github.com/apjanke/matlab-bench
la source
La classe handle a une surcharge supplémentaire du 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.
la source
Les performances OO dépendent de manière significative de la version MATLAB utilisée. Je ne peux pas faire de commentaires sur toutes les versions, mais je sais par expérience que 2012a est bien améliorée par rapport aux versions 2010. Pas de benchmarks et donc pas de chiffres à présenter. Mon code, exclusivement écrit à l'aide de classes handle et écrit sous 2012a ne fonctionnera pas du tout sous les versions antérieures.
la source
En fait pas de problème avec votre code mais c'est un problème avec Matlab. Je pense que c'est une sorte de jeu pour ressembler. La compilation du code de classe n'est rien qu'une surcharge. 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)
voici le test
Les résultats t1 =
12.0212% Poignée
t2 =
12,0042% valeur
t3 =
t4 =
Par conséquent, pour des performances efficaces, évitez d'utiliser la POO à la place, la structure est un bon choix pour regrouper les variables
la source