Analýza rýchlosti kódu

Pri tvorbe algoritmu nám v prvom rade ide o funkčnosť nášho kódu. Chceme, aby sa program správal korektne a dával také výsledky, aké by sme čakali. Niekedy je však dôležité, ako rýchlo program zbehne a kde MATLAB strávi najviac času. MATLAB poskytuje niekoľko možností, ako rýchlosť programu zmerať. Štandardne sa na meranie rýchlosti využívajú príkazy tic a toc, medzi ktoré vložíme sledovaný kód. Alternatívou môže byť funkcia timeit, ktorá meria beh funkcie. Okrem týchto príkazov môžeme využiť ešte jeden spôsob – Profiling.

Simulácia systému

Na jednoduchom príklade simulácie systému si môžeme ukázať, ako jednotlivé súčasti kódu ovplyvňujú jeho rýchlosť. Základom nášho programu bude funkcia, ktorá reprezentuje systém druhého rádu. Ide o systém pružina-tlmič, na ktorú pôsobíme silou. Funkciu vieme zapísať do súboru system2r.m nasledujúcim spôsobom:

 function dx = system2r(t,x,m,b,k,F)
dx = zeros(2,1);
dx(1) = x(2);
dx(2) = 1/m*(F(t)-k*x(1)-b*x(2));
end

Aby sme vedeli vyhodnotiť dynamiku systému, využijeme riešič MATLABu ode45. Pri skúmaní dynamiky využijeme sady viacerých parametrov. Na záver priebehy vykreslíme a vypočítame maximálny nábeh výstupného systému. V MATLABe vytvoríme nasledovný skript system_simula­cia.m.

 %% Parametre systemu
m = 1; % [kg]
k = 1; % [N/m]
b = 0.2:0.01:3; % [N/m/s]
%% Funkcia budenia systemu
F = @(t)t>10;
%% Doba simulacie
tsim = 50; % doba simulace
%% Inicializacia
Y = [];
T = 0:0.01:tsim;
%% Simulacia
for idx = 1:length(b)
  odefun = @(t,x)system2r(t,x,m,b(idx),k,F);
  [t,y] = ode45(odefun,[0 tsim],[0 0]);
  Y(:,idx) = interp1(t,y(:,1),T);
end
%% Vykreslenie vysledkov
plot(T,Y);
%% Vyhodnotenie vrcholu
for ii=1:size(Y,2)
  vrchol(ii)=max(Y(:,ii))
 end

Program nie je napísaný ideálne a zámerne sme vytvorili zopár nevhodných konštrukcií, na ktorých si ukážeme možnosti časovania kódu a ako takéto konštrukcie lepšie riešiť.

Meranie rýchlosti

Nami vytvorený skript spustíme v záložke EDITOR pomocou tlačidla Run and Time. MATLAB Profiler kód spustí a zmeria časy jednotlivých súčasti programu. Výstupom celého procesu je sumár časovania.

Vo vrchnej časti sumára vidíme celkový čas behu skriptu. Po ním sa nachádza grafické zobrazenie výkonností jednotlivých súčastí skriptu – flame graph. Graf je doplnok pridaný od verzie R2020a, ktorý pomôže ľahšie identifikovať kritické miesta. Časti vytvorené používateľmi sú modrou farbou a zvyšok sú funkcie vytvorené spoločnosťou MathWorks. Z grafu vidíme, že najviac času zaberá funkcia ode45 a volania funkcie nášho systému. Pod grafom môžeme vidieť tabuľku časti skriptu s počtom ich volaní, celkový čas ich behu a čas behu, ktorý je bez volaných podfunkcií. Kliknutím na meno funkcie system_simulácia sa dostaneme na flame graf a časovanie samostatného skriptu ako takého. Nás bude zaujímať časť, ktorá hovorí o kritických riadkoch – Line that takes the most time.

Zaujímavé súčasti tejto stránky sú ešte výsledky analýzy kódu (Code Analyzer results), ktoré nám napovedia, čo by sme mohli vylepšiť. Ďalší je pohlaď na jednotlivé volania po riadkoch v časti Function Listing, kde sú kritické riadky zvýraznené červenou farbou. Z tabuľky a jednotlivých časov vyplýva, že by sme sa mali pouvažovať nasledujúcimi časťami:

  1. funkciu ode45
  2. vykreslenie grafu
  3. premenné Y a vrchol

Práca s premennými

V našom skripte vo sekcii Vyhodnotenie vrcholu máme premennú, ktorú napĺňame v cykle postupne. Navyše si ju nechávame vypisovať do príkazového riadka, preto by sme mali použiť bodkočiarku na konci príkazu. Jedným z odporúčaní Code Analyzéra je využitie predalokácie. Pri postupnom vytváraní a rozširovaní premennej musí MATLAB neustále vytvárať miesto v pamäti a kopírovať premenné. Pri predalokácií najskôr vytvoríme premennú zodpovedajúcej dĺžky a až následne ju napĺňame. Náš kód teda vieme upraviť nasledovne:

 %% Vyhodnotenie vrcholu
vrchol=zeros(1,size(Y,2));
for ii=1:size(Y,2)
  vrchol(ii)=max(Y(:,ii));
 end

Takýto zápis nám zrýchli výpočet sekcie, čo môžeme pozorovať aj pri profilovaní.

Podobne by sme mali postupovať aj pre premennú Y. Ďalší princíp, ktorý môžeme urobiť je fakt, že väčšina vstavaných funkcií prostredia MATLAB podporuje výpočty priamo na celej matici. Výnimkou nie je ani funkcia max. Funkcia vypočíta maximálnu hodnotu v každom stĺpci matice. Predchádzajúci zápis môžeme jednoducho upraviť na nasledujúci tvar, ktorý

 vrchol=max(Y);

Pri tomto zápise sa tento riadok už do zoznamu kritických ani nedostane.

Vykreslenie grafu

Grafy kreslíme vstavanými funkciami, takže priestor na zrýchlenie moc nemáme. Opäť však vieme využiť princípy, ktoré program zrýchlia. Namiesto vykresľovania grafov v cykle a využití príkazu hold on môžeme znova využiť vektorizáciu. Pokiaľ máme dáta v matici po stĺpcoch sú jednotlivé stĺpce vykreslené ako samostatný objekt. Grafy slúžia na vizuálne overenie výsledkov. Ak sme s výsledkami spokojný pri ladení, pre ostrý beh programu môžeme grafy zakomentovať.

Funkcia ode45

Funkcia ode45 je jedným z riešičov prostredia MATLAB pre diferenciálne rovnice. MATLAB poskytuje ďalšie riešiče, ktoré môžeme vyskúšať. O typoch a ktorých kedy zvoliť sa dočítate napríklad v dokumentácií.

Pre niektoré typy úloh sa hodia špecifické riešiče. Môžeme vyskúšať, ako sa zmení rýchlosť výpočtu pre ďalšie riešice napr. ode23 a ode 113.

Na obrázkoch si môžete všimnúť nielen jednotlivé časy výpočtov, ale aj počty volaní funkcie system2r. Z pokusom vyplýva, že najvýhodnejší riešič je v našom prípade ode45.

Záver

Meranie rýchlosti vášho programu, alebo jeho časti sa dá v prostredí MATLAB vykonať viacerými spôsobmi od jednoduchých príkazov a funkcií, až o pokročilý nástroj Profiler. Viac o zrýchľovaní kódu, ale aj prácou s pamäťou sa dočítate v dokumentácii.

Michal Blaho (HUMUSOFT), 29.4.2020