arrayfun peut être significativement plus lent qu'une boucle explicite dans matlab. Pourquoi?

pour arrayfun , on considère l'essai de vitesse simple suivant:

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.

que se passe-t-il?!? arrayfun , tout en étant une solution plus propre, est un ordre de grandeur plus lent. Ce qui se passe ici?

de plus, j'ai fait un test similaire pour cellfun et j'ai trouvé qu'il était environ 3 fois plus lent que une boucle explicite. Encore une fois, ce résultat est à l'opposé de ce que j'attendais.

ma question Est: pourquoi arrayfun et cellfun sont-ils tellement plus lents? Et compte tenu de cela, y a-t-il de bonnes raisons de les utiliser (sauf pour faire paraître le code bien)?

Note: je parle de la version standard de arrayfun ici, pas la version GPU de la boîte à outils de traitement parallèle.

EDIT: Juste pour être clair, je suis consciente que Func1 ci-dessus peut être vectorisé comme l'a souligné par Oli. Je l'ai seulement choisi parce qu'il fournit un test de vitesse simple pour les besoins de la question réelle.

EDIT: suite à la suggestion de grungetta, j'ai refait le test avec feature accel off . Les résultats sont:

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

En d'autres termes, il semble qu'une grande partie de la la différence est que L'accélérateur JIT fait un bien meilleur travail d'accélérer la boucle explicite for qu'il ne fait 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 à Func1 n'a pas d'importance. De plus, j'ai remarqué que, que l'accélérateur JIT soit activé ou désactivé, mon système n'utilise qu'un seul CPU...

96
demandé sur angainor 2012-09-21 04:45:14

2 réponses

vous pouvez obtenir l'idée en exécutant d'autres versions de votre code. Envisager 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

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 un énorme overhead. Juste en écrivant explicitement la le calcul nous a permis d'accélérer le facteur 5. Je suppose que cela montre que le compilateur MATLABs JIT ne supporte pas les fonctions en ligne . Selon la réponse de gnovice, il est en fait préférable d'écrire une fonction normale plutôt qu'une fonction anonyme. Essayer.

la Prochaine étape est de 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 d'accélération 5: Il ya quelque chose dans ces déclarations disant que vous devriez éviter les boucles dans MATLAB... Ou est-il vraiment?

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 des matrices au niveau des colonnes. Vous devriez toujours (quand c'est possible) structurer vos calculs pour être vectorisés 'par colonne'.

nous pouvons retourner à Soln3 maintenant. L'ordre de boucle y est 'row-wise'.

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 encore très mauvais. Boucle unique de bien - être. Double boucle-mauvais. Je je suppose que MATLAB a fait du bon travail sur l'amélioration de la performance des boucles, mais la boucle aérienne est toujours là. Si vous aviez un travail plus lourd à l'intérieur, vous ne le remarqueriez pas. Mais puisque ce calcul est limité par la bande passante mémoire, vous voyez la boucle au-dessus. Et vous will encore plus clairement voir le au-dessus de L'appel Func1 là.

qu'est-ce qui se passe avec arrayfun? Aucune fonction inlinig là non plus, donc beaucoup de frais généraux. Mais pourquoi bien pire qu'une double boucle imbriquée? En fait, le sujet de l'utilisation de cellfun/arrayfun a été longuement discuté à de nombreuses reprises (par exemple ici , ici , ici et ici ). Ces fonctions sont tout simplement lents, vous ne pouvez pas les utiliser pour de tels calculs à grain fin. Vous pouvez les utiliser pour la concision du code et des conversions fantaisistes entre les cellules et les 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. La performance du Code est assez bonne maintenant, et si vous avez besoin de la cellule comme sortie, vous n'avez pas besoin de convertir votre matrice après avoir utilisé la solution entièrement vectorisée.

alors pourquoi arrayfun est plus lent qu'une simple structure de boucle? Malheureusement, il nous est impossible de le dire avec certitude, puisqu'il n'y a pas de code source disponible. Vous ne pouvez que supposer que puisque arrayfun est un général fonction de but, qui traite toutes sortes de structures de données et d'arguments différents, il n'est pas nécessairement très rapide dans les cas simples, que vous pouvez exprimer directement comme nids de boucle. D'où la surcharge venons, nous ne pouvons pas savoir. Une meilleure mise en œuvre permettrait-elle d'éviter les frais généraux? 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ù il ne fonctionne pas.

mise à Jour puisque le temps d'exécution de ce test est court, pour obtenir des résultats fiables j'ai ajouté maintenant une boucle autour des tests:

for i=1:1000
   % compute
end

plusieurs fois indiqué 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'un autre côté, 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 CPU. Les résultats pour Soln5 et Soln7 ne changent pas si je passe à 2 noyaux - dans Soln5, je devrais utiliser un parfor pour l'obtenir parallélisé. Oublier speedup... Soln7 ne fonctionne pas en parallèle car arrayfun ne fonctionne pas en parallèle. Olis version vectorisée d'un autre côté:

Oli  5.508085 seconds.
98
répondu angainor 2017-05-23 11:54:56

c'est parce que!!!!

x = randn(T, N); 

n'est pas "151920920 type";

Tout ce que vous devez faire est de

x = randn(T, N,'gpuArray');
-7
répondu user3932983 2014-08-12 10:50:53