我经常最终得到几个嵌套的foreach
循环,有时候在编写一般函数时(例如对于一个包),没有明显的并行化级别。有没有办法完成下面的模型描述?
foreach(i = 1:I) %if(I < J) `do` else `dopar`% {
foreach(j = 1:J) %if(I >= J) `do` else `dopar`% {
# Do stuff
}
}
此外,是否有某种方法可以检测并行后端是否已注册,以避免收到不必要的警告消息?这在CRAN提交之前检查包并且不打扰在单核计算机上运行R的用户时非常有用。
foreach(i=1:I) %if(is.parallel.backend.registered()) `dopar` else `do`% {
# Do stuff
}
感谢您的时间。
编辑:非常感谢您对内核和工作人员的所有反馈,并且您正确处理上述示例的最佳方法是重新考虑整个设置。我更喜欢下面的triu
想法,但它基本上是相同的。它当然也可以用Joris建议的并行tapply
完成。
ij <- expand.grid(i=1:I, j=1:J)
foreach(i=ij$I, j=ij$J) %dopar% {
myFuction(i, j)
}
然而,在我试图简化引起这个主题的情况时,我遗漏了一些关键细节。想象一下,我有两个函数analyse
和batch.analyse
,并且最佳并行级别可能会有所不同,具体取决于n.replicates
和n.time.points
的值。
analyse <- function(x, y, n.replicates=1000){
foreach(r = 1:n.replicates) %do% {
# Do stuff with x and y
}
}
batch.analyse <- function(x, y, n.replicates=10, n.time.points=1000){
foreach(tp = 1:time.points) %do% {
my.y <- my.func(y, tp)
analyse(x, my.y, n.replicates)
}
}
如果n.time.points > n.replicates
在batch.analyse
中并行化是有意义的,但在analyse
中进行并行化更有意义。有关如何解决它的任何想法?是否有可能在analyse
中检测是否已经进行了并行化?
答案 0 :(得分:8)
您提出的问题是foreach嵌套运算符'%:%'的动机。如果内循环的主体需要大量的计算时间,那么使用它是非常安全的:
foreach(i = 1:I) %:%
foreach(j = 1:J) %dopar% {
# Do stuff
}
这会“展开”嵌套循环,从而导致(I * J)任务可以并行执行。
如果内环的主体没有花费太多时间,解决方案就更难了。标准解决方案是并行化外部循环,但这仍然可能导致许多小任务(当我很大而J很小时)或一些大任务(当我很小而J很大时)。
我最喜欢的解决方案是使用嵌套操作符和任务分块。这是使用doMPI后端的完整示例:
library(doMPI)
cl <- startMPIcluster()
registerDoMPI(cl)
I <- 100; J <- 2
opt <- list(chunkSize=10)
foreach(i = 1:I, .combine='cbind', .options.mpi=opt) %:%
foreach(j = 1:J, .combine='c') %dopar% {
(i * j)
}
closeCluster(cl)
这导致20个“任务块”,每个由循环体的10个计算组成。如果您希望每个工作程序都有一个任务块,则可以将块大小计算为:
cs <- ceiling((I * J) / getDoParWorkers())
opt <- list(chunkSize=cs)
不幸的是,并非所有并行后端都支持任务分块。此外,doMPI不支持Windows。
有关此主题的更多信息,请参阅foreach包中的“嵌套Foreach循环”小插图:
library(foreach)
vignette('nesting')
答案 1 :(得分:6)
如果你最终得到几个嵌套的foreach循环,我会重新考虑我的方法。使用tapply
的并行版本可以解决许多麻烦。通常,您不应该使用嵌套并行化,因为它不会为您带来任何东西。并行化外部循环,忘记内部循环。
原因很简单:如果群集中有3个连接,则外部多普勒循环将使用所有三个连接。内部多巴环路将无法使用任何额外的连接,因为没有可用的连接。所以你没有得到任何东西。因此,从编程的角度来看,你提供的模型根本没有意义。
函数getDoParRegistered()
很容易回答你的第二个问题,当注册后端时返回TRUE,否则返回FALSE。请注意:
例如:
require(foreach)
require(doSNOW)
cl <- makeCluster(rep("localhost",2),type="SOCK")
getDoParRegistered()
[1] FALSE
registerDoSNOW(cl)
getDoParRegistered()
[1] TRUE
stopCluster(cl)
getDoParRegistered()
[1] TRUE
但现在运行此代码:
a <- matrix(1:16, 4, 4)
b <- t(a)
foreach(b=iter(b, by='col'), .combine=cbind) %dopar%
(a %*% b)
将返回错误:
Error in summary.connection(connection) : invalid connection
你可以建立一个额外的支票。您可以用来检查doSNOW
注册的连接是否有效的(可怕的)黑客攻击,可以是:
isvalid <- function(){
if (getDoParRegistered() ){
X <- foreach:::.foreachGlobals$objs[[1]]$data
x <- try(capture.output(print(X)),silent=TRUE)
if(is(x,"try-error")) FALSE else TRUE
} else {
FALSE
}
}
您可以将其用作
if(!isvalid()) registerDoSEQ()
如果getDoParRegistered()返回TRUE但没有有效的群集连接,则会注册顺序后端。但同样,这是一个黑客,我不知道它是否适用于其他后端甚至其他类型的集群类型(我主要使用套接字)。
答案 2 :(得分:3)
按照您提出的问题的相反顺序:
@Joris在检查注册的并行后端方面是正确的。但请注意,单核机器与并行后端是否已注册存在差异。检查核心数是一个非常平台(操作系统)的特定任务。在Linux上,这可能适合您:
CountUnixCPUs <- function(cpuinfo = "/proc/cpuinfo"){
tmpCmd <- paste("grep processor ", cpuinfo, " | wc -l", sep = "")
numCPU <- as.numeric(system(tmpCmd, intern = TRUE))
return(numCPU)
}
编辑:请参阅下面的@ Joris指向另一个页面的链接,该页面提供了有关Windows和Linux的建议。我可能会重写我自己的代码,至少包括更多用于计算内核的选项。
关于嵌套循环,我采取了不同的方法:我准备一个参数表,然后迭代行。一个非常简单的方法是,例如:
library(Matrix)
ptable <- which(triu(matrix(1, ncol = 20, nrow = 20))==1, arr.ind = TRUE)
foreach(ix_row = 1:nrow(ptable)) %dopar% { myFunction(ptable[ix_row,])}