【问题标题】:Why is the parallel package slower than just using apply?为什么并行包比只使用 apply 慢?
【发布时间】:2013-01-30 21:38:57
【问题描述】:

我正在尝试确定何时使用parallel 包来加快运行某些分析所需的时间。我需要做的一件事是创建矩阵来比较具有不同行数的两个数据帧中的变量。我在StackOverflow 上问了一个关于有效执行方式的问题,并在我的blog 上写了关于测试的文章。因为我对最好的方法感到满意,所以我想通过并行运行来加速这个过程。以下结果基于具有 8GB RAM 的 2ghz i7 Mac。我很惊讶parallel 包,尤其是parSapply 函数,比仅使用apply 函数更糟糕。复制它的代码如下。请注意,我目前只使用我创建的两列之一,但最终想同时使用。


(来源:bryer.org

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

【问题讨论】:

  • (+1) 很好的表述,我觉得很有趣! (除了博客插件:))
  • 抱歉,我不是说它是一个插件 :-) 只是想提供我目前拥有的所有信息。

标签: r parallel-processing


【解决方案1】:

并行运行作业会产生开销。只有当您在工作节点上触发的作业需要大量时间时,并行化才能提高整体性能。当单个作业只需要几毫秒时,不断解雇作业的开销会降低整体性能。诀窍是在节点上划分工作,使工作足够长,比如至少几秒钟。我用它来同时运行六个 Fortran 模型效果很好,但是这些单独的模型运行需要几个小时,几乎抵消了开销的影响。

请注意,我没有运行您的示例,但我上面描述的情况通常是并行化花费的时间比顺序运行更长的问题。

【讨论】:

  • 谢谢保罗。如果您注意到我实际上跟踪了两次,总体上包括makeCluster 调用,然后一次仅用于我的执行,以便了解有多少开销。从结果看来,启动线程的开销很小。我对执行中的开销感到困惑。
  • 在每个工作节点上都有一个 R 进程,开销包括向每个节点发送指令,告诉它们要做什么,然后收集结果。
【解决方案2】:

这些差异可归因于 1) 通信开销(尤其是跨节点运行时)和 2) 性能开销(例如,与启动并行化相比,如果您的工作不是那么密集)。通常,如果您要并行化的任务不是那么耗时,那么您通常会发现并行化并没有太大的影响(这在大型数据集上非常明显。

尽管这可能无法直接回答您的基准测试,但我希望这应该相当简单并且可以与之相关。例如,在这里,我构造了一个 data.frame1e6 行,其中 1e4 唯一列 group 条目和列 val 中的一些值。然后我在 parallel 中使用 plyr 运行 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更快

编辑:根据@Paul 的评论,我刚刚使用Sys.sleep() 实现了一个小延迟。结果当然是显而易见的。但为了完整起见,这里是 20*2 数据帧的结果:

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

这里的区别并不奇怪。

【讨论】:

  • +1 用于示例代码。最后,这一切都归结为有效地将您的问题分成相当大的块。
  • 一个非常简单的例子是使用一个只休眠十秒的函数,并行运行确实很好。如果你把一个 20 小时的工作分成 60 块,并行化将会带来很大的改进。例如,每十行像素处理一张卫星图像比每次只向工作人员提供一个像素要有效得多,所以要明智地切片。在 plyr 的情况下,切换到 data.table 可能会比并行运行带来更大的改进。所以选择合适的工具也是一个问题。
  • 此外,当在 R 中并行运行模型时,使用 6 个工作线程的性能提高了 5.6 倍,但这些运行需要数小时。
【解决方案3】:

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

但是,在您的情况下,您似乎可以从parallel 包中获得一些好处(至少如果您不使用 Windows 的话)。可能的解决方案是使用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。用我的 2 个核心,我得到了:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-28
    • 1970-01-01
    • 1970-01-01
    • 2013-07-12
    • 2012-01-01
    • 2011-03-23
    • 2013-09-01
    • 1970-01-01
    相关资源
    最近更新 更多