使用foreach并行化的问题

Cha*_*lie 11 parallel-processing foreach r

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

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

这是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))
  })
}
Run Code Online (Sandbox Code Playgroud)

接下来,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))
  }
}
Run Code Online (Sandbox Code Playgroud)

我得到以下结果:

> 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 
Run Code Online (Sandbox Code Playgroud)

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))
  }))
}
Run Code Online (Sandbox Code Playgroud)

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

> system.time(MCCltSim(nSims=10000, size=100))
   user  system elapsed 
  0.924   0.032   0.307 
Run Code Online (Sandbox Code Playgroud)

启动一个新的R会话,我们可以foreach使用doMC而不是doSNOW调用来尝试实现

library(doMC)
registerDoMC()
Run Code Online (Sandbox Code Playgroud)

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

> system.time(FECltSim(nSims=10000, size=100))
   user  system elapsed 
  6.800   0.024   6.887 
Run Code Online (Sandbox Code Playgroud)

与非并行化运行时相比,这"仅"增加了14倍.

结论:我的foreach代码在其中一个doSNOW或两个都没有高效运行doMC.知道为什么吗?

谢谢,查理

小智 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))
}
Run Code Online (Sandbox Code Playgroud)

我打赌你会发现foreach做得很好.

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


Jor*_*eys 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))
  }
}
Run Code Online (Sandbox Code Playgroud)

这给你一个向量,不需要在循环中显式地创建它。也不需要使用 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
...
Run Code Online (Sandbox Code Playgroud)

超过40%的时间是在忙着选择东西。它还在整个操作中使用了许多其他功能。实际上,foreach只有当您执行非常耗时的函数的轮数相对较少时,才建议这样做。

其他两个解决方案基于不同的技术构建,并且在 R 中执行的操作要少得多。在侧节点上,snow实际上最初是为了在集群上工作而不是在单个工作站上工作而开发的,就像multicore这样。