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ć.