在foreach%dopar%环境中嵌套的do.call无法找到.export传递的函数

时间:2017-03-02 16:42:34

标签: r foreach do.call doparallel

我在do.call并行化环境中嵌套多个%dopar%级别(每个级别自己使用参数中指定的函数,而不是硬编码),并且来自我的外部环境的函数可以'由最里面的功能找到。我知道.export上的foreach参数并且正在使用它,但不知何故,命名函数不会在整个链中传播。

我将我的问题减少到以下测试用例,确实出现了这个问题:

library(doParallel)
cl <- makeCluster(4)
registerDoParallel(cl)

simple.func <- function(a, b) {
  return(a+b)
}

inner.func <- function(a, b) {
  return(do.call(simple.func, list(a=a, b=b)))
}

outer.func <- function(a, b, my.func=inner.func) {
  return(do.call(my.func, list(a=a, b=b)))
}

main.func <- function(my.list=1:10, my.func=outer.func,
                      my.args=list(my.func=inner.func)) {
  results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE,
                     .export="simple.func") %dopar% {
    return(do.call(my.func, c(list(a=i, b=i+1), my.args)))
  }
  return(results)
}

我得到:

,而不是给出正确答案(带有一些数字的列表)
Error in { : task 1 failed - "object 'simple.func' not found" 

if (!exists("simple.func")) stop("Could not find parse.data in scope main.func")添加到每个函数的开头(根据需要更改范围的名称)会显示它inner.func没有看到simple.func - - 即使outer.func 看到了它。

我还测试了上述的几种变体,其中main.funcouter.func具有硬编码的下一级功能,而不是从参数中使用它。这两种变体都有效(例如,给出预期的结果),但对于现实世界的情况,我想保留将子函数作为参数的普遍性。

# Variation number one: Replace main.func() with this version
main.func <- function(my.list=1:10, my.func=outer.func,
                      my.args=list(my.func=inner.func)) {
  results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE,
                     .export=c("simple.func", "outer.func", "inner.func")) %dopar% {
    return(do.call(outer.func, list(a=i, b=i+1, my.func=inner.func)))
  }
  return(results)
}

# Variation number two: Replace outer.func() and main.func() with these versions
outer.func <- function(a, b, my.func=inner.func) {
  return(do.call(inner.func, list(a=a, b=b)))
}

main.func <- function(my.list=1:10, my.func=outer.func,
                      my.args=list(my.func=inner.func)) {
  results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE,
                     .export=c("simple.func", "inner.func")) %dopar% {
    return(do.call(my.func, c(list(a=i, b=i+1), my.args)))
  }
  return(results)
}

我还可以手动将simple.func传递给链,方法是将其作为一个额外的参数包含在内,但这看起来更加混乱,为什么在simple.func作为一部分传递时应该是必要的环境?

# Variation number three: Replace inner.func(), outer.func(), and main.func()
# with these versions
inner.func <- function(a, b, innermost.func=simple.func) {
  return(do.call(innermost.func, list(a=a, b=b)))
}

outer.func <- function(a, b, my.func=inner.func,
                       innermost.args=list(innermost.func=simple.func)) {
  return(do.call(my.func, c(list(a=a, b=b), innermost.args)))
}

main.func <- function(my.list=1:10, my.func=outer.func,
                      my.args=list(my.func=inner.func,
                      innermost.args=list(innermost.func=simple.func))) {
  results <- foreach(i=my.list, .multicombine=TRUE, .inorder=FALSE,
                     .export="simple.func") %dopar% {
    return(do.call(my.func, c(list(a=i, b=i+1), my.args)))
  }
  return(results)
}

有没有人对低成本解决方案或这个问题的根本原因有想法?

1 个答案:

答案 0 :(得分:0)

对于doParallel以及任何其他doNnn适配器并不分叉当前进程,我认为以下 hack 会执行此操作:

main.func <- function(my.list = 1:10, my.func=outer.func,
                      my.args = list(my.func=inner.func)) {
  results <- foreach(i = my.list, .multicombine = TRUE, .inorder = FALSE,
                     .export="simple.func") %dopar% {
    environment(my.args$my.func) <- environment()  ## <= HACK
    return(do.call(my.func, args = c(list(a=i, b=i+1), my.args)))
  }
  return(results)
}

或者,您可以使用doFuture适配器(我是作者)。然后,您不必担心全局对象,因为它们会被自动识别和导出。也就是说,无需指定.export(或.packages)。例如,在您的情况下,以下工作:

library("doFuture")
registerDoFuture()
plan(multisession, workers = 4)

main.func <- function(my.list = 1:10, my.func = outer.func,
                      my.args = list(my.func = inner.func)) {
  foreach(i = my.list, .multicombine = TRUE, .inorder = FALSE) %dopar% {
    do.call(my.func, args = c(list(a = i, b = i+1), my.args))
  }
}

res <- main.func(1:3)
str(res)
## List of 10
##  $ : num 3
##  $ : num 5
##  $ : num 7

您也可以一直跳过foreach()并执行:

library("future")
plan(multisession, workers = 4)

main <- function(my.list = 1:10, my.func = outer.func,
                 my.args = list(my.func = inner.func)) {
  future_lapply(my.list, FUN = function(i) {
    do.call(my.func, args = c(list(a = i, b = i+1), my.args))
  })
}

PS。有许多不同的plan()后端可供选择。唯一没有涉及的是您使用doRedis