Przyspieszanie obliczeń w MATLABie

Przyspieszanie obliczeń w MATLABie

Przyspieszanie wykonywania algorytmów w środowisku MATLAB to w zasadzie temat rzeka. Drogi do obranego celu są różne. Parallel Computing Toolbox umożliwia zastąpienie pętli for przez parfor, zrównoleglającej obliczenia na wszystkich dostępnych w komputerze rdzeniach obliczeniowych, a także pozwala na wykorzystanie w obliczeniach kart graficznych firmy nVidia. Przy naprawdę zasobożernych zadaniach MATLAB Distributed Computing Server daje możliwość zastosowania do obliczeń zewnętrznego klastra obliczeniowego. Można też wykorzystać MATLAB Coder do wygenerowania kodu C z części naszych algorytmów, co również powinno przełożyć się na prędkość ich działania. Zanim jednak sięgniemy po te zaawansowane i nierzadko kosztowne narzędzia, warto zacząć od przeglądnięcia m-kodu, którym dysponujemy. Nierzadko skrócenie czasu wykonania algorytmu można osiągnąć modyfikując nieznacznie m-kod.


Alokacja pamięci

To prawda, że jedną z zalet programu MATLAB jest to, że coś takiego jak alokacja pamięci może użytkownika nie obchodzić. Rzeczywiście, w „codziennym” użyciu można o tym, w jaki sposób MATLAB operuje na pamięci, w ogóle nie myśleć. Jednak, jeśli czas wykonania naszego algorytmu niebezpiecznie się wydłuża, warto się nad tą kwestią zastanowić. Bardzo wygodna jest w MATLABie swobodna zmiana wielkości macierzy i dopisywanie do niej nowych wartości. Przy różnych okazjach atrakcyjny może okazać się kod typu:

wyniki = [];
For i = 1:n
	wynik = superFajnyAlgorytm();
	wyniki = [wyniki wynik];
end

Kod jest wygodny w zapisie i łatwy do interpretacji przez człowieka, jednak może być problematyczny dla komputera. Jeśli nasze n wynosi powiedzmy 100, to w zasadzie wszystko w porządku. Jeśli jednak n wynosi 10e6 to może pojawić się problem. Rzecz w tym, że jeśli MATLAB ma uaktualnić macierz wyniki o nowy wpis, to za każdym razem musi utworzyć w pamięci miejsce na nową macierz wyniki, skopiować tam zawartość starej macierzy powiększonej o nowy wynik cząstkowy i w końcu zwolnić miejsce zajmowane przez starą macierz wyniki. To naprawdę może bardzo spowolnić działanie algorytmu. Dla przykładu, załóżmy, że przeprowadzamy symulację rzutu 5 kośćmi do gry w taki sposób, że powtarzamy rzuty do czasu, aż po raz 10 wypadnie pięć szóstek. Kod można zapisać tak:

tic
wyniki = [];

for i = 1:10
    wynik = 0;
    while sum(wynik)~=30
        wynik = randi(6,5,1);
        wyniki = [wyniki wynik];
    end
end
toc   

Elapsed time is 43.526072 seconds.

Czas wykonania algorytmu może się oczywiście zmieniać w zależności od naszego szczęścia do gry w kości, niemniej jednak jest on irytująco długi. Zobaczmy co się stanie, jeśli zaalokujemy pamięć na macierz w wynikami.

tic
komplet_szostek = 0;
wyniki = zeros(5,100000);
for i = 1:length(wyniki)
    wyniki(:,i) = randi(6,5,1);
    if sum(wyniki(:,i)) == 30
        komplet_szostek = komplet_szostek + 1;
        if komplet_szostek == 10
            break;
        end
    end
end
wyniki = wyniki(:, 1:i);
toc

Elapsed time is 0.209002 seconds.
Różnica jest kolosalna!

Unikanie pętli for

Unikanie pętli for może w pierwszej chwili wydawać się pomysłem niedorzecznym. Że niby co, wszystko przerabiać na pętle while? Kompletnie nie o to chodzi. MATLAB jest językiem stworzonym do pracy na macierzach i okazuje się, że w niektórych sytuacjach można zrezygnować ze stosowania pętli for, jeśli tylko zaczniemy operować na macierzach w sensowny sposób. Załóżmy, że jest potrzeba wygenerowania sygnału będącego sumą kolejnych, dajmy na to 50 harmonicznych. Można to zrobić oczywiście tak:

fp = 1000000; %1MHz
f0 = 2; %częstotliwość podstawowa
n = 50; % liczba harmonicznych

dt = 1/fp;
t = 0:dt:1-dt;
tic;
y = zeros(1, fp); %wektor wynikowy
for i=1:2:2*n
    x = (1/i) * sin(2*pi*f0*i*t);
    y = y + x;
end
toc

plot(t, y); xlabel('Czas [s]'); ylabel('Amplituda');


Elapsed time is 0.928059 seconds.
A można też inaczej:

fp = 1000000; %1MHz
f0 = 2; %częstotliwość podstawowa
n = 50; % liczba harmonicznych

dt = 1/fp;
t = 0:dt:1-dt;
tic;
i = 1:2:2*n;
A = 1./i;     %wektor kolejnych amplitud
f = (f0.*i)'; % wektor kolejnych częstotliwości
y = A*sin(2*pi*f*t);
plot(t,y);
toc

Elapsed time is 0.781626 seconds.

Różnica na korzyść rozwiązania „nieforowanego” ujawni się tylko wtedy, kiedy korzystać będziemy z komputera wieloprocesorowego (czy też procesora wielordzeniowego), uzbrojonego przynajmniej w 4 niezależne rdzenie obliczeniowe. Im więcej rdzeni, tym lepiej oczywiście. W obliczeniach, w których przekształcane są macierze, angażowane są wszystkie dostępne w komputerze rdzenie obliczeniowe niezależnie od tego, czy mamy zainstalowany Parallel Computing Toolbox czy też nie. Wymaga to nieco wprawy, ale czasami warto z tego skorzystać.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *