2013-08-17 13 views
5

Kiedy zacząłem pracować z Matlabem jakiś czas temu na uniwersytecie, mój przełożony zabiłby mnie, gdyby zobaczył niepotrzebną pętlę for (poprosiłby o jej wymianę na kron lub jakikolwiek sposób manipulacji indeksami, jak to możliwe). Później starałem się unikać jak najwięcej pętli matlab, szukając najciemniejszych sposobów kodowania matlabów, by zrobić czarną magię zamiast prostej pętli.Cellfun versus Simple Matlab Loop performance

I pewnego dnia odkryłem cellfun, co czyniło czarną magię dość prostsze, mogę zmienić wiele pętle pracy z komórkami i cellfun combo, ale pewnego dnia widziałem jeden post about cellfun który mnie pytanie, czy mój dziedziczone znajomość Matlab była prawda , to jest: że pętle matlab zawsze będą wolniejsze niż jedna wbudowana funkcja kompilowana, co było czymś, co miałem tyle wiary. Przetestowałem to w jednej z moich implementacji i faktycznie, pętla byłaby szybsza! Byłem jak OMG, przez te wszystkie dni robiąc niewyraźny kod marnowany na nic hahaha. Od tego czasu przestałem ciężko pracować, aby spróbować zoptymalizować kod matlab, zwykle zależy to od każdego przypadku i tak dalej.

Dzisiaj I saw this answer, który zapamiętał mój wysiłek, aby omijać jak najwięcej pętli matlabowych (nie wiem, czy to był autor, którego należy unikać do wykonania, ale w każdym razie to przypomniało wszystkie te działanie z pętlą matlab). I jedno pytanie przyszło mi do głowy: Czy celofonia jest lepsza niż w przypadku pętli? Kiedy to będzie prawda?

+1

Co do mojej odpowiedzi, o której wspomniałeś: tak, staram się unikać pętli 'for', jak to robisz (lub robiłeś), a moją motywacją jest wydajność. Myślę (lub zwykł myśleć?), Że pętle 'for' są zwykle wolniejsze i należy ich unikać. –

+0

Najwyraźniej "cela" jest wolniejsza, z wyjątkiem szczególnych przypadków. Zobacz http://www.mathworks.com/matlabcentral/answers/42335 i http: //www.mathworks.com/matlabcentral/newsreader/view_thread/301894 –

+0

@LuisMendo Tak, dokładnie! Ale niestety 'cellfun' z anonimowym uchwytem funkcji jest zwykle wolniejszy niż pętli' for' ... to by mnie tak nudziło za wysiłek za nic. Dzięki za referencje. – Werner

Odpowiedz

8

Jeśli wydajność jest głównym czynnikiem, należy unikać używania komórek, pętle lub cellfun/arrayfun. Zwykle znacznie szybciej jest użyć operacji wektorowej (zakładając, że jest to możliwe).

Poniższy kod rozwija się w przykładzie dodawania Wernera za pomocą standardowych operacji w pętli i macierzy.

Wyniki są następujące:

  • komórki LOOP - 0,1679
  • Cellfun czasu - 2,9973
  • pętli Tablica Czas - 0,0465
  • Godzina Array - 0,0019

Kod:

nTimes = 1000; 
nValues = 1000; 
myCell = repmat({0},1,nValues); 
output = zeros(1,nValues); 

% Basic operation 
tic; 
for k=1:nTimes 
    for m=1:nValues 
    output(m) = myCell{m} + 1; 
    end 
end 
cell_loop_timeAdd=toc;  
fprintf(1,'Cell Loop Time %0.4f\n', cell_loop_timeAdd); 

tic;   
for k=1:nTimes 
    output = cellfun(@(in) in+1,myCell); 
end 
cellfun_timeAdd=toc; 
fprintf(1,'Cellfun Time %0.4f\n', cellfun_timeAdd); 


myData = repmat(0,1,nValues); 
tic; 
for k=1:nTimes 
    for m=1:nValues 
    output(m) = myData(m) + 1; 
    end 
end 
loop_timeAdd=toc; 
fprintf(1,'Loop Array Time %0.4f\n', loop_timeAdd); 

tic; 
for k=1:nTimes 
    output = myData + 1; 
end 
array_timeAdd=toc; 
fprintf(1,'Array Time %0.4f\n', array_timeAdd); 
+0

Świetny, miły punkt i wkład. Tylko jeden szczegół: cellicun tic toc zawiera także funkcję fprintf. Nie wpłynie to na wyniki, ale w każdym razie ... – Werner

4

Dodam jedną odpowiedź z wynikami, które sam przetestowałem, ale byłbym zadowolony, gdyby ludzie przyczynili się do swojej wiedzy, to tylko prosty test, który zrobiłem.

Przetestowałem następujące warunki z rozmiarem komórek 1000 i 1000 pętli (wyniki na łączny czas, i prawdopodobnie musiałbym uruchomić więcej niż 1000 razy, ponieważ mam małą fluktuację wyników, ale tak czy inaczej , nie jest to artykuł naukowy):

  • Podstawowe operacje (suma)
    • prosty dla pętli: 0.2663 s
    • cellfun: 9,4612 s
  • operacji łańcuchowej (strcmp)
    • prosty pętli: 1.3124 s
    • cellfun: 11,8099 s
  • wbudowanego (isEmpty)
  • nierównomierną (Regexp)
    • prosty pętli: 24.2157 s
    • cellfun (wejście łańcuch) 44.0424 s

Więc wydaje się, że cellfun z anonimowych wywołań funkcji są wolniejsze niż prosty dla pętli, ale jeśli będziesz korzystać z wbudowanego metodę Matlab, zrób to z cellfun i używać go z notowania smyczkową . Nie musi to być prawda we wszystkich przypadkach, ale przynajmniej w przypadku testowanych funkcji.

Zaimplementowany kod testowy (jestem daleko od bycia specjalistą optymalizacji, więc tutaj jest kod w przypadku zrobiłem coś źle):

function ... 
    [loop_timeAdd,cellfun_timeAdd,... 
    loop_timeStr,cellfun_timeStr,... 
    loop_timeBuiltIn,cellfun_timeBuiltInStrInput,... 
    cellfun_timeBuiltyInFcnHandle,... 
    loop_timeNonUniform,cellfun_timeNonUniform] ... 
    = test_cellfun(nTimes,nCells) 

myCell = repmat({0},1,nCells); 
output = zeros(1,nCells); 

% Basic operation 
tic; 
for k=1:nTimes 
    for m=1:nCells 
    output(m) = myCell{m} + 1; 
    end 
end 
loop_timeAdd=toc; 

tic; 
for k=1:nTimes 
    output = cellfun(@(in) in+1,myCell); 
end 
cellfun_timeAdd=toc; 

% String operation 
myCell = repmat({'matchStr'},1,nCells); % Add str that matches 
myCell(1:2:end) = {'dontMatchStr'}; % Add another str that doesnt match 
output = zeros(1,nCells); 

tic; 
for k=1:nTimes 
    for m=1:nCells 
    output(m) = strcmp(myCell{m},'matchStr'); 
    end 
end 
loop_timeStr=toc; 

tic; 
for k=1:nTimes 
    output = cellfun(@(in) strcmp(in,'matchStr'),myCell); 
end 
cellfun_timeStr=toc; 

% Builtin function (isempty) 
myCell = cell(1,nCells); % Empty 
myCell(1:2:end) = {0}; % not empty 
output = zeros(1,nCells); 

tic; 
for k=1:nTimes 
    for m=1:nCells 
    output(m) = isempty(myCell{m}); 
    end 
end 
loop_timeBuiltIn=toc; 

tic; 
for k=1:nTimes 
    output = cellfun(@isempty,myCell); 
end 
cellfun_timeBuiltyInFcnHandle=toc; 

tic; 
for k=1:nTimes 
    output = cellfun('isempty',myCell); 
end 
cellfun_timeBuiltInStrInput=toc; 

% Builtin function (isempty) 
myCell = repmat({'John'},1,nCells); 
myCell(1:2:end) = {'Doe'}; 
output = cell(1,nCells); 

tic; 
for k=1:nTimes 
    for m=1:nCells 
    output{m} = regexp(myCell{m},'John','match'); 
    end 
end 
loop_timeNonUniform=toc; 

tic; 
for k=1:nTimes 
    output = cellfun(@(in) regexp(in,'John','match'),myCell,... 
    'UniformOutput',false); 
end 
cellfun_timeNonUniform=toc; 
0
clear all; 
ntimes = 1000; 

r = 100; 
c = 100; 
d = 100; 
A = rand(r, c, d); 
B = rand(r, c, d); 

tic 
for i = 1:ntimes 
    for j = 1 : d 
     result = A(:, :, j) * B(:, :, j); 
    end 
end 
toc 

A_cell = num2cell(A, [1 2]); 
B_cell = num2cell(B, [1 2]); 
tic 
for i = 1 : ntimes 
    result2 = cellfun(@(x, y) x*y, A_cell, B_cell, 'uni', 0); 
end 
toc 

Spróbuj tego. Przetestuj to jeszcze kilka razy. Przeciętnie celofal jest szybszy niż podwójna pętla.

+0

Zewnętrzna pętla, którą skomentowałeś "co to robi?" ma powtarzać n-razy zmierzoną pętlę, aby zmniejszyć odchylenie wyniku i doprowadzić do średniego czasu wykonania. Jeśli usuniesz go, tak jak ty, porównasz 1 czas wykonania z innymi pętlami, które powtarzają się n-razy. – Werner

+0

@Werner Zaktualizowałem kod. Twierdzę, ponieważ robię podobne rzeczy w moim projekcie. Przetestowałem wersję z podwójną pętlą w moim projekcie i wersję z Cellfun, która jest znacznie szybsza niż podwójna pętla. Nie mam pojęcia dlaczego. Ale ten przykład może pokazać, że cela jest szybsza. – user3390652

+0

Twoje porównanie wydaje się niesprawiedliwe, komórka ma wymiar 1x2, a rand ma 100x100x100. Jest też trochę czasu, w którym ostatnio użyłem programu Matlab, ale wygląda na to, że możesz zmienić 'dla j = 1: d; result = A() ... 'przez' bsxfun', który prawdopodobnie byłby szybszy. – Werner

0

Oto jak ja zwykle zdecydować się na rozwiązanie, które w użyciu:

  1. Mogę to zrobić z prostych operacji macierzowych? Zrób to, będzie to najszybszy, który możesz uzyskać i zwykle bardziej czytelny.
  2. Czy robię coś prostego? Przejdź do 3, w przeciwnym razie przejdź do 4.
  3. Czy mogę to zrobić z bsxfun? Zrób to, będzie bardzo szybko i mam nadzieję, że będzie czytelny.
  4. przeciwnym wypadku należy zastosować prosty do pętli

Jak można zauważyć, że prawie nigdy nie używać cellfun dla wydajności. Jest to spowodowane następującą logiką:

  1. Zwykle cela nie działa znacznie szybciej niż pętla i jest w większości dobra do radzenia sobie z komórkami.
  2. Jeśli wydajność jest ważna, prawdopodobnie chcesz całkowicie uniknąć użycia komórek i jako taka nie będziesz potrzebować celofonu.