在R中并行化矢量化函数的最简单方法是什么?

时间:2011-04-06 19:19:25

标签: r parallel-processing vectorization

我有一个非常大的列表X和一个矢量化函数f。我想计算f(X),但如果我使用单核实现这一点,则需要很长时间。我有(访问)48核服务器。并行计算f(X)的最简单方法是什么?以下是正确答案:

library(foreach)
library(doMC)
registerDoMC()

foreach(x=X, .combine=c) %dopar% f(x)

上述代码确实会对f(X)的计算进行并行化,但它会将f分别应用于X的每个元素。这忽略了f的矢量化特性,并且可能会使事情更慢,而不是更快。我不是将f元素应用于X,而是将X拆分为合理大小的块,并将f应用于这些块。

那么,我应该手动将X分成48个大小相等的子列表,然后并行地应用f,然后手动将结果放在一起?或者是否有为此设计的包装?

如果有人想知道,我的具体用例是here

5 个答案:

答案 0 :(得分:5)

虽然这是一个较老的问题,但对于每个通过谷歌(像我一样)偶然发现这一点的人来说,这可能很有趣:请查看pvec包中的multicore函数。我认为它完全符合您的要求。

答案 1 :(得分:4)

这是我的实施。它是一个函数chunkmap,需要一个 向量化函数,应该向量化的参数列表, 以及不应该矢量化的参数列表(即 常量),并返回与调用函数相同的结果 直接参数,除了结果是并行计算的。 对于函数f,向量参数v1v2v3和标量 参数s1s2,以下内容应返回相同的结果:

f(a=v1, b=v2, c=v3, d=s1, e=s2)
f(c=v3, b=v2, e=s2, a=v1, d=s1)
chunkapply(FUN=f, VECTOR.ARGS=list(a=v1, b=v2, c=v3), SCALAR.ARGS=list(d=s1, e=s2))
chunkapply(FUN=f, SCALAR.ARGS=list(e=s2, d=s1), VECTOR.ARGS=list(a=v1, c=v3, b=v2))

因为chunkapply函数不可能知道哪个 f的参数是矢量化的,而不是,它取决于你 指定何时调用它,否则您将得到错误的结果。您 通常应该命名您的参数以确保它们受到约束 正确。

library(foreach)
library(iterators)
# Use your favorite doPar backend here
library(doMC)
registerDoMC()

get.chunk.size <- function(vec.length,
                           min.chunk.size=NULL, max.chunk.size=NULL,
                           max.chunks=NULL) {
  if (is.null(max.chunks)) {
    max.chunks <- getDoParWorkers()
  }
  size <- vec.length / max.chunks
  if (!is.null(max.chunk.size)) {
    size <- min(size, max.chunk.size)
  }
  if (!is.null(min.chunk.size)) {
    size <- max(size, min.chunk.size)
  }
  num.chunks <- ceiling(vec.length / size)
  actual.size <- ceiling(vec.length / num.chunks)
  return(actual.size)
}

ichunk.vectors <- function(vectors=NULL,
                           min.chunk.size=NULL,
                           max.chunk.size=NULL,
                           max.chunks=NULL) {
  ## Calculate number of chunks
  recycle.length <- max(sapply(vectors, length))
  actual.chunk.size <- get.chunk.size(recycle.length, min.chunk.size, max.chunk.size, max.chunks)
  num.chunks <- ceiling(recycle.length / actual.chunk.size)

  ## Make the chunk iterator
  i <- 1
  it <- idiv(recycle.length, chunks=num.chunks)
  nextEl <- function() {
    n <- nextElem(it)
    ix <- seq(i, length = n)
    i <<- i + n
    vchunks <- foreach(v=vectors) %do% v[1+ (ix-1) %% length(v)]
    names(vchunks) <- names(vectors)
    vchunks
  }
  obj <- list(nextElem = nextEl)
  class(obj) <- c("ichunk", "abstractiter", "iter")
  obj
}

chunkapply <- function(FUN, VECTOR.ARGS, SCALAR.ARGS=list(), MERGE=TRUE, ...) {
  ## Check that the arguments make sense
  stopifnot(is.list(VECTOR.ARGS))
  stopifnot(length(VECTOR.ARGS) >= 1)
  stopifnot(is.list(SCALAR.ARGS))
  ## Choose appropriate combine function
  if (MERGE) {
    combine.fun <- append
  } else {
    combine.fun <- foreach:::defcombine
  }
  ## Chunk and apply, and maybe merge
  foreach(vchunk=ichunk.vectors(vectors=VECTOR.ARGS, ...),
          .combine=combine.fun,
          .options.multicore = mcoptions) %dopar%
  {
    do.call(FUN, args=append(vchunk, SCALAR.ARGS))
  }
}

## Only do chunkapply if it will run in parallel
maybe.chunkapply <- function(FUN, VECTOR.ARGS, SCALAR.ARGS=list(), ...) {
  if (getDoParWorkers() > 1) {
    chunkapply(FUN, VECTOR.ARGS, SCALAR.ARGS, ...)
  } else {
    do.call(FUN, append(VECTOR.ARGS, SCALAR.ARGS))
  }
}

以下是一些示例,表明chunkapply(f,list(x))会产生与f(x)相同的结果。我已经将max.chunk.size设置得非常小,以确保实际使用了分块算法。

> # Generate all even integers from 2 to 100 inclusive
> identical(chunkapply(function(x,y) x*y, list(1:50), list(2), max.chunk.size=10), 1:50 * 2)
[1] TRUE

> ## Sample from a standard normal distribution, then discard values greater than 1
> a <- rnorm(n=100)
> cutoff <- 1
> identical(chunkapply(function(x,limit) x[x<=limit], list(x=a), list(limit=cutoff), max.chunk.size=10), a[a<cutoff])
[1] TRUE

如果有人比“chunkapply”有更好的名字,请提出建议。

编辑:

正如另一个答案所指出的,在多核pacakge中有一个名为pvec的函数,它具有与我所写的功能非常相似的功能。对于简单的情况,你应该这样做,你应该投票给Jonas Rauch的答案。但是,我的功能更为通用,因此如果以下任何一项适用于您,您可能需要考虑使用我的功能:

  • 您需要使用除多核之外的并行后端(例如MPI)。我的函数使用foreach,因此您可以使用任何为foreach提供后端的并行化框架。
  • 您需要传递多个矢量化参数。 pvec仅对单个参数进行矢量化,因此您无法轻松地使用pvec实现并行矢量化加法。我的函数允许您指定任意参数。

答案 2 :(得分:2)

itertools包旨在解决此类问题。在这种情况下,我会使用isplitVector

n <- getDoParWorkers()
foreach(x=isplitVector(X, chunks=n), .combine='c') %dopar% f(x)

对于这个例子,pvec无疑是更快更简单的,但是这可以在Windows上使用doParallel包,例如。

答案 3 :(得分:0)

Map-Reduce可能是您正在寻找的;它一直是ported to R

答案 4 :(得分:0)

这样的事情怎么样? R将利用所有可用内存,multicore将并行化所有可用内核。

library(multicore)
result = mclapply(X, function,mc.preschedule=FALSE, mc.set.seed=FALSE)