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.
>> call_nops
Ordinateur: PCWIN Version: 2009b
Appel de chaque fonction / méthode 100000 fois
Fonction nop (): 0,02261 sec 0,23 usec par appel
Fonctions nop1-5 (): 0,02182 sec 0,22 usec par appel
Sous-fonction nop (): 0,02244 sec 0,22 usec par appel
@ () [] fonction anonyme: 0,08461 s 0,85 usec par appel
Méthode nop (obj): 0,24664 sec 2,47 usec par appel
méthodes nop1-5 (obj): 0,23469 sec 2,35 usec par appel
nop () fonction privée: 0,02197 sec 0,22 usec par appel
classdef nop (obj): 0,90547 sec 9,05 usec par appel
classdef obj.nop (): 1,75522 sec 17,55 usec par appel
classdef private_nop (obj): 0,84738 s 8,47 usec par appel
classdef nop (obj) (m-file): 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. 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:
- Les méthodes sont plus lentes que les fonctions.
- Les méthodes de style nouveau (classdef) sont plus lentes que les méthodes de style ancien.
- La nouvelle
obj.nop()
syntaxe est plus lente que la nop(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, appelez nop(obj)
.
- La surcharge d'appel de méthode est plus élevée (environ 2x) dans MATLAB 64 bits sous Windows. (Pas montré.)
- La répartition des méthodes MATLAB est plus lente que dans certains autres langages.
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.
Arch: PCWIN Sortie: 2011b
Machine: R2011b, Windows XP, 8x Core i7-2600 à 3,40 GHz, 3 Go de RAM, NVIDIA NVS 300
Faire chaque opération 100000 fois
style total en µs par appel
Fonction nop (): 0,01578 0,16
nop (), 10x déroulement de la boucle: 0,01477 0,15
nop (), déroulement de boucle 100x: 0,01518 0,15
Sous-fonction nop (): 0,01559 0,16
@ () [] fonction anonyme: 0,06400 0,64
Méthode nop (obj): 0,28482 2,85
fonction privée nop (): 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 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 () depuis 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 désormais comparable à celui des classes de style ancien, tant que vous utilisez la
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!)
- 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'ai exécuté 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: processeur Core i7-3615QM à 2,30 GHz, 4 Go de RAM (plate-forme virtuelle VMware)
nItres = 100000
Temps 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): 4,88
classdef class.static_nop (): 11.81
constante classdef: 4,18
Propriété classdef: 1.18
propriété classdef avec getter: 19.26
+ Fonction pkg.nop (): 4.03
+ pkg.nop () de l'intérieur + pkg: 4.16
féval ('nop'): 2,31
féval (@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 () depuis Java: 0.01
MEX mexnop (): 0,91
intégré j (): 0,02
accès au champ struct s.foo: 0.14
isempty (persistant): 0,00
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 rapide method(obj)
et beaucoup plus rapide que les objets OOP hérités.
Matlab R2015b sur PCWIN64
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 sur PCWIN64 Windows 8 6.2 (nanit-shaked)
Machine: Processeur Core i7-4720HQ à 2,60 GHz, 16 Go de RAM (20378)
nItres = 100000
Temps 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 class.static_nop (): 0,05
constante classdef: 0,25
propriété classdef: 0.25
propriété classdef avec getter: 0.64
+ fonction pkg.nop (): 0,04
+ pkg.nop () de l'intérieur + pkg: 0,04
féval ('nop'): 8,26
féval (@nop): 0,63
eval ('nop'): 21,22
Java obj.nop (): 14.15
Java nop (obj): 2.50
Java feval ('nop', obj): 10h30
Java Klass.staticNop (): 24,48
Java obj.nop () depuis Java: 0.01
MEX mexnop (): 0,33
intégré j (): 0,15
accès au champ struct s.foo: 0,25
isempty (persistant): 0,13
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.
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: processeur Core i7-3615QM à 2,30 GHz, 16 Go de RAM
nItres = 100000
Temps 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
féval ('nop'): 15,62
féval (@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 () depuis Java: 0.03
MEX mexnop (): 3,54
intégré j (): 0,10
accès au champ struct s.foo: 0.16
isempty (persistant): 0,07
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