104 votes

arrayfun peut être beaucoup plus lent que l'explicite en boucle dans matlab. Pourquoi?

Considérez les points suivants simple test de vitesse pour arrayfun:

T = 4000;
N = 500;
x = randn(T, N);
Func1 = @(a) (3*a^2 + 2*a - 1);

tic
Soln1 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln1(t, n) = Func1(x(t, n));
    end
end
toc

tic
Soln2 = arrayfun(Func1, x);
toc

Sur ma machine (Matlab, 2011b sur Linux Mint 12), la sortie de ce test est:

Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.

Quoi?!? arrayfun, tandis que certes, un nettoyeur à la recherche de la solution, est d'un ordre de grandeur inférieur. Ce qui se passe ici?

De plus, j'ai fait le même style de test pour cellfun et l'a trouvé pour être environ 3 fois plus lent qu'un explicite de la boucle. Encore une fois, ce résultat est à l'opposé de ce que j'attendais.

Ma question est: Pourquoi sont - arrayfun et cellfun beaucoup plus lent? Et compte tenu de cela, il y a toutes les bonnes raisons de les utiliser (autres que pour rendre le code de bonne apparence)?

Note: je parle de la version standard de l' arrayfun d'ici, PAS le GPU version du traitement parallèle boîte à outils.

EDIT: Juste pour être clair, je suis conscient qu' Func1 ci-dessus peuvent être vectorisé comme l'a souligné par Oli. J'ai seulement choisi parce qu'il donne un simple test de vitesse pour les fins de la question.

EDIT: Suite à la suggestion de grungetta, j'ai re-fait le test avec feature accel off. Les résultats sont les suivants:

Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.

En d'autres termes, il semble qu'une grande partie de la différence est que le JIT accélérateur fait un bien meilleur travail de l'accélération de l'explicite for boucle qu'il n' arrayfun. Cela me semble étrange, car arrayfun fournit plus d'informations, c'est à dire, de son usage révèle que l'ordre des appels d' Func1 n'ont pas d'importance. Aussi, j'ai noté que, si le JIT accélérateur est allumé ou éteint, mon système n'a jamais utilise un PROCESSEUR...

101voto

angainor Points 8406

Vous pouvez obtenir de l'idée par l'exécution d'autres versions de votre code. Explicitement prendre en compte l'écriture des 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

Le temps de calcul sur mon ordinateur:

Soln1  1.158446 seconds.
Soln2  10.392475 seconds.
Soln3  0.239023 seconds.
Oli    0.010672 seconds.

Maintenant, tout le totalement "vectorisé" solution est clairement le plus rapide, vous pouvez voir que la définition d'une fonction à appeler pour chaque x entrée est une énorme surcharge. Juste en écrivant explicitement le calcul nous a facteur 5 speedup. Je suppose que cela montre que MATLABs compilateur JIT n'a pas les fonctions inline. En fonction de la réponse par gnovice là, il est effectivement préférable d'écrire une fonction normale à la place d'un anonyme. Essayez-la.

Prochaine étape: supprimer (vectorisation) 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 5 speedup: il y a quelque chose dans ces déclarations disant: vous devez éviter les boucles dans MATLAB... Ou est-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 "pleinement" vectorisé version. Matlab magasins de matrices colonnes. Vous devriez toujours (si possible) de la structure de vos calculs pour être vectorisé 'colonnes'.

Nous pouvons revenir à Soln3 maintenant. La boucle de commande, il est "de la ligne sage". Permet de 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 unique de bien - être. Double boucle - bad. Je suppose que MATLAB fait un peu de travail décent sur l'amélioration de la performance de boucles, mais encore la boucle de surcharge est là. Si vous avez des lourds travaux à l'intérieur, vous auriez pas remarqué. Mais depuis ce calcul est la mémoire de la bande passante limitée, vous voyez la boucle de frais généraux. Et vous aurez encore plus voir clairement les frais généraux de l'appel à Func1.

Donc, qu'est-ce qu'arrayfun? Pas de fonction inlinig il soit, donc beaucoup de frais généraux. Mais pourquoi autant de pire qu'une double boucle imbriquée? En fait, le sujet de l'utilisation de cellfun/arrayfun a été largement discuté à de nombreuses reprises (par exemple ici, ici, ici et ici). Ces fonctions sont simplement ralentir, vous ne pouvez pas les utiliser pour de telles grain fin des calculs. Vous pouvez les utiliser dans le code de la brièveté et de la fantaisie des conversions entre les cellules et de tableaux. Mais la fonction doit être plus lourd 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. Code de la performance est assez bonne aujourd'hui, et si vous avez besoin de cellule en sortie, vous n'avez pas besoin de convertir votre matrice après que vous avez utilisé entièrement vectorisé solution.

Alors, pourquoi est-arrayfun plus lente qu'une simple structure de boucle? Malheureusement, il est impossible pour nous de le dire, car il n'y a pas de code source disponible. Vous pouvez seulement deviner que depuis arrayfun est un objectif général de la fonction, qui gère tous les types de différentes structures de données et arguments, il n'est pas forcément très rapide dans les cas simples, que vous pouvez exprimer directement comme des nids de boucles. D'où la surcharge venons, nous ne pouvons pas savoir. Pourrait la surcharge être évités par 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 il fonctionne bien, et ceux où elle ne l'est pas.

Mise à jour Depuis le moment de l'exécution de ce test est court, pour obtenir des résultats fiables, j'ai ajouté maintenant une boucle autour de l'tests:

for i=1:1000
   % compute
end

Certains horaires indiqué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 de trois ordres de grandeur pire que le vectorisé solution. Sur l'autre main, une seule boucle avec les colonnes de calculs est aussi rapide que le entièrement vectorisé version... Qui a été fait sur un seul PROCESSEUR. Résultats pour Soln5 et Soln7 ne changent pas si je passe à 2 cœurs - En Soln5 je dois utiliser un parfor pour l'obtenir parallélisée. Oublier speedup... Soln7 ne pas s'exécuter en parallèle parce que arrayfun ne pas s'exécuter en parallèle. Le silo vectorisé version sur l'autre main:

Oli  5.508085 seconds.

-7voto

user3932983 Points 7

Que parce que!!!!

x = randn(T, N); 

n'est pas gpuarray type;

Tout ce que vous devez faire est de

x = randn(T, N,'gpuArray');

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