Perché i loop sono lenti in R?


87

So che i loop sono lenti Re che invece dovrei provare a fare le cose in modo vettorializzato.

Ma perché? Perché i loop sono lenti ed applyè veloce? applychiama diverse sotto-funzioni: non sembra veloce.

Aggiornamento: mi dispiace, la domanda era mal posta. Stavo confondendo la vettorizzazione con apply. La mia domanda avrebbe dovuto essere,

"Perché la vettorizzazione è più veloce?"


3
Avevo l'impressione che "l'applicazione è molto più veloce di quella per i cicli" in R è un po 'un mito . Che le system.timeguerre nelle risposte inizino ...
joran

1
Molte buone informazioni qui sull'argomento: stackoverflow.com/questions/2275896/…
Chase

7
Per la cronaca: Applica NON è vettorializzazione. Applicare è una struttura ad anello con diversi (come in: no) effetti collaterali. Vedere la discussione a cui si collega @Chase.
Joris Meys

4
I loop in S ( S-Plus ?) Erano tradizionalmente lenti. Questo non è il caso di R ; in quanto tale, la tua domanda non è realmente rilevante. Non so quale sia la situazione oggi con S-Plus .
Gavin Simpson

4
non mi è chiaro il motivo per cui la domanda è stata votata pesantemente - questa domanda è molto comune tra coloro che arrivano a R da altre aree e dovrebbe essere aggiunta alle FAQ.
patrickmdnet

Risposte:


69

I loop in R sono lenti per lo stesso motivo per cui qualsiasi linguaggio interpretato è lento: ogni operazione porta con sé un sacco di bagagli extra.

Guarda R_execClosureineval.c (questa è la funzione chiamata per chiamare una funzione definita dall'utente). È lungo quasi 100 righe ed esegue tutti i tipi di operazioni: creazione di un ambiente per l'esecuzione, assegnazione di argomenti nell'ambiente, ecc.

Pensa quanto meno accade quando chiami una funzione in C (push args on to stack, jump, pop args).

Quindi questo è il motivo per cui ottieni tempi come questi (come ha sottolineato joran nel commento, in realtà non applyè veloce; è il ciclo C interno mean che è veloce. applyÈ solo un normale vecchio codice R):

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

Utilizzando un ciclo: 0,342 secondi:

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

Utilizzando sum: incommensurabilmente piccolo:

sum(A)

È un po 'sconcertante perché, asintoticamente, il loop è buono quanto sum; non c'è motivo pratico che dovrebbe essere lento; sta solo facendo più lavoro extra ogni iterazione.

Quindi considera:

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

(Quell'esempio è stato scoperto da Radford Neal )

Perché (in R è un operatore e in realtà richiede una ricerca del nome ogni volta che lo usi:

> `(` = function(x) 2
> (3)
[1] 2

Oppure, in generale, le operazioni interpretate (in qualsiasi lingua) hanno più passaggi. Naturalmente, questi passaggi offrono anche vantaggi: non potresti fare quel (trucco in C.


10
Allora qual è il punto dell'ultimo esempio? Non fare cose stupide in R e aspettarti che le faccia rapidamente?
Chase

6
@ Chase Immagino che sia un modo per dirlo. Sì, volevo dire che un linguaggio come il C non avrebbe differenze di velocità con parentesi nidificate, ma R non ottimizza o compila.
Owen

1
Anche () o {} nel corpo del ciclo: tutte queste cose implicano ricerche di nomi. O in generale, in R, quando scrivi di più, l'interprete fa di più.
Owen

1
Non sono sicuro che punto stai cercando di fare con i for()loop? Non stanno affatto facendo la stessa cosa. Il for()ciclo sta iterando su ogni elemento di Ae sommandoli. La apply()chiamata sta passando l'intero vettore A[,1](il tuo Aha una sola colonna) a una funzione vettorizzata mean(). Non vedo come questo aiuti la discussione e confonda solo la situazione.
Gavin Simpson

3
@Owen Sono d'accordo con il tuo punto generale, ed è importante; non usiamo R perché sta battendo record di velocità, lo usiamo perché è facile da usare e molto potente. Quel potere arriva con il prezzo dell'interpretazione. Era solo poco chiaro quello che stavi cercando di mostrare nel for()vs apply()esempio. Penso che dovresti rimuovere quell'esempio poiché mentre la somma è la parte più importante del calcolo della media, tutto il tuo esempio mostra davvero la velocità di una funzione vettorizzata, mean()sull'iterazione simile a C sugli elementi.
Gavin Simpson

79

Non è sempre il caso che i loop siano lenti e applyveloci. C'è una bella discussione su questo nel numero di maggio 2008 di R News :

Uwe Ligges e John Fox. R Help Desk: come posso evitare questo loop o renderlo più veloce? R News, 8 (1): 46-50, maggio 2008.

Nella sezione "Loops!" (a partire da pag.48), dicono:

Molti commenti su R affermano che l'uso dei loop è un'idea particolarmente cattiva. Questo non è necessariamente vero. In alcuni casi, è difficile scrivere codice vettorizzato o il codice vettorializzato può consumare un'enorme quantità di memoria.

Suggeriscono inoltre:

  • Inizializza i nuovi oggetti a lunghezza intera prima del ciclo, invece di aumentare la loro dimensione all'interno del ciclo.
  • Non fare cose in un ciclo che può essere fatto al di fuori del ciclo.
  • Non evitare i loop semplicemente per evitare i loop.

Hanno un semplice esempio in cui un forciclo richiede 1,3 secondi ma applyesaurisce la memoria.


35

L'unica risposta alla domanda posta è; i cicli non sono lenti se quello che devi fare è iterare su un insieme di dati che eseguono una funzione e quella funzione o l'operazione non è vettorizzata. Un for()ciclo sarà veloce, in generale, come apply(), ma forse un po 'più lento di una lapply()chiamata. L'ultimo punto è ben trattato su SO, ad esempio in questa risposta , e si applica se il codice coinvolto nella configurazione e nel funzionamento del loop è una parte significativa del carico computazionale complessivo del loop .

Il motivo per cui molte persone pensano che i for()loop siano lenti è perché loro, l'utente, stanno scrivendo un codice errato. In generale (sebbene ci siano diverse eccezioni), se è necessario espandere / far crescere un oggetto, anche questo comporterà la copia in modo da avere sia l'overhead di copiare che di aumentare l'oggetto. Questo non è limitato solo ai loop, ma se copi / cresci ad ogni iterazione di un loop, ovviamente, il loop sarà lento perché stai incorrendo in molte operazioni di copia / crescita.

L'idioma generale per usare i for()loop in R è che si alloca lo spazio di archiviazione necessario prima che il ciclo inizi, quindi si inserisce l'oggetto così allocato. Se segui questo idioma, i loop non saranno lenti. Questo è ciò che apply()gestisce per te, ma è solo nascosto alla vista.

Ovviamente, se esiste una funzione vettorizzata per l'operazione che stai implementando con il for()ciclo, non farlo . Allo stesso modo, non utilizzare apply()ecc. Se esiste una funzione vettorizzata (ad esempio, apply(foo, 2, mean)è meglio eseguita tramite colMeans(foo)).


9

Proprio come confronto (non leggere troppo!): Ho eseguito un (molto) semplice ciclo for in R e in JavaScript in Chrome e IE 8. Nota che Chrome esegue la compilazione in codice nativo e R con il compilatore il pacchetto viene compilato in 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, ci sono voluti 1162 ms in S-Plus ...

E lo "stesso" codice di 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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.