Recherche…


Remarques

Identification des goulots d'étranglement des performances à l'aide du profileur

MATLAB Profiler est un outil de profilage logiciel du code MATLAB. En utilisant le profileur, il est possible d'obtenir une représentation visuelle du temps d'exécution et de la consommation de mémoire.

L'exécution du profileur peut se faire de deux manières:

  • En cliquant sur le bouton "Run and Time" dans l'interface graphique de MATLAB tout en ayant un fichier .m ouvert dans l'éditeur (ajouté dans R2012b ).

    Bouton dans la barre d'outils

  • Par programme, en utilisant:

    profile on
    <some code we want to test>
    profile off
    

Voici un exemple de code et le résultat de son profilage:

function docTest

for ind1 = 1:100
  [~] = var(...
            sum(...
                randn(1000)));
end

spy

Sortie du profileur

De ce qui précède, nous apprenons que la fonction d' spy prend environ 25% du temps d'exécution total. Dans le cas du "code réel", une fonction qui prend un tel temps d'exécution serait un bon candidat pour l'optimisation, par opposition aux fonctions analogues à var et cla dont l'optimisation devrait être évitée.

De plus, il est possible de cliquer sur les entrées de la colonne Nom de la fonction pour voir une ventilation détaillée du temps d'exécution de cette entrée. Voici l'exemple de cliquer sur spy :

Calendrier interne de "espion"


Il est également possible de profiler la consommation de mémoire en exécutant profile('-memory') avant d'exécuter le profileur.

entrer la description de l'image ici

Comparer le temps d'exécution de plusieurs fonctions

La combinaison très répandue de tic et de toc peut donner une idée approximative du temps d'exécution d'une fonction ou d'extraits de code.

Pour comparer plusieurs fonctions, il ne faut pas l'utiliser. Pourquoi? Il est presque impossible de fournir des conditions égales pour tous les extraits de code à comparer dans un script utilisant la solution ci-dessus. Les fonctions partagent peut-être le même espace de fonction et les mêmes variables communes, de sorte que les fonctions appelées et les extraits de code ultérieurs tirent déjà parti des variables et fonctions précédemment initialisées. En outre, il n'y a pas d'aperçu si le compilateur JIT traiterait ces fragments appelés ultérieurement de la même manière.


La fonction dédiée aux benchmarks est le timeit . L'exemple suivant illustre son utilisation.

Il y a le tableau A et la matrice B Il convient de déterminer quelle ligne de B est la plus similaire à A en comptant le nombre d'éléments différents.

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

Ce mode de référence a été vu pour la première fois dans cette réponse .

C'est bien d'être célibataire!

Aperçu:

Le type de données par défaut pour les tableaux numériques dans MATLAB est double . double est une représentation à virgule flottante des nombres , et ce format prend 8 octets (ou 64 bits) par valeur. Dans certains cas, où, par exemple, ne traiter que des nombres entiers ou lorsque l’instabilité numérique n’est pas un problème imminent, une telle résolution peut ne pas être requise. Pour cette raison, il est conseillé de prendre en compte les avantages d'une single précision (ou d'autres types appropriés):

  • Temps d'exécution plus rapide (particulièrement visible sur les GPU).
  • La moitié de la consommation de mémoire: peut réussir lorsque le double échoue en raison d'une erreur de mémoire insuffisante; plus compact lors du stockage en fichiers.

La conversion d' une variable à partir de tout type de données pris en charge à single est effectuée en utilisant:

sing_var = single(var);

Certaines fonctions couramment utilisées (telles que les zeros , les eye , les ones , etc. ) qui génèrent double valeurs double par défaut permettent de spécifier le type / la classe de la sortie.

Conversion de variables dans un script en une précision / type / classe autre que celle par défaut:

Depuis juillet 2016, il n'existe aucun moyen documenté de modifier le type de données MATLAB par défaut du double .

Dans MATLAB, les nouvelles variables imitent généralement les types de données des variables utilisées lors de leur création. Pour illustrer cela, considérons l'exemple suivant:

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  

Ainsi, il peut sembler suffisant de lancer / convertir plusieurs variables initiales pour que le changement imprègne le code, mais cela est déconseillé (voir Mises en garde et pièges ci-dessous).

Mises en garde et pièges:

  1. Les conversions répétées sont déconseillées en raison de l'introduction de bruit numérique (lors du passage d'un single à un double ) ou de la perte d'informations (lors du passage du double au single ou entre certains types d'entiers ), par exemple:

    double(single(1.2)) == double(1.2)   
    ans =
         0
    

    Cela peut être atténué en utilisant typecast . Voir aussi Soyez conscient de l'imprécision en virgule flottante .

  2. Le recours exclusif au typage implicite des données (c.-à-d. Ce que MATLAB suppose que le type de sortie d'un calcul devrait être) est déconseillé en raison de plusieurs effets indésirables qui pourraient survenir:

    • Perte d'information : lorsqu'un double résultat est attendu, mais qu'une combinaison négligente d'opérandes single et double produit single précision single .

    • Consommation de mémoire élevée de manière inattendue : lorsqu'un single résultat est attendu mais qu'un calcul imprudent entraîne une double sortie.

    • Une surcharge inutile lors de l'utilisation de GPU : lors du mélange de types gpuArray (c'est-à-dire les variables stockées dans VRAM) avec des variables non gpuArray (c'est-à-dire généralement stockées dans la RAM), les données devront être transférées avant que le calcul puisse être effectué. Cette opération prend du temps et peut être très visible dans les calculs répétitifs.

    • Erreurs lors du mélange de types à virgule flottante avec des types entiers : les fonctions telles que mtimes ( * ) ne sont pas définies pour les entrées mixtes de types entiers et à virgule flottante - et seront des erreurs. Les fonctions comme les times ( .* ) Ne sont pas du tout définies pour les entrées de type entier - et seront à nouveau erronées.

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

    Pour une meilleure lisibilité du code et un risque réduit de types indésirables, une approche défensive est conseillée , dans laquelle les variables sont explicitement converties au type souhaité.


Voir également:

réorganiser un tableau ND peut améliorer les performances globales

Dans certains cas, nous devons appliquer des fonctions à un ensemble de tableaux ND. Regardons cet exemple simple.

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

Les deux matrices sont en 3D, disons que nous devons calculer ce qui suit:

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

Maintenant, nous remplaçons la boucle ci-dessus comme suit:

result= zeros(2,2);
....
temp = abs(Aprime - Bprime);
for k = 1:2
    result(i,j) = result(i,j) + temp(k, i+2*(j-1));
...

Nous avons réorganisé les données pour pouvoir exploiter la mémoire cache. La permutation et la remise en forme peuvent être coûteuses, mais lorsque vous travaillez avec des baies ND volumineuses, le coût de calcul lié à ces opérations est bien inférieur à celui des baies non organisées.

L'importance de la préallocation

Les tableaux dans MATLAB sont des blocs continus en mémoire, alloués et libérés automatiquement par MATLAB. MATLAB masque les opérations de gestion de la mémoire telles que le redimensionnement d'un tableau derrière une syntaxe facile à utiliser:

a = 1:4

a =

     1     2     3     4

a(5) = 10  % or alternatively a = [a, 10]

a =

     1     2     3     4    10

Il est important de comprendre que ce qui précède n'est pas une opération triviale, a(5) = 10 obligera MATLAB à allouer un nouveau bloc de mémoire de taille 5, à copier les 4 premiers numéros et à définir le 5 à 10. C'est une opération O(numel(a)) , et non pas O(1) .

Considérer ce qui suit:

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 est réallouée n fois dans cette boucle (à l'exclusion de certaines optimisations entreprises par MATLAB)! Notez que MATLAB nous avertit:

"La variable 'a' semble changer de taille à chaque itération de boucle. Envisagez de pré-allouer la vitesse."

Que se passe-t-il lorsque nous pré-allouons?

a=zeros(1,n);
tic
for i = 2:n
    a(i) = sqrt(a(i-1)) + i;
end
toc

Elapsed time is 0.410531 seconds.

Nous pouvons voir que le temps d'exécution est réduit d'un ordre de grandeur.

Méthodes de préallocation:

MATLAB fournit diverses fonctions pour l'allocation de vecteurs et de matrices, en fonction des besoins spécifiques de l'utilisateur. Ceux-ci incluent: les zeros , les ones , les nan , l' eye , true etc.

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

Un type de données peut également être spécifié:

a = zeros(2, 1, 'uint8');  % allocates an array of type uint8

Il est également facile de cloner la taille d'un tableau existant:

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

Et cloner le type:

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

Notez que "like" clone également la complexité et la rareté .

La préallocation est implicitement obtenue en utilisant n'importe quelle fonction qui renvoie un tableau de la taille finale requise, tel que rand , gallery , kron , bsxfun , colon et bien d'autres. Par exemple, un moyen courant d’allouer des vecteurs avec des éléments variant de façon linéaire consiste à utiliser l’opérateur deux-points (avec la variante 1 ou 2 opérandes):

a = 1:3 
a =

     1     2     3

a = 2:-3:-4
a =

     2    -1    -4

Les tableaux de cellules peuvent être alloués à l'aide de la fonction cell() de la même manière que les zeros() .

a = cell(2,3)
a = 

    []    []    []
    []    []    []

Notez que les tableaux de cellules fonctionnent en conservant des pointeurs vers les emplacements en mémoire du contenu de la cellule. Ainsi, toutes les astuces de préallocation s'appliquent également aux éléments individuels du tableau de cellules.


Lectures complémentaires:



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow