【问题标题】:Problems using foreach parallelization使用 foreach 并行化的问题
【发布时间】:2011-02-15 17:56:08
【问题描述】:

我正在尝试比较并行化选项。具体来说,我将标准的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会话,我们可以尝试使用doMC而不是doSNOW来实现foreach,调用

library(doMC)
registerDoMC()

然后像上面一样运行FECltSim(),仍然找到

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

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

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

谢谢, 查理

【问题讨论】:

    标签: r foreach parallel-processing


    【解决方案1】:

    按照 Joris 所说的话,foreach() 是最好的,当作业数量不会大大超过您将使用的处理器数量时。或者更一般地说,当每项工作本身需要大量时间(例如几秒钟或几分钟)时。创建线程有很多开销,所以你真的不想将它用于很多小工作。如果您进行 1000 万次模拟而不是 10000 次模拟,并且您的代码结构如下:

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

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

    还要注意在这种应用程序中使用 replicate() 而不是 sapply。实际上,foreach 包有一个类似的便利功能,times(),可以在这种情况下应用。当然,如果你的代码不是每次都用相同的参数做一个简单的模拟,你需要sapply()foreach()

    【讨论】:

    • 感谢分批拆分流程的建议;我敢打赌,这将节省大量的沟通时间。我以前见过replicate,但不是times
    【解决方案2】:

    首先,您可以将 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 一样。

    【讨论】:

    • 再次感谢乔恩斯。我实际上以前没有使用过 Rprof,你能解释一下如何解释这个输出或者给我一个可以的资源吗?我查看了 R 的原生帮助文件 summaryRprof 并没有那么有用。
    • @Charlie : self.time 是函数本身所花费的时间。 self.pct 是函数本身花费的总时间的百分比。 total.time 是在该函数或它调用的任何其他函数中花费的时间。例如:f1 &lt;- function(x){f2(x)},则 self.time 是 f1() 中的时间,total.time 是 f1()f2() 中的时间 - 如果从 f1() 调用! total.pct 又是总时间的百分比。刚开始有点混乱,但是对于优化来说非常强大。
    • 谢谢。 $$&lt;-.Call 和我猜其他人怎么样?是否有文件解释这些代表的含义?
    • 我检查了帮助文件(summaryRprofRprof),但他们没有讨论解释。
    • @Charlie : 我指的是 .Call、'$' 等的帮助文件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-28
    • 1970-01-01
    • 1970-01-01
    • 2016-07-11
    • 2020-12-18
    • 2019-03-09
    • 1970-01-01
    相关资源
    最近更新 更多