在 foreach %dopar% 环境中嵌套 do.call 找不到通过 .export 传递的函数
Nested do.call within a foreach %dopar% environment can't find function passed with .export
我在 %dopar%
并行环境中嵌套了多个级别的 do.call
(每个级别本身都使用参数中命名的函数,而不是硬编码),而来自外部环境的函数不能' t 被最里面的函数找到。我知道 foreach
上的 .export
参数并正在使用它,但不知何故命名函数并没有在整个链中传播。
我将我的问题简化为以下测试用例,它确实出现了这个问题:
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.func
或 outer.func
对下一级函数进行了硬编码,而不是从参数中使用它。这两种变体 do 都有效(例如,给出预期结果),但对于实际情况,我想保留将子函数作为参数的普遍性。
# 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)
}
有没有人有不那么笨拙的解决方案的想法,或者这个问题的根本原因?
对于 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
.
我在 %dopar%
并行环境中嵌套了多个级别的 do.call
(每个级别本身都使用参数中命名的函数,而不是硬编码),而来自外部环境的函数不能' t 被最里面的函数找到。我知道 foreach
上的 .export
参数并正在使用它,但不知何故命名函数并没有在整个链中传播。
我将我的问题简化为以下测试用例,它确实出现了这个问题:
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.func
或 outer.func
对下一级函数进行了硬编码,而不是从参数中使用它。这两种变体 do 都有效(例如,给出预期结果),但对于实际情况,我想保留将子函数作为参数的普遍性。
# 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)
}
有没有人有不那么笨拙的解决方案的想法,或者这个问题的根本原因?
对于 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
.