56 votes

L'indexation des vecteurs dans MATLAB est-elle inefficace?

Arrière-plan

Ma question est motivée par de simples observations, qui quelque peu ébranler les croyances et les hypothèses souvent lieu/fait par des utilisateurs de MATLAB:

  • MATLAB est très bien optimisé quand il s'agit de la intégré dans les fonctions et les fondamentaux de la langue fonctionnalités, telles que l'indexation des vecteurs et des matrices.
  • Des boucles dans MATLAB sont lent (malgré le JIT) et doit généralement être évitée si l'algorithme peut être exprimé dans une native, "vectorisé" manière.

La ligne du bas: core MATLAB fonctionnalité est efficace et en essayant de surpasser l'aide de code MATLAB est difficile, si pas impossible.

Enquête sur la performance de vecteur d'indexation

L'exemple de code est indiqué ci-dessous sont aussi fondamentaux qu'il obtient: - je attribuer une valeur scalaire à toutes les entrées. Tout d'abord, j'alloue un vecteur vide x:

tic; x = zeros(1e8,1); toc
Elapsed time is 0.260525 seconds.

Ayant x je voudrais mettre toutes ses entrées à la même valeur. Dans la pratique, vous le feriez différemment, par exemple, x = value*ones(1e8,1), mais le point ici est d'étudier les performances de vecteur d'indexation. La façon la plus simple est d'écrire:

tic; x(:) = 1; toc
Elapsed time is 0.094316 seconds.

Appelons ça de la méthode 1 (à partir de la valeur attribuée à l' x). Il semble être très rapide (plus rapide, à moins que l'allocation de mémoire). Parce que la seule chose que je fais ici est d'exploiter la mémoire, je peux estimer l'efficacité de ce code par le calcul de la obtenu efficace de la bande passante de la mémoire et de la comparer à du matériel de bande passante mémoire de mon ordinateur:

eff_bandwidth = numel(x) * 8 bytes per double * 2 / time

Ci-dessus, je multiplie par 2 car, à moins que l'ESS streaming est utilisé, les valeurs de réglage dans la mémoire exige que le vecteur est à la fois en lecture et en écriture à la mémoire. Dans l'exemple ci-dessus:

eff_bandwidth(1) = 1e8*8*2/0.094316 = 17 Gb/s

FLUX étalonnés bande passante de la mémoire de mon ordinateur est autour de 17,9 Gb/s, donc en effet - MATLAB offre de proximité à des performances de pointe dans ce cas! Pour l'instant, donc bon.

La méthode 1 est adapté si vous souhaitez définir tous les éléments du vecteur à une certaine valeur. Mais si vous voulez accéder à des éléments de chaque step des entrées, vous devez remplacer le : avec, par exemple, 1:step:end. Ci-dessous est une conséquence directe de la vitesse de comparaison avec la méthode 1:

tic; x(1:end) = 2; toc
Elapsed time is 0.496476 seconds.

Bien que vous ne vous attendez pas à effectuer toutes différentes, la méthode 2 est clairement un gros problème: facteur 5 de ralentissement pour aucune raison. Mon soupçon est que dans ce cas MATLAB explicitement attribue l'indice du vecteur (1:end). C'est un peu confirmé par l'utilisation explicite de la taille de vecteur au lieu de end:

tic; x(1:1e8) = 3; toc
Elapsed time is 0.482083 seconds.

Les méthodes 2 et 3 de réaliser tout aussi mauvais.

Une autre possibilité est de créer explicitement un indice vectoriel id et l'utiliser pour indexer x. Cela vous donne le plus flexible des fonctions d'indexation. Dans notre cas:

tic;
id = 1:1e8; % colon(1,1e8);
x(id) = 4;
toc
Elapsed time is 1.208419 seconds.

Maintenant que c'est vraiment quelque chose d'12 fois ralentissement par rapport à la méthode 1! Je comprends qu'il doit faire pire que la méthode 1 en raison de l'augmentation de la mémoire utilisée pour id,, mais pourquoi est-il tellement pire que les méthodes 2 et 3?

Nous allons essayer de donner les boucles d'essayer - comme sans espoir que cela puisse paraître.

tic;
for i=1:numel(x)
    x(i) = 5;
end
toc
Elapsed time is 0.788944 seconds.

Une grande surprise - une boucle bat un vectorized 4 de la méthode, mais il est encore plus lent que les méthodes 1, 2 et 3. Il s'avère que dans ce cas particulier vous pouvez faire mieux:

tic;
for i=1:1e8
    x(i) = 6;
end
toc
Elapsed time is 0.321246 seconds.

Et c'est sans doute le plus bizarre, les résultats de cette étude - une MATLAB écrit boucle de manière significative surpasse natif vecteur d'indexation. Qui devrait certainement pas être ainsi. Notez que le JIT ed boucle est toujours 3 fois plus lent que le théorique crête presque obtenus par la méthode 1. Donc, il y a encore beaucoup de place pour l'amélioration. C'est juste surprenant (un mot plus fort serait plus approprié) que d'habitude "vectorisé' indexation (1:end) est encore plus lente.

Questions

  • est simple indexation dans MATLAB très inefficace (les méthodes 2, 3 et 4 sont plus lente que la méthode 1), ou ai-je raté quelque chose?
  • pourquoi est-méthode 4 (beaucoup) plus lent que les méthodes 2 et 3?
  • pourquoi l'aide d' 1e8 au lieu de numel(x) comme une boucle lié accélérer le code par un facteur 2?

Modifier Après la lecture de Jonas commentaire, voici une autre façon de le faire à l'aide de logique indices:

tic;
id = logical(ones(1, 1e8));
x(id) = 7;
toc
Elapsed time is 0.613363 seconds.

Beaucoup mieux que la méthode 4.

Pour plus de commodité:

function test

tic; x = zeros(1,1e8); toc

tic; x(:) = 1; toc
tic; x(1:end) = 2; toc
tic; x(1:1e8) = 3; toc

tic;
id = 1:1e8; % colon(1,1e8);
x(id) = 4;
toc

tic;
for i=1:numel(x)
    x(i) = 5;
end
toc

tic;
for i=1:1e8
    x(i) = 6;
end
toc

end

14voto

Jonas Points 54073

Je peux, bien sûr, que spéculer. Cependant quand je lance le test avec le compilateur JIT activé vs désactivé, j'obtiens les résultats suivants:

 % with JIT   no JIT
    0.1677    0.0011 %# init
    0.0974    0.0936 %# #1 I added an assigment before this line to avoid issues with deferring
    0.4005    0.4028 %# #2
    0.4047    0.4005 %# #3
    1.1160    1.1180 %# #4
    0.8221   48.3239 %# #5 This is where "don't use loops in Matlab" comes from 
    0.3232   48.2197 %# #6
    0.5464   %# logical indexing

Divisant nous montre où il n'y a aucune augmentation de la vitesse:

% withoutJit./withJit
    0.0067 %# w/o JIT, the memory allocation is deferred
    0.9614 %# no JIT
    1.0057 %# no JIT
    0.9897 %# no JIT
    1.0018 %# no JIT
   58.7792 %# numel
  149.2010 %# no numel

La vitesse apparente sur l'initialisation se passe, parce qu'avec le JIT éteint il semble que MATLAB les retards de l'allocation de mémoire jusqu'à ce qu'il est utilisé, de sorte que x=zeros(...) ne pas faire n'importe quoi vraiment. (merci, @angainor).

Méthodes 1 à 4 ne semblent pas bénéficier de l'équipe. Je suppose que c' #4 peut être lent, en raison des nouvelles d'entrée de test en subsref à assurez-vous que l'entrée est de la forme appropriée.

L' numel résultat pourrait avoir quelque chose à faire avec elle être plus difficile pour le compilateur pour faire face à l'incertitude du nombre d'itérations, ou avec des frais généraux en raison de vérifier si la borne de la boucle est ok (la pensée n'a-JIT tests suggèrent que d'environ 0,1 s pour qu')

Étonnamment, sur R2012b sur ma machine, logique d'indexation semble être plus lent que le #4.

Je pense que cela montre, une fois de plus, que MathWorks ont fait un grand travail dans l'accélération de code, et que "ne pas utiliser de boucles" n'est pas toujours préférable si vous essayez d'obtenir le plus rapide en temps d'exécution (au moins pour le moment). Néanmoins, je trouve que la vectorisation de l'est en général une bonne approche, car (a) l'équipe échoue sur plus de boucles complexes, et (b) apprendre à vectoriser vous fait comprendre Matlab beaucoup mieux.

Conclusion: Si vous voulez de la vitesse, utilisez le générateur de profils, et re-profil si vous passez Matlab versions.


Pour référence, j'ai utilisé légèrement modifié la fonction de test

function tt = speedTest

tt = zeros(8,1);

tic; x = zeros(1,1e8); tt(1)=toc;

x(:) = 2;

tic; x(:) = 1; tt(2)=toc;
tic; x(1:end) = 2; tt(3)=toc;
tic; x(1:1e8) = 3; tt(4)=toc;

tic;
id = 1:1e8; % colon(1,1e8);
x(id) = 4;
tt(5)=toc;

tic;
for i=1:numel(x)
    x(i) = 5;
end
tt(6)=toc;

tic;
for i=1:1e8
    x(i) = 6;
end
tt(7)=toc;

%# logical indexing
tic;
id = true(1e8,1));
x(id)=7;
tt(8)=toc;

9voto

angainor Points 8406

Je n'ai pas de réponse à tous les problèmes, mais j'ai quelques raffiné, des spéculations sur les méthodes 2, 3 et 4.

Concernant les méthodes 2 et 3. Il semble en effet que MATLAB alloue de la mémoire pour le vecteur des indices et le remplit avec les valeurs de 1 de 1e8. Pour le comprendre, permet de voir ce qui se passe. Par défaut, MATLAB utilise double comme son type de données. L'attribution de l'indice de tableau prend le même temps que l'allocation x

tic; x = zeros(1e8,1); toc
Elapsed time is 0.260525 seconds.

Pour l'instant, l'indice de tableau ne contient que des zéros. L'affectation de valeurs à l' x vecteur d'une manière optimale, comme dans la méthode 1, prend 0.094316 secondes. Maintenant, l'indice du vecteur doit être lu à partir de la mémoire, de sorte qu'il peut être utilisé lors de l'indexation. En complément 0.094316/2 secondes. Rappelons que dans x(:)=1 vecteur x doit être à la fois en lecture et en écriture à la mémoire. Si seulement la lecture, il prend la moitié du temps. En supposant que c'est tout ce qui est fait en x(1:end)=value, le temps total de méthodes 2 et 3 devraient être

t = 0.260525+0.094316+0.094316/2 = 0.402

Il est presque correct, mais pas tout à fait. Je ne peux que spéculer, mais le remplissage de l'index de vecteur avec des valeurs est sans doute fait un pas de plus et prend d'autres 0.094316 secondes. Par conséquent, t=0.4963, ce qui est plus ou moins unique avec le temps des méthodes 2 et 3.

Ce ne sont que des spéculations, mais ils ne semblent confirmer que MATLAB explicitement crée de l'indice des vecteurs lors natif vecteur d'indexation. Personnellement, je considère que cela est une performance bug. MATLABs compilateur JIT devrait être assez intelligent pour comprendre ce trivial construire et de le convertir à un appel pour un correct fonctionnement interne. Comme il est maintenant, sur le aujourd'hui de la bande passante mémoire bornée architectures d'indexation effectue autour de 20% théorique de pointe.

Donc, si vous vous souciez de performance, vous aurez à mettre en œuvre x(1:step:end) comme un MEX de la fonction, quelque chose comme

set_value(x, 1, step, 1e8, value);

Maintenant, c'est clairement illégal dans MATLAB, puisque vous n'êtes PAS AUTORISÉ à modifier des tableaux dans le MEX fichiers en place.

Edit Concernant la méthode 4, on peut essayer d'analyser la performance des différentes étapes comme suit:

tic;
id = 1:1e8; % colon(1,1e8);
toc
tic
x(id) = 4;
toc

Elapsed time is 0.475243 seconds.
Elapsed time is 0.763450 seconds.

La première étape, de répartition et de remplissage dans les valeurs de l'indice de vecteur prend le même temps que les méthodes 2 et 3 seulement. Il semble que c'est beaucoup trop, il devrait prendre au plus, le temps nécessaire pour allouer de la mémoire et de définir les valeurs (0.260525s+0.094316s = 0.3548s), il y a donc une surcharge supplémentaire de l' 0.12 secondes, quelque part, que je ne peux pas comprendre. La deuxième partie (x(id) = 4) semble également très inefficace: il faut prendre le temps nécessaire pour définir les valeurs de x, et à lire l' id vecteur (0.094316s+0.094316/2s = 0.1415s) et de certaines vérifications d'erreur sur l' id valeurs. Programmé en C, en deux étapes:

create id                              0.214259
x(id) = 4                              0.219768

Le code utilisé vérifie qu'un double de l'indice, en fait, représente un entier, et qu'il s'adapte à la taille de l' x:

tic();
id  = malloc(sizeof(double)*n);
for(i=0; i<n; i++) id[i] = i;
toc("create id");

tic();
for(i=0; i<n; i++) {
  long iid = (long)id[i];
  if(iid>=0 && iid<n && (double)iid==id[i]){
    x[iid] = 4;
  } else break;
}
toc("x(id) = 4");

La deuxième étape prend plus de temps que prévu 0.1415s - c'est à cause de la nécessité de contrôles d'erreur sur id valeurs. La surcharge semble trop grand pour moi - peut-être il pourrait être mieux écrit. Encore, le temps nécessaire est - 0.4340s , pas 1.208419s. Ce que MATLAB ne sous le capot - je n'ai aucune idée. C'est peut-être nécessaire de le faire, je n'ai juste pas le voir.

Bien sûr, à l'aide de doubles que les indices introduit deux niveaux additionnels de frais généraux:

  • la taille de l' double deux fois la taille de l' uint32. Rappelons que la bande passante mémoire est le facteur limitant ici.
  • doubles doivent être exprimées en nombres entiers pour l'indexation

Méthode 4 peut être écrit en MATLAB à l'aide de entier indices:

tic;
id = uint32(1):1e8;
toc
tic
x(id) = 8;
toc

Elapsed time is 0.327704 seconds.
Elapsed time is 0.561121 seconds.

Ce qui est clairement amélioré les performances de 30% et prouve que l'on devrait utiliser des nombres entiers comme vecteur d'indices. Toutefois, la charge est toujours là.

Comme je le vois maintenant, nous ne pouvons rien faire pour améliorer la situation de travail au sein de l'MATLAB cadre, et nous devons attendre jusqu'à Mathworks résout ces problèmes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X