doParallel(package)foreach不适用于R

时间:2016-06-10 14:36:30

标签: r parallel-processing parallel-foreach doparallel

我在PC(OS Linux)上运行以下代码(摘自doParallel's Vignettes),分别具有4个和8个物理和逻辑内核。

运行iter=1e+6或更少的代码,一切都很好,我可以从CPU使用情况看到所有核都用于此计算。但是,在迭代次数较多的情况下(例如iter=4e+6),似乎并行计算在这种情况下不起作用。当我还监视CPU使用情况时,计算中只涉及一个核心(100%使用率)。

示例1

require("doParallel")
require("foreach")
registerDoParallel(cores=8)
x <- iris[which(iris[,5] != "setosa"), c(1,5)]
iter=4e+6
ptime <- system.time({
    r <- foreach(i=1:iter, .combine=rbind) %dopar% {
        ind <- sample(100, 100, replace=TRUE)
        result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
        coefficients(result1)
    }
})[3]

你知道可能是什么原因吗?记忆能成为原因吗?

我搜索了一下,我发现THIS与我的问题有关,但问题是我没有遇到任何错误,OP似乎已经提出了一个解决办法,在{{1循环。但是我的循环中没有使用包,可以看出。

UPDATE1

我的问题仍未解决。根据我的实验,我不认为记忆可能是原因。我在系统上有8GB的内存,我运行以下简单的并行(在所有8个逻辑内核上)迭代:

例2

foreach

运行此代码时没有问题,但是当我监控CPU使用情况时,只有一个核心(8个)是100%。

UPDATE2

对于 Example2 ,@ SteveWeston(感谢指出这一点)表示(在评论中):“您的更新中的示例正在遭受微小的任务。只有主人才有任何实际工作要做的,包括发送任务和处理结果。这与原始示例的问题根本不同,原始示例在较少的迭代次数上使用了多个内核。“

然而, Example1 仍未解决。当我运行它并使用require("doParallel") require("foreach") registerDoParallel(cores=8) iter=4e+6 ptime <- system.time({ r <- foreach(i=1:iter, .combine=rbind) %dopar% { i } })[3] 监控流程时,更详细地说明了这一点:

让我们将所有8个已创建的流程htop命名为p1p8S的{​​{1}}列状态为htop,表示它正在运行且保持不变。但是,对于p1最多R,在几分钟后,状态将更改为p2(即不间断睡眠),并在几分钟后再次更改为p8(即终止但未被其父母收获)。你知道为什么会这样吗?

2 个答案:

答案 0 :(得分:3)

我认为你内存不足。这是该示例的修改版本,当您有许多任务时应该可以更好地工作。它使用doSNOW而不是doParallel,因为doSNOW允许您使用combine函数处理结果,因为它们由工作人员返回。此示例将这些结果写入文件以便使用更少的内存,但是它使用&#34; .final&#34;将结果读回到内存中。功能,但如果你没有足够的记忆,你可以跳过它。

library(doSNOW)
library(tcltk)
nw <- 4  # number of workers
cl <- makeSOCKcluster(nw)
registerDoSNOW(cl)

x <- iris[which(iris[,5] != 'setosa'), c(1,5)]
niter <- 15e+6
chunksize <- 4000  # may require tuning for your machine
maxcomb <- nw + 1  # this count includes fobj argument
totaltasks <- ceiling(niter / chunksize)

comb <- function(fobj, ...) {
  for(r in list(...))
    writeBin(r, fobj)
  fobj
}

final <- function(fobj) {
  close(fobj)
  t(matrix(readBin('temp.bin', what='double', n=niter*2), nrow=2))
}

mkprogress <- function(total) {
  pb <- tkProgressBar(max=total,
                      label=sprintf('total tasks: %d', total))
  function(n, tag) {
    setTkProgressBar(pb, n,
      label=sprintf('last completed task: %d of %d', tag, total))
  }
}
opts <- list(progress=mkprogress(totaltasks))
resultFile <- file('temp.bin', open='wb')

r <-
  foreach(n=idiv(niter, chunkSize=chunksize), .combine='comb',
          .maxcombine=maxcomb, .init=resultFile, .final=final,
          .inorder=FALSE, .options.snow=opts) %dopar% {
    do.call('c', lapply(seq_len(n), function(i) {
      ind <- sample(100, 100, replace=TRUE)
      result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
      coefficients(result1)
    }))
  }

我包含了一个进度条,因为此示例需要几个小时才能执行。

请注意,此示例还使用idiv包中的iterators函数来增加每个任务中的工作量。此技术称为 chunking ,通常可以提高并行性能。但是,使用idiv会弄乱任务索引,因为变量i现在是每任务索引而不是全局索引。对于全局索引,您可以编写一个包装idiv的自定义迭代器:

idivix <- function(n, chunkSize) {
  i <- 1
  it <- idiv(n, chunkSize=chunkSize)
  nextEl <- function() {
    m <- nextElem(it)  # may throw 'StopIterator'
    value <- list(i=i, m=m)
    i <<- i + m
    value
  }
  obj <- list(nextElem=nextEl)
  class(obj) <- c('abstractiter', 'iter')
  obj
}

此迭代器发出的值是列表,每个列表包含起始索引和计数。这是一个使用此自定义迭代器的简单foreach循环:

r <- 
  foreach(a=idivix(10, chunkSize=3), .combine='c') %dopar% {
    do.call('c', lapply(seq(a$i, length.out=a$m), function(i) {
      i
    }))
  }

当然,如果任务计算量足够大,您可能不需要分块,并且可以像原始示例中那样使用简单的foreach循环。

答案 1 :(得分:3)

起初我以为你遇到了内存问题,因为提交许多任务确实会占用更多内存,这最终会导致主进程陷入困境,所以我的原始答案显示了使用更少内存的几种技术。然而,现在听起来像是一个启动和关闭阶段,只有主进程忙,但工作人员在中间忙一段时间。我认为问题是这个例子中的任务实际上并不是计算密集型的,所以当你有很多任务时,你会开始真正注意到启动和关闭时间。我对实际计算进行了定时,发现每个任务只需要大约3毫秒。在过去,你不会从并行计算中获得任何好处,但是现在,根据你的机器,你可以获得一些好处,但是开销很大,所以当你有很多任务时,你真的注意到开销。

我仍然认为我的另一个答案适用于这个问题,但是因为你有足够的记忆,所以它有点矫枉过正。使用分块最重要的技术。下面是一个使用分块的示例,对原始示例进行了少量更改:

require("doParallel")
nw <- 8
registerDoParallel(nw)
x <- iris[which(iris[,5] != "setosa"), c(1,5)]
niter <- 4e+6
r <- foreach(n=idiv(niter, chunks=nw), .combine='rbind') %dopar% {
  do.call('rbind', lapply(seq_len(n), function(i) {
    ind <- sample(100, 100, replace=TRUE)
    result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
    coefficients(result1)
  }))
}

请注意,这使得分块与我的其他答案略有不同。它仅使用idiv chunks选项而不是chunkSize选项,为每个工作人员使用一个任务。这减少了主人完成的工作量,如果你有足够的内存,这是一个很好的策略。