Rozważ następujący prosty test szybkości dla arrayfun
:
T = 4000;
N = 500;
x = randn(T, N);
Func1 = @(a) (3*a^2 + 2*a - 1);
tic
Soln1 = ones(T, N);
for t = 1:T
for n = 1:N
Soln1(t, n) = Func1(x(t, n));
end
end
toc
tic
Soln2 = arrayfun(Func1, x);
toc
Na moim komputerze (Matlab 2011b na Linux Mint 12) wynik tego testu to:
Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.
Co?!? arrayfun
, choć wprawdzie bardziej przejrzyste rozwiązanie, jest o rząd wielkości wolniejsze. Co tu się dzieje?
Ponadto wykonałem podobny test dla cellfun
i stwierdziłem, że jest około 3 razy wolniejszy niż jawna pętla. Ponownie, ten wynik jest przeciwieństwem tego, czego się spodziewałem.
Moje pytanie brzmi: dlaczego są arrayfun
io cellfun
wiele wolniejsze? A biorąc pod uwagę to, czy są jakieś dobre powody, aby ich używać (poza tym, że kod wygląda dobrze)?
Uwaga: mówię tutaj o standardowej wersji arrayfun
, a NIE wersji GPU z zestawu narzędzi do przetwarzania równoległego.
EDYCJA: Żeby było jasne, zdaję sobie sprawę, że Func1
powyżej można wektoryzować, jak wskazał Oli. Wybrałem to tylko dlatego, że daje prosty test szybkości dla celów rzeczywistego pytania.
EDYCJA: Zgodnie z sugestią grungetty ponownie wykonałem test feature accel off
. Wyniki są następujące:
Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.
Innymi słowy, wydaje się, że duża część różnicy polega na tym, że akcelerator JIT znacznie lepiej przyspiesza jawną for
pętlę niż robi arrayfun
. Wydaje mi się to dziwne, ponieważ w arrayfun
rzeczywistości dostarcza więcej informacji, tj. Jego użycie pokazuje, że kolejność wywołań Func1
nie ma znaczenia. Zauważyłem również, że niezależnie od tego, czy akcelerator JIT jest włączony, czy wyłączony, mój system używa tylko jednego procesora ...
źródło
Odpowiedzi:
Możesz zrozumieć pomysł, uruchamiając inne wersje swojego kodu. Rozważ jawne napisanie obliczeń, zamiast używania funkcji w pętli
Czas na obliczenia na moim komputerze:
Teraz, podczas gdy w pełni „wektoryzowane” rozwiązanie jest zdecydowanie najszybsze, widać, że zdefiniowanie funkcji, która ma być wywoływana dla każdego wpisu x, jest ogromnym narzutem. Samo wyraźne wypisanie obliczeń dało nam 5-krotne przyspieszenie. Wydaje mi się, że to pokazuje, że kompilator MATLABs JIT nie obsługuje funkcji wbudowanych . Zgodnie z odpowiedzią udzieloną tam przez gnovice, właściwie lepiej jest napisać normalną funkcję niż anonimową. Spróbuj.
Następny krok - usunięcie (wektoryzacja) pętli wewnętrznej:
Kolejne przyspieszenie o czynnik 5: w tych stwierdzeniach jest coś, co mówi, że powinieneś unikać pętli w MATLAB-ie ... A może naprawdę? Spójrz na to więc
Znacznie bliżej do „w pełni” wektoryzowanej wersji. Matlab przechowuje macierze według kolumn. Zawsze (jeśli to możliwe) należy nadawać obliczeniom strukturę wektoryzacji „kolumnowej”.
Możemy teraz wrócić do Soln3. Tam kolejność pętli jest „wierszowa”. Zmieńmy to
Lepiej, ale nadal bardzo źle. Pojedyncza pętla - dobra. Podwójna pętla - źle. Myślę, że MATLAB wykonał porządną pracę nad poprawą wydajności pętli, ale nadal istnieje obciążenie pętli. Gdybyś miał w środku cięższą pracę, nie zauważyłbyś. Ale ponieważ to obliczenie jest ograniczone pasmem pamięci, widać narzut pętli. I będzie jeszcze wyraźniej zobaczyć napowietrznej wywołanie func1 tam.
Więc o co chodzi z arrayfun? Tam też nie ma żadnej funkcji, więc dużo narzutów. Ale dlaczego jest o wiele gorszy niż podwójna zagnieżdżona pętla? Właściwie temat korzystania z cellfun / arrayfun był obszernie omawiany wiele razy (np. Tutaj , tutaj , tutaj i tutaj ). Te funkcje są po prostu powolne, nie można ich używać do tak drobnoziarnistych obliczeń. Możesz ich używać do zwięzłości kodu i fantazyjnych konwersji między komórkami i tablicami. Ale funkcja musi być cięższa niż ta, którą napisałeś:
Zauważ, że Soln7 jest teraz komórką… czasami jest to przydatne. Wydajność kodu jest teraz całkiem dobra i jeśli potrzebujesz komórki jako wyniku, nie musisz konwertować macierzy po użyciu w pełni wektoryzowanego rozwiązania.
Dlaczego więc arrayfun działa wolniej niż prosta struktura pętli? Niestety nie możemy tego powiedzieć na pewno, ponieważ nie ma dostępnego kodu źródłowego. Można się tylko domyślać, że skoro arrayfun jest funkcją ogólnego przeznaczenia, która obsługuje wszystkie rodzaje różnych struktur danych i argumentów, niekoniecznie jest bardzo szybka w prostych przypadkach, które można bezpośrednio wyrazić jako zagnieżdżenia pętli. Skąd się bierze narzut, nie wiemy. Czy można uniknąć kosztów ogólnych dzięki lepszej implementacji? Może nie. Ale niestety jedyne, co możemy zrobić, to zbadać wydajność, aby zidentyfikować przypadki, w których działa dobrze, i te, w których nie.
Aktualizacja Ponieważ czas wykonania tego testu jest krótki, aby uzyskać wiarygodne wyniki dodałem teraz pętlę wokół testów:
Czasami podane poniżej:
Widzisz, że arrayfun jest nadal zły, ale przynajmniej nie o trzy rzędy wielkości gorszy niż rozwiązanie wektoryzowane. Z drugiej strony pojedyncza pętla z obliczeniami opartymi na kolumnach jest tak szybka, jak w pełni zwektoryzowana wersja ... Wszystko to zostało zrobione na jednym procesorze. Wyniki dla Soln5 i Soln7 nie zmieniają się, jeśli przełączę się na 2 rdzenie - w Soln5 musiałbym użyć parfor, aby uzyskać równoległość. Zapomnij o przyspieszeniu ... Soln7 nie działa równolegle, ponieważ arrayfun nie działa równolegle. Z drugiej strony wersja wektoryzowana Olis:
źródło
cellfun
został zaimplementowany jako plik MEX (z dostępnym obok niego kodem źródłowym C). Właściwie było to całkiem proste. Oczywiście obsługiwał tylko zastosowanie jednej z 6 zakodowanych na stałe funkcji (nie można było przekazać uchwytu funkcji, tylko ciąg z jedną nazwą funkcji)To dlatego, że !!!!
nie jest
gpuarray
typem;Wszystko, co musisz zrobić, to
źródło
gpuarray
. Prawie na pewno dlatego ta odpowiedź została odrzucona.gpuarray
jest obsługiwana tylko przez karty graficzne nVidia. Jeśli nie mają takiego sprzętu, twoja rada (lub jej brak) jest bez znaczenia. -1