使用foreach并行化的问题

时间:2011-02-15 17:56:08

标签: r foreach parallel-processing

我正在尝试比较并行化选项。具体来说,我将标准SNOWmulitcore实施与使用doSNOWdoMCforeach的实施进行比较。作为一个样本问题,我通过多次计算从标准正态分布中抽取的样本的均值来说明中心极限定理。这是标准代码:

CltSim <- function(nSims=1000, size=100, mu=0, sigma=1){
  sapply(1:nSims, function(x){
    mean(rnorm(n=size, mean=mu, sd=sigma))
  })
}

以下是SNOW实施:

library(snow)
cl <- makeCluster(2)

ParCltSim <- function(cluster, nSims=1000, size=100, mu=0, sigma=1){
  parSapply(cluster, 1:nSims, function(x){
    mean(rnorm(n=size, mean=mu, sd=sigma))
  })
}

接下来,doSNOW方法:

library(foreach)
library(doSNOW)
registerDoSNOW(cl)

FECltSim <- function(nSims=1000, size=100, mu=0, sigma=1) {
  x <- numeric(nSims)
  foreach(i=1:nSims, .combine=cbind) %dopar% {
    x[i] <- mean(rnorm(n=size, mean=mu, sd=sigma))
  }
}

我得到以下结果:

> system.time(CltSim(nSims=10000, size=100))
   user  system elapsed 
  0.476   0.008   0.484 
> system.time(ParCltSim(cluster=cl, nSims=10000, size=100))
   user  system elapsed 
  0.028   0.004   0.375 
> system.time(FECltSim(nSims=10000, size=100))
   user  system elapsed 
  8.865   0.408  11.309 

SNOW实现相对于非平行运行削减了大约23%的计算时间(随着模拟数量的增加,时间节省变得更大,正如我们所期望的那样)。 foreach尝试实际增加运行时间20倍。此外,如果我将%dopar%更改为%do%并检查循环的非平行版本,则需要7秒钟。

此外,我们可以考虑multicore包。为multicore编写的模拟是

library(multicore)
MCCltSim <- function(nSims=1000, size=100, mu=0, sigma=1){
  unlist(mclapply(1:nSims, function(x){
    mean(rnorm(n=size, mean=mu, sd=sigma))
  }))
}

我们获得比SNOW更快的速度提升:

> system.time(MCCltSim(nSims=10000, size=100))
   user  system elapsed 
  0.924   0.032   0.307 

开始新的R会话,我们可以使用foreach代替doMC尝试doSNOW实施,并致电

library(doMC)
registerDoMC()

然后如上所述运行FECltSim(),仍然找到

> system.time(FECltSim(nSims=10000, size=100))
   user  system elapsed 
  6.800   0.024   6.887 

这比非并行化运行时“仅”增加了14倍。

结论:我的foreach代码无法在doSNOWdoMC下有效运行。知道为什么吗?

谢谢, 查理

2 个答案:

答案 0 :(得分:5)

要遵循Joris所说的内容,foreach()最适合的工作数量并不会超过您将使用的处理器数量。或者更一般地说,当每个工作自己花费大量时间(比如几秒或几分钟)。创建线程有很多开销,所以你真的不想将它用于很多小工作。如果你正在做1000万个sim而不是1万个,那么你构建了这样的代码:

nSims = 1e7
nBatch = 1e6
foreach(i=1:(nSims/nBatch), .combine=c) %dopar% {
  replicate(nBatch, mean(rnorm(n=size, mean=mu, sd=sigma))
}

我打赌你会发现foreach的表现相当不错。

另请注意replicate()用于此类应用而不是使用。实际上,foreach包具有类似的便利功能times(),可以在这种情况下应用。当然,如果您的代码每次都没有使用相同的参数进行简单的模拟,则需要sapply()foreach()

答案 1 :(得分:4)

首先,您可以更简洁地编写foreach代码:

FECltSim <- function(nSims=1000, size=100, mu=0, sigma=1) {
  foreach(i=1:nSims, .combine=c) %dopar% {
    mean(rnorm(n=size, mean=mu, sd=sigma))
  }
}

这为您提供了一个向量,无需在循环中明确地使用它。也不需要使用cbind,因为你的结果每次只是一个数字。所以.combine=c会做

与foreach相关的是,它在内核之间进行通信会产生相当多的开销,并且可以将不同内核的结果放在一起。快速查看配置文件可以清楚地看到这一点:

$by.self
                         self.time self.pct total.time total.pct
$                             5.46    41.30       5.46     41.30
$<-                           0.76     5.75       0.76      5.75
.Call                         0.76     5.75       0.76      5.75
...

超过40%的时间忙于选择东西。它还为整个操作使用了许多其他功能。实际上,foreach仅在通过非常耗时的函数进行相对较少的轮次时才是可取的。

另外两个解决方案是基于不同的技术构建的,并且在R中做得少得多。在一个副节点上,snow实际上最初是针对集群而不是单个工作站开发的,例如multicore