doParallel“foreach”不一致地从父环境继承对象:“{:task 1 failed - ”中的错误 - 找不到函数...“

时间:2016-01-07 11:05:53

标签: r foreach parallel-processing doparallel

我对foreach有一个问题,我无法弄明白。以下代码在我尝试的两台Windows计算机上失败,但在三台Linux计算机上成功运行,所有这些都运行相同版本的R和doParallel:

library("doParallel")
registerDoParallel(cl=2,cores=2)

f <- function(){return(10)}
g <- function(){
    r = foreach(x = 1:4) %dopar% {
        return(x + f())
    }
    return(r)
}
g()

在这两台Windows计算机上,返回以下错误:

Error in { : task 1 failed - "could not find function "f""

然而,这在Linux计算机上运行得很好,并且在%do%而不是%dopar%下工作得很好,并且适用于常规for循环。

变量也是如此,例如:设置i <- 10并将return(x + f())替换为return(x + i)

对于有相同问题的其他人,有两种解决方法:

1)使用.export:

显式导入所需的函数和变量
r = foreach(x=1:4, .export="f") %dopar% 

2)导入所有全局对象:

r = foreach(x=1:4, .export=ls(.GlobalEnv)) %dopar% 

这些变通办法的问题在于,对于一​​个积极开发的大型软件包来说,它们并不是最稳定的。无论如何,foreach应该表现得像。

是什么导致了这个以及是否有修复的想法?

该功能可以使用的计算机的版本信息:

R version 3.2.2 (2015-08-14)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: CentOS release 6.5 (Final)

other attached packages:
[1] doParallel_1.0.10 iterators_1.0.8   foreach_1.4.3

该功能不起作用的电脑:

R version 3.2.2 (2015-08-14)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

other attached packages:
[1] doParallel_1.0.10 iterators_1.0.8   foreach_1.4.3  

2 个答案:

答案 0 :(得分:11)

@Tensibai是对的。尝试在Windows上使用doParallel时,必须“导出”要使用的不在当前范围内的功能。根据我的经验,我完成这项工作的方式是使用以下(编辑)示例。

format_number <- function(data) {
  # do stuff that requires stringr
}

format_date_time <- function(data) {
  # do stuff that requires stringr
}

add_direction_data <- function(data) {
  # do stuff that requires dplyr
}

parse_data <- function(data) {
  voice_start <- # vector of values
  voice_end <- # vector of values
  target_phone_numbers <- # vector of values
  parse_voice_block <- function(block_start, block_end, number) {
    # do stuff
  }

  number_of_cores <- parallel::detectCores() - 1
  clusters <- parallel::makeCluster(number_of_cores)
  doParallel::registerDoParallel(clusters)
  data_list <- foreach(i = 1:length(voice_start), .combine=list,
                       .multicombine=TRUE, 
                       .export = c("format_number", "format_date_time", "add_direction_data"), 
                       .packages = c("dplyr", "stringr")) %dopar% 
                       parse_voice_block(voice_start[i], voice_end[i], target_phone_numbers[i])
  doParallel::stopCluster(clusters)
  output <- plyr::rbind.fill(data_list)
}

由于前三个函数未包含在我当前的环境中,doParallel会在启动R的新实例时忽略它们,但它会知道在哪里找到parse_voice_block,因为它位于目前的范围。另外,你需要指定在每个新的R实例中应该加载哪些包。正如Tensibai所说,这是因为你没有运行分支过程,而是同时启动R的多个实例并同时运行命令。

答案 1 :(得分:7)

当你使用:

注册doParallel时,这是非常不幸的
registerDoParallel(2)

然后doParallel在Linux和Mac OS X上使用mclapply,但clusterApplyLB在Windows上使用隐式创建的群集对象。这通常会导致代码在Linux上运行但在Windows上失败,因为由于mclapply使用fork时工作者是主服务器的克隆。出于这个原因,我通常使用以下方法测试我的代码:

cl <- makePSOCKcluster(2)
registerDoParallel(cl)

确保我正在加载所有必需的包并导出所有必需的函数和变量,然后切换回registerDoParallel(2)以在支持它的平台上获得mclapply的好处。

请注意,.packages使用.export时会忽略doParallelmclapply选项,但我建议您始终使用它们来实现可移植性。

在函数中使用foreach的自动导出功能并不是很顺利,因为foreach对自动导出的内容相当保守。自动导出当前环境中定义的变量和函数似乎是非常安全的,但由于R的范围规则的复杂性,在我之外似乎有风险。

我倾向于同意您的评论,即对于积极开发的软件包,您的两种解决方法不是很稳定,但如果在f包中定义了gfoo,然后你应该使用foreach .package选项在worker上加载包foo

g <- function(){
    r = foreach(x = 1:4, .packages='foo') %dopar% {
        return(x + f())
    }
    return(r)
}

然后f将在g的范围内,即使它不是由foreach隐式或显式导出的。但是,这确实要求ffoo的导出函数(而不是内部函数),因为工作程序执行的代码未在foo中定义,因此它只能访问导出的功能。 (抱歉以两种不同的方式使用术语“导出”,但很难避免。)

我总是有兴趣听到你这样的评论,因为我总是想知道是否应该调整自动导出规则。在这种情况下,我认为如果foreach循环由包中定义的函数执行,集群工作者应该自动加载该包而不需要.packages选项。我将尝试调查此问题,并将其添加到下一版doParalleldoSNOW