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

    Przycisk na pasku narzędzi

  • 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

Wyjście profilera

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 :

Wewnętrzny czas „szpiega”


Możliwe jest również profilowanie zużycia pamięci przez wykonanie profile('-memory') przed uruchomieniem programu Profiler.

wprowadź opis zdjęcia tutaj

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:

  1. Powtarzające się konwersje są odradzane z powodu wprowadzenia szumu numerycznego (podczas rzutowania z single na double ) lub utraty informacji (podczas rzutowania z double na single 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 .

  2. 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łączenie single i double argumentu daje single 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 zmiennymi gpuArray 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 jak times ( .* ) 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ż:

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:



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow