web-dev-qa-db-fra.com

Pourquoi les boucles sont-elles lentes dans R?

Je sais que les boucles sont lentes dans 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 apply est rapide? apply appelle plusieurs sous-fonctions - cela ne semble pas rapide.

pdate: 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?"

78
isomorphismes

Les boucles en R sont lentes pour la même raison que tout langage interprété est lent: chaque opération transporte beaucoup de bagages supplémentaires.

Regarder R_execClosure dans eval.c (c'est la fonction appelée pour appeler une fonction définie par l'utilisateur). Il fait près de 100 lignes et effectue toutes sortes d'opérations - création d'un environnement pour l'exécution, affectation d'arguments à l'environnement, etc.

Pensez à combien moins de choses se produisent lorsque vous appelez une fonction en C (pousser les arguments sur pour empiler, sauter, faire apparaître les arguments).

C'est pourquoi vous obtenez des timings comme ceux-ci (comme joran l'a souligné dans le commentaire, ce n'est pas réellement apply qui est rapide; c'est la boucle C interne dans mean qui est rapide. apply est juste un ancien code R ordinaire):

A = matrix(as.numeric(1:100000))

Utilisation d'une boucle: 0,342 secondes:

system.time({
    Sum = 0
    for (i in seq_along(A)) {
        Sum = Sum + A[[i]]
    }
    Sum
})

Utilisation de la somme: infiniment petite:

sum(A)

C'est un peu déconcertant car, asymptotiquement, la boucle est tout aussi bonne que sum; il n'y a aucune raison pratique que cela soit lent; il fait juste plus de travail supplémentaire à chaque itération.

Considérez donc:

# 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 )

Car ( dans R est un opérateur, et nécessite en fait 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 que ( astuce en C.

68
Owen

Ce n'est pas toujours le cas, les boucles sont lentes et apply est rapide. Il y a une belle discussion à ce sujet dans le mai 2008, numéro 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 mauvaise idée. Ce n'est pas forcément vrai. Dans certains cas, il est difficile d'écrire du code vectorisé, ou le code vectorisé peut consommer une énorme quantité de mémoire.

Ils suggèrent en outre:

  • Initialisez les nouveaux objets à leur pleine longueur avant la boucle, plutôt que d'augmenter leur taille dans la boucle.
  • Ne faites pas dans une boucle des choses qui peuvent être faites en dehors de la boucle.
  • N'évitez pas les boucles simplement pour éviter les boucles.

Ils ont un exemple simple où une boucle for prend 1,3 s mais apply manque de mémoire.

76
Karl

La seule réponse à la question posée est; les boucles sont pas lentes si ce que vous devez faire est d'itérer sur un ensemble de données exécutant une fonction et 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 lentement qu'un appel lapply(). Le dernier point est bien couvert sur SO, par exemple dans ce Answer , et s'applique si le code impliqué dans la configuration et le fonctionnement de la boucle est une partie importante de la charge de calcul globale de la boucle .

Beaucoup de gens pensent que les boucles for() sont lentes parce que l'utilisateur écrit de mauvais code. En général (bien qu'il y ait plusieurs exceptions), si vous devez développer/agrandir un objet, cela impliquera également la copie de sorte que vous ayez à la fois la surcharge de copie et la croissance de l'objet. Cela ne se limite pas aux boucles, mais si vous copiez/agrandissez à chaque itération d'une boucle, bien sûr, la boucle va être lente car vous effectuez de nombreuses opérations de copie/agrandissement.

L'idiome général pour utiliser les boucles for() dans R est que vous allouez le stockage dont vous avez besoin avant le démarrage de la boucle, puis remplissez l'objet ainsi alloué. Si vous suivez cet idiome, les boucles ne seront pas lentes. C'est ce que apply() gère pour vous, mais il est juste caché à la vue.

Bien sûr, s'il existe une fonction vectorisée pour l'opération que vous implémentez avec la boucle for(), ne le faites pas . De même, n'utilisez pas apply() etc s'il existe une fonction vectorisée (par exemple apply(foo, 2, mean) est mieux exécutée via colMeans(foo)).

34
Gavin Simpson

Juste à titre de comparaison (ne lisez pas trop dedans!): J'ai exécuté une boucle (très) simple pour R et JavaScript dans Chrome et IE 8. Notez que Chrome effectue la compilation en code natif, et R avec le package du compilateur compile 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 en 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;
9
Tommy