我不是foreach 的专业人士,但有几件事很突出:
-
func2 引用了 int1 和 int2 但它只给出了后者;这可能是您简化示例的产物,也许不是?
-
你的代码需要用花括号括起来,即你需要从
out <- foreach(i=1:length(int1list),.combine=rbind) %:%
out1 <- func1(i)
if(out1[[2]]==FALSE) ...
到
out <- foreach(i=1:length(int1list),.combine=rbind) %:% {
out1 <- func1(i)
if(out1[[2]]==FALSE) ...
}
-
foreach 的文档建议二元运算符 %:% 是在两个 foreach 调用之间使用的嵌套运算符,但您没有这样做。我想我可以让它与%do%(或%dopar%)一起正常工作
- 我不认为
prints 在并行foreach 循环中工作得很好......它可能在主节点上工作,但在其他所有节点上都没有,参考:How can I print when using %dopar%
- 可能再次由于简化示例,您定义但实际上并未使用
int1list 的内容(只是它的长度),我将在此示例中进行补救
-
next 在“正常” R 循环中工作,而不是在这些专门的 foreach 循环中工作;不过,这不是问题,因为您的 if/else 结构提供了相同的效果
这是您的示例,稍作修改以考虑上述所有内容。我加UsedJ表示
library(doParallel)
library(foreach)
func1 <- function(int1){
results <- list(int1,int1>2)
return(results)
}
func2 <- function(int1,int2){
return(int1/int2)
}
int1list <- seq(1,3)
int2list <- seq(1,5)
out <- foreach(i=1:length(int1list),.combine=rbind) %do% {
out1 <- func1(int1list[i])
if(!out1[[2]]){
data.frame("Scenario"=i, "Result"=out1[[1]], UsedJ=FALSE)
# next
} else{
foreach(j=1:length(int2list),.combine=rbind) %dopar% {
int3 <- func2(out1[[1]], int2list[j])
data.frame("Scenario"=i,"Result"=int3, UsedJ=TRUE)
}
}
}
out
# Scenario Result UsedJ
# 1 1 1.00 FALSE
# 2 2 2.00 FALSE
# 3 3 3.00 TRUE
# 4 3 1.50 TRUE
# 5 3 1.00 TRUE
# 6 3 0.75 TRUE
# 7 3 0.60 TRUE
编辑
如果您没有看到并行化,可能是因为您还没有设置“集群”。基于foreach 使用%:% 运算符嵌套循环的方法,还对工作流程进行了一些其他更改以使其能够很好地并行化。
为了“证明”这是并行工作的,我添加了一些基于How can I print when using %dopar% 的日志记录(因为并行进程并不像人们希望的那样print)。
library(doParallel)
library(foreach)
Log <- function(text, ..., .port = 4000, .sock = make.socket(port=.port)) {
msg <- sprintf(paste0(as.character(Sys.time()), ": ", text, "\n"), ...)
write.socket(.sock, msg)
close.socket(.sock)
}
func1 <- function(int1) {
Log(paste("func1", int1))
Sys.sleep(5)
results <- list(int1, int1 > 2)
return(results)
}
func2 <- function(int1, int2) {
Log(paste("func2", int1, int2))
Sys.sleep(1)
return(int1 / int2)
}
日志代码的使用需要从该套接字读取的外部方式。我在这里使用带有ncat -k -l 4000 的netcat(nc 或Nmap 的ncat)。这项工作当然不需要工作,但在这里可以方便地查看事情的进展情况。 (注意:此侦听器/服务器需要在您尝试使用Log 之前运行。)
我无法让嵌套的“foreach -> func1 -> foreach -> func2”正确并行化 func2。根据睡眠,对func1 的三个调用应该需要 5 秒,对func2 的五个调用需要 2 秒(每批三个),但需要 10 秒(对@987654358 的三个并行调用@,然后连续调用五次func2):
system.time(
out <- foreach(i=1:length(int1list), .combine=rbind, .packages="foreach") %dopar% {
out1 <- func1(int1list[i])
if (!out1[[2]]) {
data.frame(Scenario=i, Result=out1[[1]], UsedJ=FALSE)
} else {
foreach(j=1:length(int2list), .combine=rbind) %dopar% {
int3 <- func2(out1[[1]], int2list[j])
data.frame(Scenario=i, Result=int3, UsedJ=TRUE)
}
}
}
)
# user system elapsed
# 0.02 0.00 10.09
使用相应的控制台输出:
2018-11-12 11:51:17: func1 2
2018-11-12 11:51:17: func1 1
2018-11-12 11:51:17: func1 3
2018-11-12 11:51:23: func2 3 1
2018-11-12 11:51:24: func2 3 2
2018-11-12 11:51:25: func2 3 3
2018-11-12 11:51:26: func2 3 4
2018-11-12 11:51:27: func2 3 5
(注意顺序不保证。)
所以我们可以先把它分解成计算 func1 的东西:
system.time(
out1 <- foreach(i = seq_along(int1list)) %dopar% {
func1(int1list[i])
}
)
# user system elapsed
# 0.02 0.01 5.03
str(out1)
# List of 3
# $ :List of 2
# ..$ : int 1
# ..$ : logi FALSE
# $ :List of 2
# ..$ : int 2
# ..$ : logi FALSE
# $ :List of 2
# ..$ : int 3
# ..$ : logi TRUE
控制台:
2018-11-12 11:53:21: func1 2
2018-11-12 11:53:21: func1 1
2018-11-12 11:53:21: func1 3
然后处理func2 的东西:
system.time(
out2 <- foreach(i = seq_along(int1list), .combine="rbind") %:%
foreach(j = seq_along(int2list), .combine="rbind") %dopar% {
Log(paste("preparing", i, j))
if (out1[[i]][[2]]) {
int3 <- func2(out1[[i]][[1]], j)
data.frame(i=i, j=j, Result=int3, UsedJ=FALSE)
} else if (j == 1L) {
data.frame(i=i, j=NA_integer_, Result=out1[[i]][[1]], UsedJ=FALSE)
}
}
)
# user system elapsed
# 0.03 0.00 2.05
out2
# i j Result UsedJ
# 1 1 NA 1.00 FALSE
# 2 2 NA 2.00 FALSE
# 3 3 1 3.00 FALSE
# 4 3 2 1.50 FALSE
# 5 3 3 1.00 FALSE
# 6 3 4 0.75 FALSE
# 7 3 5 0.60 FALSE
两秒(第一批三个是1秒,第二批两个是1秒)是我所期望的。控制台:
2018-11-12 11:54:01: preparing 1 2
2018-11-12 11:54:01: preparing 1 3
2018-11-12 11:54:01: preparing 1 1
2018-11-12 11:54:01: preparing 1 4
2018-11-12 11:54:01: preparing 1 5
2018-11-12 11:54:01: preparing 2 1
2018-11-12 11:54:01: preparing 2 2
2018-11-12 11:54:01: preparing 2 3
2018-11-12 11:54:01: preparing 2 4
2018-11-12 11:54:01: preparing 2 5
2018-11-12 11:54:01: preparing 3 1
2018-11-12 11:54:01: preparing 3 2
2018-11-12 11:54:01: func2 3 1
2018-11-12 11:54:01: preparing 3 3
2018-11-12 11:54:01: func2 3 2
2018-11-12 11:54:01: func2 3 3
2018-11-12 11:54:02: preparing 3 4
2018-11-12 11:54:02: preparing 3 5
2018-11-12 11:54:02: func2 3 4
2018-11-12 11:54:02: func2 3 5
您可以看到func2 被正确调用了五次。不幸的是,您看到循环内部有很多“旋转”。当然,它实际上是一个无操作(如 2.05 秒运行时所证明的那样),因此节点上的负载可以忽略不计。
如果有人有办法避免这种不必要的旋转,我欢迎 cmets 或“竞争”的答案。