Pracuję z OO MATLAB już od jakiegoś czasu i ostatecznie przyjrzałem się podobnym problemom z wydajnością.
Krótka odpowiedź brzmi: tak, OOP MATLABA jest trochę powolne. Istnieje znaczny narzut wywołań metod, wyższy niż w przypadku popularnych języków obiektowych i niewiele można z tym zrobić. Jednym z powodów może być to, że idiomatyczny MATLAB używa kodu „wektoryzowanego” w celu zmniejszenia liczby wywołań metod, a narzut na wywołanie nie ma wysokiego priorytetu.
Testowałem wydajność, pisząc funkcje „nic nie rób” jako różne typy funkcji i metod. Oto kilka typowych wyników.
>> call_nops
Komputer: PCWIN Wydanie: 2009b
Wywołanie każdej funkcji / metody 100000 razy
Funkcja nop (): 0,02261 s 0,23 usek na wywołanie
Funkcje nop1-5 (): 0,02182 s 0,22 usek na wywołanie
Podfunkcja nop (): 0,02244 s 0,22 usek na wywołanie
@ () [] funkcja anonimowa: 0,08461 s 0,85 usek na połączenie
nop (obj): 0,24664 s 2,47 usek na wywołanie
metody nop1-5 (obj): 0,23469 s 2,35 usek na wywołanie
funkcja prywatna nop (): 0,02197 s 0,22 usek na wywołanie
classdef nop (obj): 0,90547 s 9,05 usec na wywołanie
classdef obj.nop (): 1,75522 s 17,55 usek na wywołanie
classdef private_nop (obj): 0,84738 s 8,47 usek na połączenie
classdef nop (obj) (plik m): 0,90560 s 9,06 usek na wywołanie
classdef class.staticnop (): 1,16361 s 11,64 usec na wywołanie
Java nop (): 2,43035 s 24,30 usek na połączenie
Java static_nop (): 0,87682 s 8,77 usec na wywołanie
Java nop () z języka Java: 0,00014 s 0,00 usec na wywołanie
MEX mexnop (): 0,11409 sek. 1,14 usek na połączenie
C nop (): 0,00001 s 0,00 usec na połączenie
Podobne wyniki dla R2008a do R2009b. Dotyczy to systemu Windows XP x64 z 32-bitowym programem MATLAB.
Metoda „Java nop ()” jest metodą Java, która nie robi nic, wywoływana z pętli kodu M i zawiera narzut z MATLAB-do-Java na każde wywołanie. „Java nop () z języka Java” to to samo, co wywoływane w pętli for () języka Java i nie powoduje takiej kary za granicę. Spójrz na czasy Java i C z przymrużeniem oka; sprytny kompilator mógłby całkowicie zoptymalizować wywołania.
Mechanizm określania zakresu pakietów jest nowy, wprowadzony mniej więcej w tym samym czasie co klasy classdef. Jego zachowanie może być powiązane.
Kilka wstępnych wniosków:
- Metody są wolniejsze niż funkcje.
- Metody nowego stylu (classdef) są wolniejsze niż metody starego stylu.
- Nowa
obj.nop()
składnia jest wolniejsza niż nop(obj)
składnia, nawet dla tej samej metody w obiekcie classdef. To samo dotyczy obiektów Java (nie pokazano). Jeśli chcesz jechać szybko, zadzwoń nop(obj)
.
- Narzut wywołania metody jest wyższy (około 2x) w 64-bitowej wersji MATLAB w systemie Windows. (Nie pokazany.)
- Wysyłanie metody MATLAB jest wolniejsze niż w przypadku niektórych innych języków.
Mówienie, dlaczego tak jest, byłoby z mojej strony tylko spekulacją. Wewnętrzne elementy OO silnika MATLAB nie są publiczne. Nie jest to problem interpretowany i skompilowany per se - MATLAB ma JIT - ale luźniejsze pisanie i składnia MATLAB-a może oznaczać więcej pracy w czasie wykonywania. (Np. Nie można stwierdzić na podstawie samej składni, czy „f (x)” jest wywołaniem funkcji czy indeksem tablicy; zależy to od stanu obszaru roboczego w czasie wykonywania). Może to być spowodowane tym, że definicje klas MATLAB-a są powiązane do stanu systemu plików w sposób, w jaki nie ma wielu innych języków.
Więc co robić?
Idiomatycznym podejściem MATLAB do tego jest „wektoryzacja” kodu poprzez strukturyzację definicji klas w taki sposób, że instancja obiektu opakowuje tablicę; to znaczy, że każde z jego pól zawiera tablice równoległe (zwane organizacją „planarną” w dokumentacji MATLAB). Zamiast mieć tablicę obiektów, z których każdy ma pola przechowujące wartości skalarne, zdefiniuj obiekty, które same są tablicami, i niech metody przyjmują tablice jako dane wejściowe i wykonują wektoryzowane wywołania pól i danych wejściowych. Zmniejsza to liczbę wywołań metod, miejmy nadzieję, że narzut wysyłania nie jest wąskim gardłem.
Naśladowanie klasy C ++ lub Java w MATLAB-u prawdopodobnie nie będzie optymalne. Klasy Java / C ++ są zwykle budowane w taki sposób, że obiekty są najmniejszymi blokami konstrukcyjnymi, tak specyficznymi, jak to tylko możliwe (to jest wiele różnych klas), i tworzysz je w tablicach, obiektach kolekcji itp. I iterujesz po nich za pomocą pętli. Aby tworzyć szybkie zajęcia MATLAB, odwróć to podejście na lewą stronę. Miej większe klasy, których pola są tablicami, i wywołuj metody wektoryzowane na tych tablicach.
Chodzi o to, aby zaaranżować kod tak, aby wykorzystywał mocne strony języka - obsługę tablic, matematykę wektorową - i unikać słabych punktów.
EDYCJA: Od czasu oryginalnego postu pojawiły się R2010b i R2011a. Ogólny obraz jest taki sam, przy czym wywołania MCOS stają się nieco szybsze, a wywołania Java i metody starego typu stają się wolniejsze .
EDYCJA: Kiedyś miałem tutaj kilka uwag na temat „czułości ścieżki” z dodatkową tabelą czasów wywołań funkcji, gdzie na czasy funkcji wpływał sposób konfiguracji ścieżki Matlab, ale wydaje się, że była to aberracja mojej konkretnej konfiguracji sieci w czas. Powyższy wykres odzwierciedla czasy typowe dla przeważania moich testów w czasie.
Aktualizacja: R2011b
EDYCJA (13.02.2012): R2011b jest niedostępny, a obraz wydajności zmienił się na tyle, aby to zaktualizować.
Arch: PCWIN Wydanie: 2011b
Maszyna: R2011b, Windows XP, 8x Core i7-2600 @ 3,40 GHz, 3 GB RAM, NVIDIA NVS 300
Wykonywanie każdej operacji 100000 razy
styl łącznie w µs na połączenie
Funkcja nop (): 0,01578 0,16
nop (), 10x rozwijanie pętli: 0,01477 0,15
nop (), rozwinięcie pętli 100x: 0,01518 0,15
Podfunkcja nop (): 0,01559 0,16
@ () [] funkcja anonimowa: 0,06400 0,64
nop (obj), metoda: 0,28482 2,85
funkcja prywatna 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
stała classdef: 1,51890 15,19
właściwość classdef: 0,12992 1,30
Właściwość classdef z funkcją getter: 1.39912 13.99
Funkcja + pkg.nop (): 0,87345 8,73
+ pkg.nop () od wewnątrz + 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 () z Java: 0,00010 0,00
MEX mexnop (): 0,08709 0,87
C nop (): 0,00001 0,00
j () (wbudowany): 0,00251 0,03
Myślę, że rezultatem tego jest to, że:
- Metody MCOS / classdef są szybsze. Koszt jest teraz porównywalny z klasami w starym stylu, o ile używasz
foo(obj)
składni. Dlatego w większości przypadków szybkość metody nie jest już powodem do trzymania się klas starego stylu. (Uznanie, MathWorks!)
- Umieszczanie funkcji w przestrzeniach nazw powoduje ich spowolnienie. (Nie nowy w R2011b, tylko nowy w moim teście).
Aktualizacja: R2014a
Zrekonstruowałem kod testowy i uruchomiłem go na R2014a.
Matlab R2014a na PCWIN64
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 na PCWIN64 Windows 7 6.1 (eilonwy-win7)
Maszyna: procesor Core i7-3615QM @ 2,30 GHz, 4 GB pamięci RAM (platforma wirtualna VMware)
nIters = 100000
Czas pracy (µs)
Funkcja nop (): 0,14
Podfunkcja nop (): 0.14
@ () [] funkcja anonimowa: 0,69
nop (obj), metoda: 3.28
nop () prywatny fcn na @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
stała classdef: 4.18
właściwość classdef: 1.18
Właściwość classdef z funkcją getter: 19.26
+ funkcja pkg.nop (): 4.03
+ pkg.nop () od wewnątrz + pkg: 4.16
feval ('nop'): 2,31
feval (@nop): 0,22
eval ('nop'): 59,46
Java obj.nop (): 26.07.2016
Java nop (obj): 3.72
Java feval ('nop', obj): 9.25
Java Klass.staticNop (): 10.54
Java obj.nop () z Java: 0.01
MEX mexnop (): 0,91
wbudowany j (): 0,02
Dostęp do pola struct s.foo: 0.14
isempty (trwałe): 0,00
Aktualizacja: R2015b: obiekty stały się szybsze!
Oto wyniki R2015b, udostępnione przez @Shaked. To duża zmiana: OOP jest znacznie szybsze, a teraz obj.method()
składnia jest równie szybka method(obj)
i znacznie szybsza niż starsze obiekty OOP.
Matlab R2015b na PCWIN64
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 na PCWIN64 Windows 8 6.2 (wstrząśnięty nanit)
Maszyna: procesor Core i7-4720HQ @ 2,60 GHz, 16 GB RAM (20378)
nIters = 100000
Czas pracy (µs)
Funkcja nop (): 0,04
Podfunkcja nop (): 0,08
@ () [] funkcja anonimowa: 1,83
nop (obj), metoda: 3.15
nop () prywatny fcn na @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 stała: 0,25
właściwość classdef: 0,25
Właściwość classdef z funkcją pobierającą: 0.64
Funkcja + pkg.nop (): 0,04
+ pkg.nop () od wewnątrz + 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 () z Java: 0.01
MEX mexnop (): 0,33
wbudowany j (): 0,15
Dostęp do pola struct s.foo: 0.25
isempty (trwałe): 0,13
Aktualizacja: R2018a
Oto wyniki R2018a. Nie jest to ogromny skok, który widzieliśmy, gdy nowy silnik wykonawczy został wprowadzony w R2015b, ale nadal jest to znaczna poprawa z roku na rok. Warto zauważyć, że anonimowe uchwyty funkcji stały się znacznie szybsze.
Matlab R2018a na MACI64
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 na MACI64 Mac OS X 10.13.5 (eilonwy)
Maszyna: procesor Core i7-3615QM @ 2,30 GHz, 16 GB pamięci RAM
nIters = 100000
Czas pracy (µs)
Funkcja nop (): 0,03
Podfunkcja nop (): 0,04
@ () [] funkcja anonimowa: 0,16
classdef nop (obj): 0.16
classdef obj.nop (): 0.17
classdef pivate_nop (obj): 0.16
classdef class.static_nop (): 0,03
stała classdef: 0,16
właściwość classdef: 0.13
Właściwość classdef z funkcją getter: 0,39
+ Funkcja pkg.nop (): 0,02
+ pkg.nop () od wewnątrz + 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.0
Java feval ('nop', obj): 21,85
Java Klass.staticNop (): 45.49
Java obj.nop () z Java: 0.03
MEX mexnop (): 3.54
wbudowany j (): 0,10
dostęp do pola struct s.foo: 0.16
isempty (trwałe): 0,07
Aktualizacja: R2018b i R2019a: bez zmian
Brak znaczących zmian. Nie kłopoczę się dołączaniem wyników testu.
Kod źródłowy dla testów porównawczych
Umieściłem kod źródłowy tych testów na GitHub, wydanym na licencji MIT. https://github.com/apjanke/matlab-bench