Ich habe eine Weile mit OO MATLAB gearbeitet und mich am Ende mit ähnlichen Leistungsproblemen befasst.
Die kurze Antwort lautet: Ja, MATLABs OOP ist ziemlich langsam. Es gibt einen erheblichen Aufwand für Methodenaufrufe, der höher ist als bei herkömmlichen OO-Sprachen, und Sie können nicht viel dagegen tun. Ein Grund dafür kann sein, dass das idiomatische MATLAB "vektorisierten" Code verwendet, um die Anzahl der Methodenaufrufe zu reduzieren, und dass der Overhead pro Anruf keine hohe Priorität hat.
Ich habe die Leistung durch Schreiben von "NOP" -Funktionen als verschiedene Arten von Funktionen und Methoden bewertet. Hier sind einige typische Ergebnisse.
>> call_nops
Computer: PCWIN Release: 2009b
Aufruf jeder Funktion / Methode 100000 Mal
nop () Funktion: 0.02261 sec 0.23 usec pro Aufruf
nop1-5 () -Funktionen: 0,02182 Sek. 0,22 usec pro Aufruf
Unterfunktion nop (): 0,02244 Sek. 0,22 usec pro Aufruf
@ () [] anonyme Funktion: 0,08461 Sek. 0,85 usec pro Anruf
nop (obj) -Methode: 0,24664 Sek. 2,47 usec pro Aufruf
nop1-5 (obj) -Methoden: 0,23469 Sek. 2,35 usec pro Aufruf
private Funktion nop (): 0,02197 Sek. 0,22 usec pro Aufruf
classdef nop (obj): 0,90547 Sek. 9,05 usec pro Anruf
classdef obj.nop (): 1.75522 Sek. 17.55 usec pro Aufruf
classdef private_nop (obj): 0,84738 Sek. 8,47 usec pro Aufruf
classdef nop (obj) (m-Datei): 0,90560 Sek. 9,06 usec pro Aufruf
classdef class.staticnop (): 1.16361 Sek. 11.64 usec pro Aufruf
Java nop (): 2.43035 Sek. 24.30 usec pro Aufruf
Java static_nop (): 0,87682 Sek. 8,77 usec pro Aufruf
Java nop () von Java: 0,00014 Sek. 0,00 usec pro Aufruf
MEX mexnop (): 0,11409 Sek. 1,14 usec pro Anruf
C nop (): 0,00001 Sek. 0,00 usec pro Anruf
Ähnliche Ergebnisse für R2008a bis R2009b. Dies ist unter Windows XP x64 mit 32-Bit-MATLAB möglich.
"Java nop ()" ist eine Nichtstun-Java-Methode, die aus einer M-Code-Schleife heraus aufgerufen wird und den Versandaufwand von MATLAB zu Java bei jedem Aufruf enthält. "Java nop () from Java" ist dasselbe, was in einer Java for () - Schleife aufgerufen wird, und verursacht keine Grenzstrafe. Nehmen Sie die Java- und C-Timings mit einem Körnchen Salz; Ein cleverer Compiler könnte die Aufrufe komplett optimieren.
Der Paket-Scoping-Mechanismus ist neu und wird ungefähr zur gleichen Zeit wie die classdef-Klassen eingeführt. Sein Verhalten kann zusammenhängen.
Einige vorläufige Schlussfolgerungen:
- Methoden sind langsamer als Funktionen.
- Neue Stilmethoden (classdef) sind langsamer als alte Stilmethoden.
- Die neue
obj.nop()
Syntax ist langsamer als die nop(obj)
Syntax, selbst für dieselbe Methode für ein classdef-Objekt. Gleiches gilt für Java-Objekte (nicht gezeigt). Wenn Sie schnell gehen möchten, rufen Sie an nop(obj)
.
- Der Aufwand für Methodenaufrufe ist in 64-Bit-MATLAB unter Windows höher (ca. 2x). (Nicht gezeigt.)
- Der Versand der MATLAB-Methode ist langsamer als in einigen anderen Sprachen.
Zu sagen, warum das so ist, wäre nur Spekulation meinerseits. Die OO-Interna der MATLAB-Engine sind nicht öffentlich. Es ist an sich kein interpretiertes oder kompiliertes Problem - MATLAB hat eine JIT -, aber die lockerere Eingabe und Syntax von MATLAB kann zur Laufzeit mehr Arbeit bedeuten. (Zum Beispiel können Sie nicht allein anhand der Syntax erkennen, ob "f (x)" ein Funktionsaufruf oder ein Index in einem Array ist. Dies hängt vom Status des Arbeitsbereichs zur Laufzeit ab.) Dies kann daran liegen, dass die Klassendefinitionen von MATLAB verknüpft sind zum Dateisystemstatus in einer Weise, wie es viele andere Sprachen nicht sind.
Also, was tun?
Ein idiomatischer MATLAB-Ansatz besteht darin, Ihren Code zu "vektorisieren", indem Sie Ihre Klassendefinitionen so strukturieren, dass eine Objektinstanz ein Array umschließt. Das heißt, jedes seiner Felder enthält parallele Arrays (in der MATLAB-Dokumentation als "planare" Organisation bezeichnet). Anstatt ein Array von Objekten mit Feldern zu haben, die skalare Werte enthalten, definieren Sie Objekte, die selbst Arrays sind, und lassen Sie die Methoden Arrays als Eingaben verwenden und vektorisierte Aufrufe für die Felder und Eingaben ausführen. Dies reduziert die Anzahl der durchgeführten Methodenaufrufe, hoffentlich genug, dass der Versandaufwand kein Engpass ist.
Die Nachahmung einer C ++ - oder Java-Klasse in MATLAB ist wahrscheinlich nicht optimal. Java / C ++ - Klassen werden normalerweise so erstellt, dass Objekte die kleinsten Bausteine sind, so spezifisch wie möglich (dh viele verschiedene Klassen), und Sie komponieren sie in Arrays, Sammlungsobjekten usw. und durchlaufen sie mit Schleifen. Um schnelle MATLAB-Klassen zu erstellen, drehen Sie diesen Ansatz um. Haben Sie größere Klassen, deren Felder Arrays sind, und rufen Sie vektorisierte Methoden für diese Arrays auf.
Der Punkt ist, Ihren Code so anzuordnen, dass er die Stärken der Sprache ausspielt - Array-Handhabung, vektorisierte Mathematik - und die Schwachstellen vermeidet.
EDIT: Seit dem ursprünglichen Beitrag sind R2010b und R2011a herausgekommen. Das Gesamtbild ist das gleiche: MCOS-Aufrufe werden etwas schneller und Java- und Methodenaufrufe im alten Stil werden langsamer .
BEARBEITEN: Früher hatte ich hier einige Hinweise zur "Pfadempfindlichkeit" mit einer zusätzlichen Tabelle mit Funktionsaufrufzeiten, in der die Funktionszeiten durch die Konfiguration des Matlab-Pfads beeinflusst wurden. Dies scheint jedoch eine Abweichung von meinem speziellen Netzwerk-Setup bei gewesen zu sein die Zeit. Die obige Tabelle zeigt die Zeiten, die für das Überwiegen meiner Tests im Laufe der Zeit typisch sind.
Update: R2011b
BEARBEITEN (13.02.2012): R2011b ist nicht verfügbar, und das Leistungsbild hat sich ausreichend geändert, um dies zu aktualisieren.
Arch: PCWIN Veröffentlichung: 2011b
Maschine: R2011b, Windows XP, 8x Core i7-2600 bei 3,40 GHz, 3 GB RAM, NVIDIA NVS 300
Jede Operation 100000 Mal ausführen
Stil insgesamt µsec pro Anruf
nop () -Funktion: 0,01578 0,16
nop (), 10x Loop Unroll: 0.01477 0.15
nop (), 100x Loop Unroll: 0.01518 0.15
Unterfunktion nop (): 0.01559 0.16
@ () [] anonyme Funktion: 0.06400 0.64
nop (obj) -Methode: 0,28482 2,85
nop () private Funktion: 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 Konstante: 1.51890 15.19
classdef-Eigenschaft: 0.12992 1.30
classdef-Eigenschaft mit Getter: 1.39912 13.99
+ pkg.nop () Funktion: 0.87345 8.73
+ pkg.nop () von innen + 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 () von Java: 0,00010 0,00
MEX mexnop (): 0,08709 0,87
C nop (): 0,00001 0,00
j () (eingebaut): 0,00251 0,03
Ich denke, das Ergebnis ist:
- MCOS / classdef-Methoden sind schneller. Die Kosten sind jetzt ungefähr gleich hoch wie bei alten Stilklassen, solange Sie die
foo(obj)
Syntax verwenden. Daher ist die Methodengeschwindigkeit in den meisten Fällen kein Grund mehr, sich an alte Stilklassen zu halten. (Kudos, MathWorks!)
- Das Einfügen von Funktionen in Namespaces verlangsamt diese. (Nicht neu in R2011b, nur neu in meinem Test.)
Update: R2014a
Ich habe den Benchmarking-Code rekonstruiert und auf R2014a ausgeführt.
Matlab R2014a auf PCWIN64
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 unter PCWIN64 Windows 7 6.1 (eilonwy-win7)
Maschine: Core i7-3615QM CPU bei 2,30 GHz, 4 GB RAM (VMware Virtual Platform)
nIters = 100000
Betriebszeit (µsec)
nop () Funktion: 0.14
Unterfunktion nop (): 0.14
@ () [] anonyme Funktion: 0.69
nop (obj) -Methode: 3.28
nop () private fcn auf @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 Konstante: 4.18
classdef-Eigenschaft: 1.18
classdef-Eigenschaft mit Getter: 19.26
+ Funktion pkg.nop (): 4.03
+ pkg.nop () von innen + 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 () von Java: 0.01
MEX mexnop (): 0,91
eingebautes j (): 0,02
struct s.foo field access: 0.14
isempty (persistent): 0,00
Update: R2015b: Objekte wurden schneller!
Hier sind die Ergebnisse von R2015b, freundlicherweise zur Verfügung gestellt von @Shaked. Dies ist eine große Änderung: OOP ist erheblich schneller, und jetzt ist die obj.method()
Syntax so schnell wie method(obj)
und viel schneller als bei älteren OOP-Objekten.
Matlab R2015b auf PCWIN64
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 unter PCWIN64 Windows 8 6.2 (nanit-shaked)
Maschine: Core i7-4720HQ-CPU bei 2,60 GHz, 16 GB RAM (20378)
nIters = 100000
Betriebszeit (µsec)
nop () Funktion: 0.04
Unterfunktion nop (): 0.08
@ () [] anonyme Funktion: 1.83
nop (obj) -Methode: 3.15
nop () private fcn auf @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 Konstante: 0,25
classdef-Eigenschaft: 0,25
classdef-Eigenschaft mit Getter: 0.64
+ pkg.nop () Funktion: 0.04
+ pkg.nop () von innen + 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 Uhr
Java Klass.staticNop (): 24.48
Java obj.nop () von Java: 0.01
MEX mexnop (): 0,33
eingebautes j (): 0,15
struct s.foo field access: 0.25
isempty (persistent): 0,13
Update: R2018a
Hier sind die Ergebnisse von R2018a. Es ist nicht der große Sprung, den wir gesehen haben, als die neue Ausführungs-Engine in R2015b eingeführt wurde, aber es ist immer noch eine spürbare Verbesserung gegenüber dem Vorjahr. Insbesondere anonyme Funktionshandles wurden viel schneller.
Matlab R2018a auf MACI64
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 unter MACI64 Mac OS X 10.13.5 (eilonwy)
Maschine: Core i7-3615QM CPU bei 2,30 GHz, 16 GB RAM
nIters = 100000
Betriebszeit (µsec)
nop () Funktion: 0.03
Unterfunktion nop (): 0.04
@ () [] anonyme Funktion: 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-Konstante: 0,16
classdef-Eigenschaft: 0.13
classdef-Eigenschaft mit Getter: 0.39
+ pkg.nop () Funktion: 0.02
+ pkg.nop () von innen + 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 () von Java: 0.03
MEX mexnop (): 3,54
eingebautes j (): 0,10
struct s.foo field access: 0.16
isempty (persistent): 0,07
Update: R2018b und R2019a: Keine Änderung
Keine wesentlichen Änderungen. Ich mache mir nicht die Mühe, die Testergebnisse einzubeziehen.
Quellcode für Benchmarks
Ich habe den Quellcode für diese Benchmarks auf GitHub veröffentlicht, der unter der MIT-Lizenz veröffentlicht wurde. https://github.com/apjanke/matlab-bench