2012-10-16 11 views
8

Mam trochę kodu Matlab, który musi zostać przyspieszony. Poprzez profilowanie zidentyfikowałem określoną funkcję jako sprawcę spowolnienia wykonywania. Ta funkcja nazywa się setki tysięcy razy w pętli.Matlab: Czy wywoływanie tej samej funkcji mexa z pętli powoduje zbyt wiele narzutów?

Moją pierwszą myślą było przekonwertowanie funkcji na mex (używając Matlab Coder), aby przyspieszyć. Jednak wspólny rozsądek programowania mówi mi, że interfejs pomiędzy Matlabem i kodem mex doprowadziłby do pewnego narzutu, co oznacza, że ​​wywoływanie tej funkcji mex tysiące razy nie jest dobrym pomysłem. Czy to jest poprawne? A może Matlab robi trochę magii, gdy jest to ten sam mex, który jest wielokrotnie wywoływany, aby usunąć narzut?

Jeśli jest znaczący głową, myślę restrukturyzacji kodu, tak aby dodać pętlę do samej funkcji i następnie tworząc mex tego. Zanim to zrobię, chciałbym potwierdzić moje założenie, aby uzasadnić czas poświęcony na to.

Aktualizacja:

Próbowałem @ sugestią angainor, a stworzony donothing.m z następującego kodu:

function nothing = donothing(dummy) %#codegen 
nothing = dummy; 
end 

Potem stworzyliśmy funkcję mex od tego jak donothing_mex i próbowałem następujący kod :

tic; 
for i=1:1000000 
    donothing_mex(5); 
end 
toc; 

Rezultatem było to, że milion połączeń z funkcją zajęło około 9 sekund. Nie jest to znaczące obciążenie dla naszych celów, więc na razie myślę, że zmienię wywoływaną funkcję samodzielnie na mex. Jednak wywołanie funkcji z pętli, która wykonuje około miliona razy, wydaje się dość głupim pomysłem z perspektywy czasu, biorąc pod uwagę, że jest to kod krytyczny pod względem wydajności, więc przeniesienie pętli do funkcji mex jest nadal w książkach, ale o znacznie mniejszym priorytecie.

+1

Zdecydowanie przenieś pętlę do kodu. Powinno to wymagać tylko 2 lub 3 dodatkowych linii kodu mex i pozwoli Ci zaoszczędzić prawdopodobnie 8,5 sekundy z tego 9. – twerdster

+2

Sądząc po twoim komentarzu na @ Angainor's odpowiedź, podejście, które robisz, ma posmak [problemu XY] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), np. MEX, który chcesz utworzyć, aby rozwiązać problem z wydajnością * może * mieć znacznie szybsze rozwiązanie w Matlab, tylko taki, o którym wcześniej nie myślałeś. Czy możesz opublikować esencję obliczeń, które chcesz wykonać w pętli, którą teraz masz? –

+0

@RodyOldenhuis To także dobry punkt. [Przedwczesna optymalizacja jest źródłem wszelkiego zła] (http://shreevatsa.wordpress.com/2008/05/16/premature-optimization-is-the-root-of-all-evil/);) – angainor

Odpowiedz

5

Jak zwykle wszystko zależy od ilości pracy wykonywanej w pliku MEX. Narzut wywoływania funkcji MEX jest stały i nie zależy np. Od rozmiaru problemu. Oznacza to, że argumenty są kopiowane na nowe, tymczasowe tablice. Stąd, jeśli jest to wystarczająca ilość pracy, obciążenie MATLAB wywołania pliku MEX nie zostanie pokazane. W każdym razie, z mojego doświadczenia wynika, że ​​obciążenie wywołaniem MEX jest znaczące tylko wtedy, gdy po raz pierwszy wywoływana jest funkcja mex - biblioteka dynamiczna musi być ładowana, symbole rozwiązywane itd. Kolejne wywołania MEX mają bardzo mały narzut i są bardzo wydajne.

Prawie wszystko w MATLAB wiąże się z pewnym obciążeniem ze względu na charakter tego wysokiego poziomu języka. Chyba że masz kod, który na pewno jest w pełni skompilowany z JIT (ale wtedy nie potrzebujesz pliku mex :)) Więc masz wybór jednego narzutu nad drugim ..

Podsumowując - I nie obawiałbym się, że MEX wezwie nad głową.

Edit Jak często słyszałem tu i gdzie indziej, jedyną rozsądną rzeczą do zrobienia w każdym konkretnym przypadku jest oczywiście BENCHMARKU i sprawdzić je dla siebie. można łatwo oszacować połączeń napowietrznych MEX pisząc trywialny funkcji MEX:

#include "mex.h" 
void mexFunction(int nlhs, mxArray *plhs[ ], int nrhs, const mxArray *prhs[ ]) 
{  
} 

Na moim komputerze masz

tic; for i=1:1000000; mexFun; end; toc 
Elapsed time is 2.104849 seconds. 

To 2e-6s napowietrznych za MEX rozmowy.Dodaj swój kod, czas i zobacz, czy obciążenie jest na akceptowalnym poziomie, czy nie.

Jak zauważył Andrew Janke poniżej (dziękuję!), Funkcja MEX na ogół zależy od liczby argumentów przekazywanych do funkcji MEX. Jest to mała zależność, ale jest tam:

a = ones(1000,1); 
tic; for i=1:1000000; mexFun(a); end; toc 
Elapsed time is 2.41 seconds. 

to nie jest związane z wielkością a:

a = ones(1000000,1); 
tic; for i=1:1000000; mexFun(a); end; toc 
Elapsed time is 2.41805 seconds. 

Ale to jest związane z liczbą argumentów

a = ones(1000000,1); 
b = ones(1000000,1); 
tic; for i=1:1000000; mexFun(a, b); end; toc 
Elapsed time is 2.690237 seconds. 

Więc możesz wziąć to pod uwagę w swoich testach.

+0

Kod w środku jest właściwie mały - w zasadzie jest to funkcja kalkulatora odległości Levenshtein. – sundar

+0

@sundar Nie mogę powiedzieć dokładnie, czego możesz się spodziewać, ponieważ nie znam kodu i problemów, którymi się zajmujesz. Jeśli nie chcesz inwestować zbyt wiele czasu, dlaczego nie napiszesz trywialnego pliku mex, który nie dodaje nic więcej niż dwie liczby lub zwraca 'x + 1' i mierzy obciążenie? Jak już powiedziałem, jest stały, ale oczywiście zależy od wersji twojego sprzętu/os/matlab. Będziesz mógł dodać go do wydajności twojego kodu C i zobaczyć, czego się spodziewać w MATLAB + MEX. – angainor

+0

Mam zaktualizowane pytanie z pomiarem napowietrznym i postanowiłem odłożyć przeniesienie pętli na teraz. Będę czekać do jutra na wypadek, gdyby pojawiły się inne odpowiedzi, a potem przyjmiesz odpowiedź. – sundar

2

Powinieneś bezwzględnie bez wahania przenieść pętlę do pliku mex. Poniższy przykład demonstruje 1000-krotne przyspieszenie dla praktycznie pustej jednostki pracy w pętli for. Oczywiście, gdy ilość pracy w pętli for ulegnie zmianie, to przyspieszenie będzie się zmniejszać.

Oto przykład różnicą,

funkcji Mex bez pętli wewnętrznej:

#include "mex.h" 
void mexFunction(int nlhs, mxArray *plhs[ ], int nrhs, const mxArray *prhs[ ]) 
{  
    int i=1;  
    plhs[0] = mxCreateDoubleScalar(i); 
} 

wywoływane w Matlab:

tic;for i=1:1000000;donothing();end;toc 
Elapsed time is 3.683634 seconds. 

funkcja Mex z pętli wewnętrznej:

#include "mex.h" 
void mexFunction(int nlhs, mxArray *plhs[ ], int nrhs, const mxArray *prhs[ ]) 
{  
    int M = mxGetScalar(prhs[0]); 
    plhs[0] = mxCreateNumericMatrix(M, 1, mxDOUBLE_CLASS, mxREAL); 
    double* mymat = mxGetPr(plhs[0]); 
    for (int i=0; i< M; i++) 
     mymat[i] = M-i; 
} 

Powołani w Matlab:

tic; a = donothing(1000000); toc 
Elapsed time is 0.003350 seconds. 
+1

Chociaż masz oczywiście rację, że w tym przypadku otrzymasz dużo szybszy kod, pytanie było inne. Dotyczyło to wywoływania funkcji MEX. Twoje przyspieszenie może całkowicie zniknąć, jeśli w pliku MEX robisz więcej pracy niż tylko tworzenie skalaru. Jest to wyraźny kompromis i twoja odpowiedź dotyczy tylko twojego specjalnego scenariusza. Jeśli jedno wywołanie pliku mex zajmuje 0.01s, nie obchodzi cię, gdzie jest pętla. Poza tym przenoszenie ** ** pętli do pliku MEX może wcale nie być banalne. – angainor

+0

Oczywiście przyspieszenie może zniknąć, jeśli wykonasz więcej pracy w pliku mex, ale to nie ma znaczenia. Pytanie dotyczyło narzutów plików MEX. Pokazałem, że jeśli wywołasz plik mex 1e6 razy w pętli w programie Matlab, w przeciwieństwie do wywoływania tej samej funkcjonalności w pętli w samym pliku MEX, to poniesiesz koszty wywołania pliku MEX. Nie jestem pewien, dlaczego uważasz inaczej. – twerdster

+0

Nie kłócę się z tym. Podałem również absolutne szacunki ogólne. OP wyraźnie powiedział, że zanim zdecyduje się przenieść pętlę do pliku MEX, chce mieć pewność, czy jest to warte wysiłku. Czy według ciebie zawsze warto to robić? Ponieważ myślę, że to zależy od 1) ilości pracy jaką wykonuje w 1 iteracji pętli i 2) ilości pracy programistycznej niezbędnej do przeniesienia struktury pętli do funkcji MEX. Nie jest prawdą, że zajmuje to zazwyczaj tylko 2 dodatkowe linie. W twoim przykładzie zajmuje to 2 linie. – angainor

2

Cóż, jest to najszybszy mogę to zrobić w Matlab:

%#eml 
function L = test(s,t) 

    m = numel(s); 
    n = numel(t); 

    % trivial cases 
    if m==0 && n==0 
     L = 0; return; end 
    if n==0 
     L = m; return; end 
    if m==0 
     L = n; return; end 

    % non-trivial cases 
    M = zeros(m+1,n+1);  
    M(:,1) = 0:m; 

    for j = 2:n+1 
     for i = 2:m+1 
      M(i,j) = min([ 
       M(i-1,j) + 1 
       M(i,j-1) + 1 
       M(i-1,j-1) + (s(i-1)~=t(j-1)); 
       ]); 
     end 
    end 

    L = min(M(end,:)); 

end 

można skompilować i uruchomić to jakieś testy? (Z jakiegoś dziwnego powodu kompilacja nie działa na mojej instalacji ...) Być może najpierw zmień %#eml na %#codegen, jeśli uważasz, że to jest łatwiejsze.

UWAGA: w przypadku wersji C należy również zamienić pętle for, tak aby pętla nad j była wewnętrzną.

Ponadto, podejście row1 i row2 zapewnia znacznie większą wydajność pamięci. Jeśli masz zamiar skompilować i tak, użyłbym tego podejścia.

+0

To prawie dokładna kopia mojego kodu (z wyjątkiem tego, że nie potrzebujemy banalnych przypadków). Co robi% # eml w przeciwieństwie do% # codegen? Dobra sugestia o zamianie pętli for, dziękuję (domyślam się, że C jest rzędu-major, zamiana sprawi, że będzie bardziej przyjazna dla pamięci podręcznej, czy to prawda?). Jeśli chodzi o podejście row1, row2, czy pamięć będzie wydajna również szybciej? – sundar

+0

@sundar: '% # eml' mówi Matlabowi, aby używał osadzonego Matlaba, podzbioru' m', który Matlab może kompilować bezpośrednio do kodu maszynowego. '% # codegen' służy do generowania kodu C (i nigdy go nie używałem, ponieważ nie ma go w R2010b :). Tak, C ma numer główny. Często jest to poważna przeszkoda w tłumaczeniu między językami z rodziny C i Fortran. Podejście 'row1, row2': prawdopodobnie będzie tylko odrobinę szybsze, po prostu zużyje mniej pamięci, co może być istotne przy porównywaniu większych łańcuchów. Ponadto, radę: *** ZAWSZE *** zaimplementuj jakieś trywialne przypadki !! One ** zdarzą się wcześniej lub później, zamierzone lub nie. –

+0

@sundar: Ale dla mojego zrozumienia, nazywasz tę funkcję milion razy w pętli? Czy to właśnie próbujesz zrobić? Jeśli tak jest, spróbuj skopiować-wklejając cały kod bezpośrednio do treści pętli (z poprawkami dla poprawnego połączenia oczywiście). W ten sposób JIT firmy Matlab skompiluje całą pętlę, a ty możesz zyskać dużą prędkość (JIT nie może obsłużyć wywołań funkcji nie wbudowanych w pętlę, więc będziesz działać z "szybkością interpretacji") –

Powiązane problemy