2016-03-11 13 views
9

Ho pensato che in generale usando %>% non avrebbe avuto un effetto notevole sulla velocità. Ma in questo caso gira 4x più lentamente.Perché utilizzare dplyr pipe (%>%) più lento di un'espressione non pipe equivalente?

library(dplyr) 
library(microbenchmark) 

set.seed(0) 
dummy_data <- dplyr::data_frame(
    id=floor(runif(100000, 1, 100000)) 
    , label=floor(runif(100000, 1, 4)) 
) 

microbenchmark(dummy_data %>% group_by(id) %>% summarise(list(unique(label)))) 
microbenchmark(dummy_data %>% group_by(id) %>% summarise(label %>% unique %>% list)) 

Senza tubo:

min  lq  mean median  uq  max neval 
1.691441 1.739436 1.841157 1.812778 1.880713 2.495853 100 

con tubo:

min  lq  mean median  uq  max neval 
6.753999 6.969573 7.167802 7.052744 7.195204 8.833322 100 

Perché %>% così molto più lento in questa situazione? C'è un modo migliore per scrivere questo?

+7

Non si dovrebbero lasciare le unità. In questo caso, probabilmente stai parlando di millisecondi o anche di microsecondi. –

+4

Se stai cercando di confrontare due snippet, eseguili entrambi nella stessa chiamata 'microbenchmark':' microbenchmark (code1 = {... primo snippet ...}, code2 = {... secondo snippet ...}) '(o senza i nomi) in modo da poter confrontare direttamente i tempi. – alistaire

risposta

26

Quello che potrebbe essere un effetto trascurabile in un'applicazione completa del mondo reale diventa non trascurabile quando si scrivono one-liner che dipendono dal tempo in precedenza "trascurabili". Ho il sospetto che se il profilo i test allora la maggior parte del tempo sarà nella clausola summarize, così lascia microbenchmark qualcosa di simile a quello:

> set.seed(99);z=sample(10000,4,TRUE) 
> microbenchmark(z %>% unique %>% list, list(unique(z))) 
Unit: microseconds 
        expr  min  lq  mean median  uq  max neval 
z %>% unique %>% list 142.617 144.433 148.06515 145.0265 145.969 297.735 100 
     list(unique(z)) 9.289 9.988 10.85705 10.5820 11.804 12.642 100 

Questo sta facendo qualcosa di un po 'diverso per il codice, ma illustra il punto. I tubi sono più lenti.

Poiché le tubature devono ristrutturare la chiamata di R nella stessa che le valutazioni di funzione stanno utilizzando e quindi valutarle. Quindi lo ha lo più lento. Da quanto dipende da quanto siano veloci le funzioni. Le chiamate a unique e list sono piuttosto veloci in R, quindi l'intera differenza qui è il sovraccarico del tubo.

profilatura espressioni come questo mi ha mostrato la maggior parte del tempo viene speso nelle funzioni di tubo:

      total.time total.pct self.time self.pct 
"microbenchmark"    16.84  98.71  1.22  7.15 
"%>%"       15.50  90.86  1.22  7.15 
"eval"       5.72  33.53  1.18  6.92 
"split_chain"     5.60  32.83  1.92 11.25 
"lapply"      5.00  29.31  0.62  3.63 
"FUN"       4.30  25.21  0.24  1.41 
..... stuff ..... 

poi da qualche parte intorno al 15 ° posto il lavoro vero viene fatto:

"as.list"      1.40  8.13  0.66  3.83 
"unique"      1.38  8.01  0.88  5.11 
"rev"       1.26  7.32  0.90  5.23 

Considerando che, se chiamate semplicemente le funzioni come intendevano le camere, R le raggiunge direttamente:

      total.time total.pct self.time self.pct 
"microbenchmark"    2.30  96.64  1.04 43.70 
"unique"      1.12  47.06  0.38 15.97 
"unique.default"    0.74  31.09  0.64 26.89 
"is.factor"     0.10  4.20  0.10  4.20 

Da qui la raccomandazione spesso citata che le pipe vanno bene sulla riga di comando dove il cervello pensa in catene, ma non in funzioni che potrebbero essere time-critical. In pratica questo overhead sarà probabilmente spazzato via in una chiamata allo glm con poche centinaia di punti dati, ma questa è un'altra storia ....

+5

FWIW, 'library (pipeR); z% >>% unique% >>% list' fa la stessa cosa ed è circa 4 volte più veloce della versione 'magrittr', sebbene sia ancora più lenta di quella base pura. – BrodieG

+3

Dal pacchetto funzionale, 'Compose' è anche la libreria' più veloce (funzionale); microbenchmark (mag = z%>% unique%>% list, base = lista (unique (z)), fun = Compose (unique, list) (z)) '(comunque 6 volte più lento della base, però). – Frank