MATLAB Language
Wydajność i analiza porównawcza
Szukaj…
Uwagi
- Profilowanie kodu jest sposobem na uniknięcie przerażającej praktyki „ przedwczesnej optymalizacji ” poprzez skupienie programisty na tych częściach kodu, które faktycznie uzasadniają wysiłki związane z optymalizacją.
- Artykuł dokumentacyjny MATLAB zatytułowany „ Zmierz wydajność swojego programu ”.
Identyfikacja wąskich gardeł wydajności za pomocą Profilera
MATLAB Profiler to narzędzie do profilowania oprogramowania kodu MATLAB. Za pomocą Profilera można uzyskać wizualną reprezentację czasu wykonania i zużycia pamięci.
Uruchomienie programu Profiler można wykonać na dwa sposoby:
Kliknięcie przycisku „Uruchom i czas” w graficznym interfejsie użytkownika MATLAB przy otwartym pliku
.m
w edytorze (dodanym w R2012b ).Programowo, używając:
profile on <some code we want to test> profile off
Poniżej znajduje się przykładowy kod i wynik jego profilowania:
function docTest
for ind1 = 1:100
[~] = var(...
sum(...
randn(1000)));
end
spy
Z powyższego dowiadujemy się, że funkcja spy
zajmuje około 25% całkowitego czasu wykonania. W przypadku „prawdziwego kodu” funkcja, która zajmuje tak duży procent czasu wykonania, byłaby dobrym kandydatem do optymalizacji, w przeciwieństwie do funkcji analogicznych do var
i cla
których optymalizacji należy unikać.
Ponadto można kliknąć pozycje w kolumnie Nazwa funkcji, aby zobaczyć szczegółowy podział czasu wykonania dla tej pozycji. Oto przykład kliknięcia spy
:
Możliwe jest również profilowanie zużycia pamięci przez wykonanie profile('-memory')
przed uruchomieniem programu Profiler.
Porównanie czasu wykonania wielu funkcji
Szeroko stosowana kombinacja tic
i toc
może dać przybliżone wyobrażenie o czasie wykonywania funkcji lub fragmentów kodu.
Do porównywania kilku funkcji nie należy go używać. Dlaczego? Prawie niemożliwe jest zapewnienie równych warunków dla wszystkich fragmentów kodu do porównania w skrypcie przy użyciu powyższego rozwiązania. Być może funkcje mają tę samą przestrzeń funkcji i wspólne zmienne, dlatego tak zwane funkcje i fragmenty kodu już korzystają z wcześniej zainicjowanych zmiennych i funkcji. Nie ma też wglądu, czy kompilator JIT poradziłby sobie z tymi później zwanymi fragmentami po równo.
Dedykowaną funkcją dla testów porównawczych jest timeit
. Poniższy przykład ilustruje jego użycie.
Istnieje tablica A
i macierz B
Należy ustalić, który wiersz B
jest najbardziej podobny do A
, licząc liczbę różnych elementów.
function t = bench()
A = [0 1 1 1 0 0];
B = perms(A);
% functions to compare
fcns = {
@() compare1(A,B);
@() compare2(A,B);
@() compare3(A,B);
@() compare4(A,B);
};
% timeit
t = cellfun(@timeit, fcns);
end
function Z = compare1(A,B)
Z = sum( bsxfun(@eq, A,B) , 2);
end
function Z = compare2(A,B)
Z = sum(bsxfun(@xor, A, B),2);
end
function Z = compare3(A,B)
A = logical(A);
Z = sum(B(:,~A),2) + sum(~B(:,A),2);
end
function Z = compare4(A,B)
Z = pdist2( A, B, 'hamming', 'Smallest', 1 );
end
Ten sposób testu został po raz pierwszy widziany w tej odpowiedzi .
Można być singlem!
Przegląd:
Domyślny typ danych dla tablic numerycznych w MATLAB jest double
. double
jest zmiennoprzecinkową reprezentacją liczb , a ten format zajmuje 8 bajtów (lub 64 bitów) na wartość. W niektórych przypadkach, gdy np. Zajmowanie się tylko liczbami całkowitymi lub gdy niestabilność numeryczna nie jest bezpośrednim problemem, tak duża głębia bitowa może nie być wymagana. Z tego powodu zaleca się rozważenie zalet single
precyzji (lub innych odpowiednich typów ):
- Krótszy czas wykonania (szczególnie zauważalny na GPU).
- Połowa zużycia pamięci: może się powieść w przypadku awarii
double
powodu błędu braku pamięci; bardziej kompaktowy podczas przechowywania jako pliki.
Przekształcanie zmiennej z dowolnego obsługiwanego typu danych na single
odbywa się za pomocą:
sing_var = single(var);
Niektóre często używane funkcje (takie jak: zeros
, eye
, ones
itp. ), Które domyślnie generują double
wartości, pozwalają na określenie typu / klasy wyniku.
Konwertowanie zmiennych w skrypcie na niestandardową precyzję / typ / klasę:
Od lipca 2016 r. Nie ma udokumentowanego sposobu zmiany domyślnego typu danych MATLAB z double
.
W MATLAB nowe zmienne zwykle naśladują typy danych zmiennych używanych podczas ich tworzenia. Aby to zilustrować, rozważ następujący przykład:
A = magic(3);
B = diag(A);
C = 20*B;
>> whos C
Name Size Bytes Class Attributes
C 3x1 24 double
A = single(magic(3)); % A is converted to "single"
B = diag(A);
C = B*double(20); % The stricter type, which in this case is "single", prevails
D = single(size(C)); % It is generally advised to cast to the desired type explicitly.
>> whos C
Name Size Bytes Class Attributes
C 3x1 12 single
Dlatego może wydawać się wystarczające rzutowanie / konwersja kilku zmiennych początkowych, aby zmiana przenikała przez cały kod - jednak jest to odradzane (patrz Ostrzeżenia i pułapki poniżej).
Ostrzeżenia i pułapki:
Powtarzające się konwersje są odradzane z powodu wprowadzenia szumu numerycznego (podczas rzutowania z
single
nadouble
) lub utraty informacji (podczas rzutowania zdouble
nasingle
lub między pewnymi typami liczb całkowitych ), np .:double(single(1.2)) == double(1.2) ans = 0
Można to nieco złagodzić, stosując
typecast
. Zobacz także Należy pamiętać o niedokładności zmiennoprzecinkowej .Opierając się wyłącznie na danych niejawny-pisanie (czyli co MATLAB zgaduje typ wyjścia z obliczeń powinny być) nie jest zalecane ze względu na kilka niepożądanych skutków, które mogą się pojawić:
Utrata informacji : gdy oczekuje się
double
wyniku, ale nieostrożne połączeniesingle
idouble
argumentu dajesingle
precyzję.Niespodziewanie wysokie zużycie pamięci : gdy oczekiwany jest
single
wynik, ale nieostrożne obliczenia skutkujądouble
wyjściem.Niepotrzebny narzut podczas pracy z procesorami graficznymi : podczas mieszania typów
gpuArray
(tj.gpuArray
przechowywanych w VRAM) ze zmiennymigpuArray
niżgpuArray
(tj. Zwykle przechowywanymi w pamięci RAM), dane będą musiały zostać przesłane w jedną lub drugą stronę przed wykonaniem obliczeń. Ta operacja wymaga czasu i może być bardzo zauważalna w powtarzalnych obliczeniach.Błędy podczas mieszania typów zmiennoprzecinkowych z typami liczb całkowitych : funkcje takie jak
mtimes
(*
) nie są zdefiniowane dla mieszanych danych wejściowych typów liczb całkowitych i zmiennoprzecinkowych - i będąmtimes
błędy. Funkcje takie jaktimes
(.*
) W ogóle nie są zdefiniowane dla wejść typu całkowitoliczbowego - i ponownie wystąpi błąd.>> ones(3,3,'int32')*ones(3,3,'int32') Error using * MTIMES is not fully supported for integer classes. At least one input must be scalar. >> ones(3,3,'int32').*ones(3,3,'double') Error using .* Integers can only be combined with integers of the same class, or scalar doubles.
Dla lepszej czytelności kodu i zmniejszenia ryzyka niepożądanych typów zaleca się podejście defensywne, w którym zmienne są jawnie rzutowane na pożądany typ.
Zobacz też:
- Dokumentacja MATLAB: Liczby zmiennoprzecinkowe .
- Artykuł techniczny Mathworks: Najlepsze praktyki konwertowania kodu MATLAB na punkt stały .
zmiana kolejności macierzy ND może poprawić ogólną wydajność
W niektórych przypadkach musimy zastosować funkcje do zestawu tablic ND. Spójrzmy na ten prosty przykład.
A(:,:,1) = [1 2; 4 5];
A(:,:,2) = [11 22; 44 55];
B(:,:,1) = [7 8; 1 2];
B(:,:,2) = [77 88; 11 22];
A =
ans(:,:,1) =
1 2
4 5
ans(:,:,2) =
11 22
44 55
>> B
B =
ans(:,:,1) =
7 8
1 2
ans(:,:,2) =
77 88
11 22
Obie macierze są trójwymiarowe, powiedzmy, że musimy obliczyć:
result= zeros(2,2);
...
for k = 1:2
result(i,j) = result(i,j) + abs( A(i,j,k) - B(i,j,k) );
...
if k is very large, this for-loop can be a bottleneck since MATLAB order the data in a column major fashion. So a better way to compute "result" could be:
% trying to exploit the column major ordering
Aprime = reshape(permute(A,[3,1,2]), [2,4]);
Bprime = reshape(permute(B,[3,1,2]), [2,4]);
>> Aprime
Aprime =
1 4 2 5
11 44 22 55
>> Bprime
Bprime =
7 1 8 2
77 11 88 22
Teraz zamieniamy powyższą pętlę na:
result= zeros(2,2);
....
temp = abs(Aprime - Bprime);
for k = 1:2
result(i,j) = result(i,j) + temp(k, i+2*(j-1));
...
Zmieniliśmy rozmieszczenie danych, abyśmy mogli wykorzystać pamięć podręczną. Permutacja i zmiana kształtu mogą być kosztowne, ale podczas pracy z dużymi macierzami ND koszt obliczeniowy związany z tymi operacjami jest znacznie niższy niż w przypadku pracy z nieuporządkowanymi macierzami.
Znaczenie wstępnej alokacji
Tablice w MATLAB są przechowywane w pamięci jako ciągłe bloki, przydzielane i zwalniane automatycznie przez MATLAB. MATLAB ukrywa operacje zarządzania pamięcią, takie jak zmiana rozmiaru tablicy za łatwą w użyciu składnią:
a = 1:4
a =
1 2 3 4
a(5) = 10 % or alternatively a = [a, 10]
a =
1 2 3 4 10
Ważne jest, aby zrozumieć, że powyższe nie jest trywialną operacją, a(5) = 10
spowoduje, że MATLAB przydzieli nowy blok pamięci o rozmiarze 5, skopiuje pierwsze 4 liczby i ustawi 5 na 10. To O(numel(a))
, a nie O(1)
.
Rozważ następujące:
clear all
n=12345678;
a=0;
tic
for i = 2:n
a(i) = sqrt(a(i-1)) + i;
end
toc
Elapsed time is 3.004213 seconds.
a
jest ponownie przydzielane n
razy w tej pętli (z wyłączeniem niektórych optymalizacji podjętych przez MATLAB)! Pamiętaj, że MATLAB ostrzega nas:
„Wydaje się, że zmienna„ a ”zmienia rozmiar przy każdej iteracji pętli. Rozważ wstępne przydzielenie prędkości.”
Co się stanie, gdy dokonamy wstępnej alokacji?
a=zeros(1,n);
tic
for i = 2:n
a(i) = sqrt(a(i-1)) + i;
end
toc
Elapsed time is 0.410531 seconds.
Widzimy, że czas działania jest zmniejszony o rząd wielkości.
Metody wstępnej alokacji:
MATLAB zapewnia różne funkcje do alokacji wektorów i macierzy, w zależności od specyficznych wymagań użytkownika. Należą do nich: zeros
, ones
, nan
, eye
, true
itp.
a = zeros(3) % Allocates a 3-by-3 matrix initialized to 0
a =
0 0 0
0 0 0
0 0 0
a = zeros(3, 2) % Allocates a 3-by-2 matrix initialized to 0
a =
0 0
0 0
0 0
a = ones(2, 3, 2) % Allocates a 3 dimensional array (2-by-3-by-2) initialized to 1
a(:,:,1) =
1 1 1
1 1 1
a(:,:,2) =
1 1 1
1 1 1
a = ones(1, 3) * 7 % Allocates a row vector of length 3 initialized to 7
a =
7 7 7
Typ danych można również określić:
a = zeros(2, 1, 'uint8'); % allocates an array of type uint8
Łatwo jest również sklonować rozmiar istniejącej tablicy:
a = ones(3, 4); % a is a 3-by-4 matrix of 1's
b = zeros(size(a)); % b is a 3-by-4 matrix of 0's
I sklonuj typ:
a = ones(3, 4, 'single'); % a is a 3-by-4 matrix of type single
b = zeros(2, 'like', a); % b is a 2-by-2 matrix of type single
zwróć uwagę, że „polubienie” również klonuje złożoność i rzadkość .
Wstępna alokacja jest niejawnie osiągana za pomocą dowolnej funkcji, która zwraca tablicę o ostatecznym wymaganym rozmiarze, takiej jak rand
, gallery
, kron
, bsxfun
, colon
i wiele innych. Na przykład częstym sposobem przydzielania wektorów z liniowo zmieniającymi się elementami jest użycie operatora dwukropka (w wariancie 1 lub 2-operandowym):
a = 1:3
a =
1 2 3
a = 2:-3:-4
a =
2 -1 -4
Tablice komórkowe można przydzielić za pomocą funkcji cell()
w taki sam sposób, jak zeros()
.
a = cell(2,3)
a =
[] [] []
[] [] []
Zauważ, że tablice komórek działają, przytrzymując wskaźniki do lokalizacji w pamięci zawartości komórki. Zatem wszystkie wskazówki dotyczące wstępnej alokacji dotyczą również poszczególnych elementów macierzy komórek.
Dalsza lektura:
- Oficjalna dokumentacja MATLAB na temat „ Preallocating Memory ”.
- Oficjalna dokumentacja MATLAB na temat „ Jak MATLAB przydziela pamięć ”.
- Wydajność wstępnej alokacji na nieudokumentowanym matlabie .
- Zrozumienie prealokacji macierzy na Lorenie o sztuce MATLAB