Vous pouvez avoir l'idée en exécutant d'autres versions de votre code. Envisagez d'écrire explicitement les calculs, au lieu d'utiliser une fonction dans votre boucle
tic
Soln3 = ones(T, N);
for t = 1:T
for n = 1:N
Soln3(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
end
end
toc
Il est temps de calculer sur mon ordinateur:
Soln1 1.158446 seconds.
Soln2 10.392475 seconds.
Soln3 0.239023 seconds.
Oli 0.010672 seconds.
Maintenant, alors que la solution entièrement «vectorisée» est clairement la plus rapide, vous pouvez voir que la définition d'une fonction à appeler pour chaque entrée x est une surcharge énorme . Le simple fait d'écrire explicitement le calcul nous a permis d'accélérer le facteur 5. Je suppose que cela montre que le compilateur MATLABs JIT ne prend pas en charge les fonctions en ligne . D'après la réponse de gnovice, il est en fait préférable d'écrire une fonction normale plutôt qu'une fonction anonyme. Essayez-le.
Étape suivante - supprimer (vectoriser) la boucle interne:
tic
Soln4 = ones(T, N);
for t = 1:T
Soln4(t, :) = 3*x(t, :).^2 + 2*x(t, :) - 1;
end
toc
Soln4 0.053926 seconds.
Un autre facteur d'accélération 5: il y a quelque chose dans ces déclarations disant que vous devriez éviter les boucles dans MATLAB ... Ou y a-t-il vraiment? Jetez un oeil à ceci alors
tic
Soln5 = ones(T, N);
for n = 1:N
Soln5(:, n) = 3*x(:, n).^2 + 2*x(:, n) - 1;
end
toc
Soln5 0.013875 seconds.
Beaucoup plus proche de la version «entièrement» vectorisée. Matlab stocke les matrices par colonne. Vous devez toujours (si possible) structurer vos calculs pour qu'ils soient vectorisés «par colonne».
Nous pouvons revenir à Soln3 maintenant. L'ordre des boucles est «par ligne». Permet de le changer
tic
Soln6 = ones(T, N);
for n = 1:N
for t = 1:T
Soln6(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
end
end
toc
Soln6 0.201661 seconds.
Mieux, mais toujours très mauvais. Boucle simple - bon. Double boucle - mauvais. Je suppose que MATLAB a fait un travail décent pour améliorer les performances des boucles, mais la surcharge de la boucle est toujours là. Si vous aviez un travail plus lourd à l'intérieur, vous ne le remarqueriez pas. Mais comme ce calcul est limité par la bande passante mémoire, vous voyez la surcharge de la boucle. Et vous verrez encore plus clairement les frais généraux liés à l'appel de Func1.
Alors quoi de neuf avec arrayfun? Aucune fonction inlinig là non plus, donc beaucoup de frais généraux. Mais pourquoi tant de pire qu'une double boucle imbriquée? En fait, le sujet de l'utilisation de cellfun / arrayfun a été largement discuté à plusieurs reprises (par exemple ici , ici , ici et ici ). Ces fonctions sont simplement lentes, vous ne pouvez pas les utiliser pour de tels calculs à grain fin. Vous pouvez les utiliser pour la brièveté du code et les conversions sophistiquées entre les cellules et les tableaux. Mais la fonction doit être plus lourde que ce que vous avez écrit:
tic
Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false);
toc
Soln7 0.016786 seconds.
Notez que Soln7 est une cellule maintenant .. parfois c'est utile. Les performances du code sont assez bonnes maintenant, et si vous avez besoin d'une cellule en sortie, vous n'avez pas besoin de convertir votre matrice après avoir utilisé la solution entièrement vectorisée.
Alors, pourquoi arrayfun est-il plus lent qu'une simple structure en boucle? Malheureusement, il nous est impossible de le dire avec certitude, car il n'y a pas de code source disponible. Vous ne pouvez que deviner que puisque arrayfun est une fonction à usage général, qui gère toutes sortes de structures de données et d'arguments différents, elle n'est pas nécessairement très rapide dans des cas simples, que vous pouvez directement exprimer sous forme de nids de boucles. D'où viennent les frais généraux, nous ne pouvons pas le savoir. Les frais généraux pourraient-ils être évités grâce à une meilleure mise en œuvre? Peut être pas. Mais malheureusement, la seule chose que nous pouvons faire est d'étudier la performance pour identifier les cas, dans lesquels cela fonctionne bien, et ceux où cela ne fonctionne pas.
Mise à jour Puisque le temps d'exécution de ce test est court, pour obtenir des résultats fiables, j'ai maintenant ajouté une boucle autour des tests:
for i=1:1000
% compute
end
Quelques temps donnés ci-dessous:
Soln5 8.192912 seconds.
Soln7 13.419675 seconds.
Oli 8.089113 seconds.
Vous voyez que le arrayfun est toujours mauvais, mais au moins pas trois ordres de grandeur pire que la solution vectorisée. D'autre part, une seule boucle avec des calculs par colonne est aussi rapide que la version entièrement vectorisée ... Tout cela a été fait sur un seul processeur. Les résultats pour Soln5 et Soln7 ne changent pas si je passe à 2 cœurs - Dans Soln5, je devrais utiliser un parfor pour le paralléliser. Oubliez l'accélération ... Soln7 ne fonctionne pas en parallèle car arrayfun ne fonctionne pas en parallèle. Olis version vectorisée par contre:
Oli 5.508085 seconds.