Je sais que les boucles sont lentes en R
et que je devrais plutôt essayer de faire les choses de manière vectorisée.
Mais pourquoi? Pourquoi les boucles sont-elles lentes et les apply
rapides? apply
appelle plusieurs sous-fonctions – cela ne semble pas rapide.
Mise à jour: Je suis désolé, la question était mal posée. Je confondais la vectorisation avec apply
. Ma question aurait dû être,
“Pourquoi la vectorisation est-elle plus rapide?”
Les boucles dans R sont lentes pour la même raison que tout langage interprété est lent: chaque opération transporte beaucoup de bagages supplémentaires.
Regardez R_execClosure
dans eval.c
(c’est la fonction appelée pour appeler une fonction définie par l’utilisateur). Il a près de 100 lignes de long et effectue toutes sortes d’opérations – création d’un environnement d’exécution, atsortingbution d’arguments dans l’environnement, etc.
Pensez combien moins se produit lorsque vous appelez une fonction en C (poussez des arguments pour emstackr, sauter, exécuter des arguments).
Donc, c’est pourquoi vous obtenez des timings comme ceux-ci (comme joran l’a souligné dans le commentaire, ce n’est pas réellement le fait d’être rapide; la boucle C interne mean
que le code R est rapide).
A = masortingx(as.numeric(1:100000))
En utilisant une boucle: 0.342 secondes:
system.time({ Sum = 0 for (i in seq_along(A)) { Sum = Sum + A[[i]] } Sum })
En utilisant sum: incroyablement petit:
sum(A)
C’est un peu déconcertant car, asymptotiquement, la boucle est aussi bonne que la sum
; il n’y a aucune raison pratique pour que cela soit lent; c’est juste faire plus de travail supplémentaire à chaque itération.
Alors considérez:
# 0.370 seconds system.time({ I = 0 while (I < 100000) { 10 I = I + 1 } }) # 0.743 seconds -- double the time just adding parentheses system.time({ I = 0 while (I < 100000) { ((((((((((10)))))))))) I = I + 1 } })
(Cet exemple a été découvert par Radford Neal )
Parce que (
dans R est un opérateur et nécessite une recherche de nom chaque fois que vous l'utilisez:
> `(` = function(x) 2 > (3) [1] 2
Ou, en général, les opérations interprétées (dans n'importe quelle langue) ont plus d'étapes. Bien sûr, ces étapes offrent également des avantages: vous ne pouviez pas faire ça (
astuce en C.
Ce n’est pas toujours le cas si les boucles sont lentes et que l’ apply
est rapide. Il y a une belle discussion à ce sujet dans le numéro de mai 2008 de R News :
Uwe Ligges et John Fox. R Help Desk: Comment puis-je éviter cette boucle ou la rendre plus rapide? R News, 8 (1): 46-50, mai 2008.
Dans la section “Boucles!” (à partir de la page 48), ils disent:
De nombreux commentaires sur R indiquent que l’utilisation de boucles est une idée particulièrement mauvaise. Ce n’est pas forcément vrai. Dans certains cas, il est difficile d’écrire du code vectorisé, ou le code vectoriel peut consumr énormément de mémoire.
Ils suggèrent en outre:
- Initialiser les nouveaux objects sur toute leur longueur avant la boucle, plutôt que d’augmenter leur taille dans la boucle.
- Ne faites pas les choses en boucle qui peuvent être faites en dehors de la boucle.
- Ne pas éviter les boucles simplement pour éviter les boucles.
Ils ont un exemple simple où une boucle for
prend 1,3 sec mais apply
une mémoire insuffisante.
La seule réponse à la question posée est: les boucles ne sont pas lentes si ce que vous devez faire est d’itérer sur un dataset exécutant certaines fonctions et que cette fonction ou l’opération n’est pas vectorisée. Une boucle for()
sera aussi rapide, en général, que apply()
, mais peut-être un peu plus lente qu’un appel lapply()
. Le dernier point est bien couvert par SO, par exemple dans cette réponse , et s’applique si le code impliqué dans la configuration et le fonctionnement de la boucle représente une part importante de la charge de calcul globale de la boucle .
Pourquoi beaucoup de gens pensent for()
boucles for()
sont lentes parce qu’elles écrivent du code erroné. En général (bien qu’il y ait plusieurs exceptions), si vous avez besoin d’étendre / développer un object, cela impliquera aussi de copier pour avoir à la fois la surcharge de la copie et de la croissance de l’object. Cela ne se limite pas aux boucles, mais si vous copiez / développez à chaque itération d’une boucle, bien sûr, la boucle sera lente car vous subissez de nombreuses opérations de copie / croissance.
L’idiome général d’utilisation for()
boucles for()
dans R est que vous allouez le stockage dont vous avez besoin avant le démarrage de la boucle, puis remplissez l’object ainsi alloué. Si vous suivez cet idiome, les boucles ne seront pas lentes. C’est ce que apply()
pour vous, mais il est juste caché de la vue.
Bien sûr, si une fonction vectorisée existe pour l’opération que vous implémentez avec la boucle for()
, ne faites pas cela . De même, n’utilisez pas apply()
etc si une fonction vectorisée existe (par exemple, apply(foo, 2, mean)
est mieux exécuté via colMeans(foo)
).
Juste comme une comparaison (ne lisez pas trop dedans!): J’ai fait une (très) simple boucle en R et en JavaScript dans Chrome et IE 8. Notez que Chrome comstack en code natif, et R avec le compilateur package comstack en bytecode.
# In R 2.13.1, this took 500 ms f <- function() { sum<-0.5; for(i in 1:1000000) sum<-sum+i; sum } system.time( f() ) # And the compiled version took 130 ms library(compiler) g <- cmpfun(f) system.time( g() )
@Gavin Simpson: Btw, il a fallu 1162 ms dans S-Plus ...
Et le "même" code que JavaScript:
// In IE8, this took 282 ms // In Chrome 14.0, this took 4 ms function f() { var sum = 0.5; for(i=1; i<=1000000; ++i) sum = sum + i; return sum; } var start = new Date().getTime(); f(); time = new Date().getTime() - start;