R: foreach循环如何找到应该被调用的函数?

21

使用 foreach 循环(使用 %dopar%)调用自定义函数时,我遇到了问题。在 Linux 上工作时没有问题,但在 Windows 上使用时无法找到自定义函数。很难用语言解释这个问题,所以我编写了一个小例子来展示它。假设我有三个简单函数的集合,其中 FUN2(使用 %do%)和 FUN3(使用 %dopar%)调用第一个函数(FUN):

FUN <- function(x,y,z) { x + y + z }
FUN2 <- function(a, b) {
  foreach(i=1:3) %do% FUN(i, a, b)
}
FUN3 <- function(a, b) {
  foreach(i=1:3) %dopar% FUN(i, a, b)
}

这些函数存储在一个名为foreach_testfunctions.R的脚本中。在另一个脚本(foreach.test)中,我使用library(doParallel)调用这些函数并尝试使用它们。首先在Linux中进行,一切正常:

source("foreach_testfunctions.R")
a <- 2
b <- 3
library(doParallel)
registerDoParallel()

foreach(i=1:3) %do% FUN(i, a, b)    ## works fine
FUN2(a, b)                          ## works fine
foreach(i=1:3) %dopar% FUN(i, a, b) ## works fine
FUN3(a, b)                          ## works fine 

然后我在Windows上执行:

source("foreach_testfunctions.R")
a <- 2
b <- 3
library(doParallel)
cl <- makeCluster(3)
registerDoParallel(cl)

foreach(i=1:3) %do% FUN(i, a, b)    ## works fine
FUN2(a, b)                          ## works fine
foreach(i=1:3) %dopar% FUN(i, a, b) ## works fine
FUN3(a, b)                          ## does not work
Error in FUN(i, a, b) : task 1 failed - "Could not find function "FUN""

结论: (1) 使用 %do% 没有问题。(2) 在 Windows 上使用 %dopar% 存在问题。我尝试在调用 FUN3 的行之前插入一行 clusterExport(cl, varlist=c("FUN", "a", "b"), env=environment()),以确保函数 FUN 和变量 a 和 b 在正确的环境中找到,但错误仍然存在。
我的问题是:为什么 Windows 的行为与 Linux 不同,尽管代码相同(除了不同的registerDoParallel 语法)? 如何确保Windows在通过函数FUN3调用函数FUN时能够找到它?
2个回答

28

它们的行为不同是因为registerDoParallel在Linux上注册了mclapply后端,而在Windows上注册了clusterApplyLB后端。使用mclapply后端时,基本上没有数据导出问题,因此它可以在Linux上正常运行。但是,如果foreach未自动导出所需的函数和数据,则使用clusterApplyLB可能会遇到问题。

您可以通过修改FUN3以通过.export选项导出FUN来解决此问题:

FUN3 <- function(a, b) {
  foreach(i=1:3, .export='FUN') %dopar% FUN(i, a, b)
}

这个解决方案适用于Linux和Windows,因为.exportmclapply后端忽略。

正如Hong Ooi所指出的那样,您在使用clusterExport时存在错误,但我不会使用clusterExport来解决问题,因为它是后端特定的。


1
+1 不错的回答。如果FUN之前没有被引用过(即在工作环境中不存在),那怎么办?是否可以通过导出相应的R文件(例如,.export="path/to/FUN.R")在foreach内部使其可用?换句话说,.export是否也适用于文件而不仅仅是R对象? - Antoine
@Steve Weston,原因是在我的情况下,每个工作进程都需要写入一个 .txt 文件,调用一个以该文件为输入的可执行文件,并读取 .exe 的输出(另一个 .txt 文件)。我想知道是否可以使用 .export.exe 文件复制到每个工作进程的临时工作目录中。 - Antoine
@Antoine 不可以,你只能使用 foreach 的 .export 参数指定一个或多个变量的名称。此外,我通常会使用特定于后端的方法来初始化工作进程,例如在使用 doParallel 时使用 clusterExportclusterEvalQ - Steve Weston

1
在您的clusterExport调用中,删除env=environment()部分。这样做是告诉clusterExport在全新的环境中查找您的对象,因此自然无法找到它们。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接