¿Por qué el paquete paralelo es más lento que solo usar apply?

Estoy tratando de determinar cuándo usar el paquete parallel para acelerar el tiempo necesario para ejecutar algunos análisis. Una de las cosas que debo hacer es crear matrices que comparen variables en dos marcos de datos con diferente número de filas. Hice una pregunta sobre una forma eficiente de hacer en StackOverflow y escribí sobre pruebas en mi blog . Como me siento cómodo con el mejor enfoque, quería acelerar el proceso ejecutándolo en paralelo. Los resultados a continuación se basan en una Mac 2ghz i7 con 8 gb de RAM. Me sorprende que el paquete parallel , la función parSapply en particular, sea peor que solo usar la función apply . El código para replicar esto está debajo. Tenga en cuenta que actualmente solo estoy usando una de las dos columnas que creo, pero que eventualmente quiero usar ambas.

Tiempo de ejecución http://sofes.miximages.com/r/ParalleVsApplyTiming.png

 require(parallel) require(ggplot2) require(reshape2) set.seed(2112) results <- list() sizes <- seq(1000, 30000, by=5000) pb <- txtProgressBar(min=0, max=length(sizes), style=3) for(cnt in 1:length(sizes)) { i <- sizes[cnt] df1 <- data.frame(row.names=1:i, var1=sample(c(TRUE,FALSE), i, replace=TRUE), var2=sample(1:10, i, replace=TRUE) ) df2 <- data.frame(row.names=(i + 1):(i + i), var1=sample(c(TRUE,FALSE), i, replace=TRUE), var2=sample(1:10, i, replace=TRUE)) tm1 <- system.time({ df6 <- sapply(df2$var1, FUN=function(x) { x == df1$var1 }) dimnames(df6) <- list(row.names(df1), row.names(df2)) }) rm(df6) tm2 <- system.time({ cl <- makeCluster(getOption('cl.cores', detectCores())) tm3 <- system.time({ df7 <- parSapply(cl, df1$var1, FUN=function(x, df2) { x == df2$var1 }, df2=df2) dimnames(df7) <- list(row.names(df1), row.names(df2)) }) stopCluster(cl) }) rm(df7) results[[cnt]] <- c(apply=tm1, parallel.total=tm2, parallel.exec=tm3) setTxtProgressBar(pb, cnt) } toplot <- as.data.frame(results)[,c('apply.user.self','parallel.total.user.self', 'parallel.exec.user.self')] toplot$size <- sizes toplot <- melt(toplot, id='size') ggplot(toplot, aes(x=size, y=value, colour=variable)) + geom_line() + xlab('Vector Size') + ylab('Time (seconds)') 

La ejecución de trabajos en paralelo incurre en gastos generales. Solo si los trabajos que usted inicia en los nodos de trabajadores toman una cantidad significativa de tiempo, la paralelización mejora el desempeño general. Cuando los trabajos individuales toman solo milisegundos, la sobrecarga de disparar constantemente trabajos deteriorará el rendimiento general. El truco es dividir el trabajo sobre los nodos de tal manera que los trabajos sean lo suficientemente largos, digamos al menos unos segundos. Utilicé esto con gran efecto ejecutando seis modelos de Fortran simultáneamente, pero estas ejecuciones de modelos individuales tomaron horas, casi anulando el efecto de sobrecarga.

Tenga en cuenta que no he ejecutado su ejemplo, pero la situación que describo anteriormente es a menudo el problema cuando la parallización lleva más tiempo que la ejecución secuencial.

Estas diferencias se pueden atribuir a 1) gastos generales de comunicación (especialmente si se ejecuta en nodos) y 2) gastos generales de rendimiento (si su trabajo no es tan intenso en comparación con iniciar una paralelización, por ejemplo). Por lo general, si la tarea que está paralelizando no consume mucho tiempo, entonces, en general, encontrará que la paralelización NO tiene mucho efecto (lo cual es mucho más visible en grandes conjuntos de datos).

Aunque esto puede no responder directamente a su evaluación comparativa, espero que esto sea más sencillo y pueda relacionarse. Como ejemplo, aquí, construyo un data.frame con 1e6 filas con 1e4 entradas de group columnas únicas y algunos valores en columna val . Y luego corro usando plyr en parallel usando doMC y sin paralelización.

 df <- data.frame(group = as.factor(sample(1:1e4, 1e6, replace = T)), val = sample(1:10, 1e6, replace = T)) > head(df) group val # 1 8498 8 # 2 5253 6 # 3 1495 1 # 4 7362 9 # 5 2344 6 # 6 5602 9 > dim(df) # [1] 1000000 2 require(plyr) require(doMC) registerDoMC(20) # 20 processors # parallelisation using doMC + plyr P.PLYR <- function() { o1 <- ddply(df, .(group), function(x) sum(x$val), .parallel = TRUE) } # no parallelisation PLYR <- function() { o2 <- ddply(df, .(group), function(x) sum(x$val), .parallel = FALSE) } require(rbenchmark) benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed") test replications elapsed relative user.self sys.self user.child sys.child 2 PLYR() 2 8.925 1.000 8.865 0.068 0.000 0.000 1 P.PLYR() 2 30.637 3.433 15.841 13.945 8.944 38.858 

Como puede ver, la versión paralela de plyr ejecuta 3.5 veces más lenta

Ahora, permítanme usar el mismo data.frame , pero en lugar de calcular sum , permítanme construir una función un poco más exigente, por ejemplo, median(.) * median(rnorm(1e4) ((sin sentido, sí):

Verás que las mareas están comenzando a cambiar:

 # parallelisation using doMC + plyr P.PLYR <- function() { o1 <- ddply(df, .(group), function(x) median(x$val) * median(rnorm(1e4)), .parallel = TRUE) } # no parallelisation PLYR <- function() { o2 <- ddply(df, .(group), function(x) median(x$val) * median(rnorm(1e4)), .parallel = FALSE) } > benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed") test replications elapsed relative user.self sys.self user.child sys.child 1 P.PLYR() 2 41.911 1.000 15.265 15.369 141.585 34.254 2 PLYR() 2 73.417 1.752 73.372 0.052 0.000 0.000 

Aquí, la versión paralela es 1.752 times más rápida que la versión no paralela.

Editar: Siguiendo el comentario de @ Paul, acabo de implementar un pequeño retraso usando Sys.sleep() . Por supuesto, los resultados son obvios. Pero solo para completar, aquí está el resultado en un 20 * 2 data.frame:

 df <- data.frame(group=sample(letters[1:5], 20, replace=T), val=sample(20)) # parallelisation using doMC + plyr P.PLYR <- function() { o1 <- ddply(df, .(group), function(x) { Sys.sleep(2) median(x$val) }, .parallel = TRUE) } # no parallelisation PLYR <- function() { o2 <- ddply(df, .(group), function(x) { Sys.sleep(2) median(x$val) }, .parallel = FALSE) } > benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed") # test replications elapsed relative user.self sys.self user.child sys.child # 1 P.PLYR() 2 4.116 1.000 0.056 0.056 0.024 0.04 # 2 PLYR() 2 20.050 4.871 0.028 0.000 0.000 0.00 

La diferencia aquí no es sorprendente.

Totalmente de acuerdo con los argumentos de @Arun y @PaulHiemestra sobre ¿Por qué …? parte de tu pregunta.

Sin embargo, parece que puede obtener algunos beneficios del paquete parallel en su situación (al menos si no está atrapado con Windows). La posible solución es usar mclapply lugar de parSapply , que se basa en bifurcaciones rápidas y memoria compartida.

  tm2 <- system.time({ tm3 <- system.time({ df7 <- matrix(unlist(mclapply(df2$var1, FUN=function(x) {x==df1$var1}, mc.cores=8)), nrow=i) dimnames(df7) <- list(row.names(df1), row.names(df2)) }) }) 

Por supuesto, system.time nested no es necesario aquí. Con mis 2 núcleos obtuve:

enter image description here