Ja eksperymentuje z MATLAB OOP , jako początek I naśladował moje C ++ 's klasy Logger i Kładę wszystkie moje ciąg funkcji pomocniczych w klasy String, myśląc, że byłoby wspaniale móc robić takie rzeczy jak a + b
, a == b
, a.find( b )
zamiast strcat( a b )
, strcmp( a, b )
, pobrać pierwszy element strfind( a, b )
itp.
Problem: spowolnienie
Skorzystałem z powyższych rzeczy i od razu zauważyłem drastyczne spowolnienie. Czy robię to źle (co jest z pewnością możliwe, ponieważ mam raczej ograniczone doświadczenie w MATLAB-ie), czy też OOP MATLABa wprowadza po prostu dużo narzutów?
Mój przypadek testowy
Oto prosty test, który przeprowadziłem dla ciągu znaków, po prostu dodając ciąg i ponownie usuwając dołączoną część:
Uwaga: nie pisz takiej klasy String w prawdziwym kodzie! Matlab ma teraz natywny
string
typ tablicy i powinieneś go zamiast tego użyć.
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' );
Wyniki
Całkowity czas w sekundach dla 1000 iteracji:
btest 0.550 (z String.SetLength 0,138, String.plus 0,065, String.Length 0,057)
co najmniej 0,015
Wyniki dla systemu loggera są podobne: 0,1 sekundy na 1000 wywołań do frpintf( 1, 'test\n' )
, 7 (!) Sekund na 1000 wywołań mojego systemu przy użyciu klasy String wewnętrznie (OK, ma dużo więcej logiki, ale w porównaniu z C ++: narzut mojego systemu, który używa std::string( "blah" )
i std::cout
po stronie wyjściowej w porównaniu do zwykłego, std::cout << "blah"
jest rzędu 1 milisekundy.)
Czy to tylko narzut podczas wyszukiwania funkcji klas / pakietów?
Ponieważ MATLAB jest interpretowany, musi sprawdzić definicję funkcji / obiektu w czasie wykonywania. Zastanawiałem się więc, że być może znacznie więcej narzutów wiąże się z wyszukiwaniem klas lub funkcji pakietu w porównaniu z funkcjami, które są na ścieżce. Próbowałem to przetestować i stało się dziwniejsze. Aby wykluczyć wpływ klas / obiektów, porównałem wywołanie funkcji w ścieżce z funkcją w pakiecie:
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
Wyniki zebrane w taki sam sposób jak powyżej:
atest 0,004 sek., 0,001 sek. w ctest
btest 0,060 sek., 0,014 sek. w użytkowaniu .ctest
Czy więc cały ten narzut pochodzi tylko z MATLAB-a spędzającego czas na szukaniu definicji dla jego implementacji OOP, podczas gdy ten narzut nie dotyczy funkcji, które są bezpośrednio na ścieżce?
for i = 1:this.get_n_quantities() if(strcmp(id,this.get_quantity_rlz(i).get_id())) ix = i; end end
zajmuje 2,2 sekundy, podczas gdynq = 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
zajmuje 0,01, dwa zamówienia magazynkaOdpowiedzi:
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.
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:
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)
.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ć.
Myślę, że rezultatem tego jest to, że:
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!)Aktualizacja: R2014a
Zrekonstruowałem kod testowy i uruchomiłem go na R2014a.
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 szybkamethod(obj)
i znacznie szybsza niż starsze obiekty OOP.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.
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
źródło
Klasa handle ma dodatkowe obciążenie związane ze śledzeniem wszystkich odwołań do samej siebie w celu czyszczenia.
Spróbuj tego samego eksperymentu bez użycia klasy handle i zobacz, jakie są wyniki.
źródło
Wydajność OO zależy w dużym stopniu od używanej wersji MATLAB-a. Nie mogę komentować wszystkich wersji, ale z doświadczenia wiem, że 2012a jest znacznie ulepszona w stosunku do wersji 2010. Brak testów porównawczych, a więc żadnych liczb do przedstawienia. Mój kod, napisany wyłącznie przy użyciu klas obsługi i napisany pod 2012a, w ogóle nie będzie działał we wcześniejszych wersjach.
źródło
Właściwie nie ma problemu z Twoim kodem, ale jest to problem z Matlabem. Myślę, że jest to rodzaj zabawy, aby wyglądać. Kompilacja kodu klasy to nic innego jak obciążenie. Zrobiłem test z prostym punktem klasy (raz jako uchwyt) i drugim (raz jako klasa wartości)
oto test
Wyniki t1 =
12.0212% Uchwyt
t2 =
12,0042% wartości
t3 =
t4 =
Dlatego w celu zapewnienia wydajnej wydajności należy unikać używania OOP, zamiast tego struktura jest dobrym wyborem do grupowania zmiennych
źródło