【问题标题】:doParallel "foreach" inconsistently inherits objects from parent environment: "Error in { : task 1 failed - "could not find function..."doParallel “foreach” 不一致地从父环境继承对象:“{ 中的错误:任务 1 失败 - “找不到函数...”
【发布时间】:2016-04-11 18:00:00
【问题描述】:

我对 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 &lt;- 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 的行为都应该类似于 for。

关于导致此问题的原因以及是否有解决方法的任何想法?


该功能所运行的计算机的版本信息:

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  

【问题讨论】:

  • 您的示例代码中的f() 函数在哪里?根据您提供的内容,Windows 机器似乎给出了正确的错误,因为 f 不是一个函数,而是一个数字。
  • 来自 doParallel vignette:“要使用类似多核的功能,我们将指定要使用的核心数量(但请注意,在 Windows 上,尝试使用多个并行的核心会导致错误)" 即:windows 没有实现 doParallel 使用的 fork 之类的东西,解决方法是启动一个全新的 R 会话以将作业放入,IIRC 它复制父环境,这里是 g 函数 env 而不是全局一。
  • @brittenb 抱歉,我做了不完整的更改;我的意思是做 f = function(){return(10)}。编辑原件。
  • @Tensibai 有趣。这是针对类似“多核”的功能,但该软件包会自动在 Windows 上使用类似“雪”的功能。不过,这可能会得到它....
  • 不,很遗憾没有。您可以通过注册 registerDoParallel(cl=2) 在 Linux 上使用类似“雪”的功能,但这仍然适用于 Linux,但在 Windows 上仍然失败。

标签: r foreach parallel-processing doparallel


【解决方案1】:

@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 所说,这是因为您不是在运行 fork 进程,而是启动多个 R 实例并同时运行命令。

【讨论】:

  • 您应该使用stopCluster(clusters) 而不是stopImplicitCluster(),因为您明确地调用了makeCluster。此外,如果您有超过 100 个任务,使用 .combine=list 会在列表中为您提供列表。
  • @SteveWeston 从字面上看,在你告诉我这件事 6 天后,我正在运行带有 doParallel 的代码,并在最后尝试使用 plyr::rbind.fill(data_list),但它失败了。我不知道为什么大约一个小时,然后我终于意识到这是因为这个评论。再次感谢您的提醒。
  • 您还可以在使用clusterExport(cl, c("list","of","variables")) 创建集群后导出变量。如果它在函数中,请记住设置envir = environment(),因为它clusterExport 默认为globalenv
【解决方案2】:

不幸的是,当您注册doParallel 时使用:

registerDoParallel(2)

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

cl <- makePSOCKcluster(2)
registerDoParallel(cl)

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

请注意,doParallel 使用 mclapply 时,.packages.export 选项将被忽略,但我建议始终使用它们以实现可移植性。


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

我倾向于同意您的评论,即您的两种变通方法对于积极开发的软件包来说不是很稳定,但是如果 fg 在软件包 foo 中定义,那么您应该使用 foreach .package 选项将包 foo 加载到工作人员上:

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

然后f 将在g 的范围内,即使它既不是由 foreach 隐式导出也不是显式导出。但是,这确实要求ffoo 的导出函数(而不是内部函数),因为worker 执行的代码没有在foo 中定义,因此它只能访问导出的函数。 (很抱歉以两种不同的方式使用“出口”一词,但很难避免。)

我一直很想听听像你们这样的 cmets,因为我一直想知道是否应该调整自动导出规则。在这种情况下,我认为如果 foreach 循环由包中定义的函数执行,集群工作人员应该自动加载该包,而不需要 .packages 选项。我会尝试对此进行调查,并可能将其添加到 doParalleldoSNOW 的下一个版本中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-05
    • 1970-01-01
    • 2021-09-09
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 1970-01-01
    • 2019-07-08
    相关资源
    最近更新 更多