为什么并行包比使用apply慢?

我正在尝试确定何时使用parallel程序包来加快运行某些分析所需的时间。 我需要做的事情之一是创buildmatrix,比较两个dataframe中具有不同行数的variables。 我问了一个关于在StackOverflow上进行的有效方法的问题,并在我的博客上写了关于testing的问题。 因为我对最好的方法感到满意,所以我想通过并行运行来加速这个过程。 下面的结果是基于一个2GB的i7 Mac与8GB的RAM。 我感到惊讶的是, parallel包,特别是parSapply函数,比使用apply函数更糟糕。 下面是复制代码的代码。 请注意,我目前只使用我创build的两个列中的一个,但最终要同时使用这两个列。

执行时间http://jason.bryer.orghttp://img.dovov.comParalleVsApplyTiming.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)') 

平行运行的工作会招致开销。 只有在工作节点上执行的任务需要大量时间,并行化才能提高整体性能。 当单个工作只需要几毫秒时,不断裁员的开销会降低整体绩效。 诀窍是把工作分成节点,使得工作足够长,比如说至less几秒钟。 我用它来同时运行六个Fortran模型,但是这些单独的模型运行花费了几个小时,几乎否定了开销的影响。

请注意,我还没有运行您的示例,但上面描述的情况往往是并行化需要比顺序运行更长的问题。

这些差异可以归因于1)通信开销(特别是如果您运行跨节点)和2)性能开销(例如,如果您的工作是不是比开始一个并行化,密集)。 通常情况下,如果你平行的任务不是那么耗时,那么你会发现平行化并没有太多的效果(这在大数据集上是非常明显的。

即使这可能不会直接回答你的基准,但我希望这应该是相当直接的,可以涉及到。 作为一个例子,在这里,我构造了data.frame行的数据data.frame1e61e4唯一的列group条目和val列中的一些值。 然后我使用plyr parallel运行使用doMC并且没有并行化。

 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 

正如你所看到的, plyr并行版本运行速度要慢3.5倍

现在,让我使用相同的data.frame ,而不是计算sum ,让我构造一个更加苛刻的函数,比如说median(.) * median(rnorm(1e4) ((无意义的,是)):

你会看到潮汐正在开始转移:

 # 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 

在这里, 并行版本是非并行版本的1.752 times

编辑:继@保罗的评论,我只是用Sys.sleep()实现了一个小的延迟。 当然结果是显而易见的。 但是为了完整起见,下面是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 

这里的区别并不奇怪。

完全同意@Arun和@PaulHiemestra关于为什么…的论点 你的问题的一部分。

但是,在你的情况下,你可以从parallel包中获得一些好处(至less如果你没有被Windows阻塞的话)。 可能的解决scheme是使用mclapply而不是parSapply ,它依赖于快速分叉和共享内存。

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

当然,这里不需要嵌套的system.time 。 用我的两个核心,我得到了:

在这里输入图像描述