作为this question (https://stackoverflow.com/questions/17222942/allow-foreach-workers-to-register-and-distribute-sub-tasks-to-other-workers)的延续,将doSNOW和SOCK集群连接到Torque/MOAB调度器的最佳实践是什么,以避免在处理外部并行循环的某些代码的内部并行循环中的处理器亲和性?
在the Steve's answer to that question中,不与调度程序交互的基线代码可以是:
library(doSNOW)
hosts <- c('host-1', 'host-2')
cl <- makeSOCKcluster(hosts)
registerDoSNOW(cl)
r <- foreach(i=1:4, .packages='doMC') %dopar% {
registerDoMC(2)
foreach(j=1:8, .combine='c') %dopar% {
i * j
}
}
stopCluster(cl) 发布于 2013-06-25 21:57:57
Torque始终创建一个包含由Moab分配给作业的节点名的文件,并通过PBS_NODEFILE环境变量将该文件的路径传递给作业。节点名称可能会多次列出,以指示它为该节点上的作业分配了多个核心。在本例中,我们希望为PBS_NODEFILE中的每个唯一节点名启动一个集群工作器,但要跟踪每个节点上分配的核心数量,以便在注册doMC时指定正确的核心数量。
下面是一个读取PBS_NODEFILE并返回包含已分配节点信息的数据帧的函数:
getnodes <- function() {
f <- Sys.getenv('PBS_NODEFILE')
x <- if (nzchar(f)) readLines(f) else rep('localhost', 3)
as.data.frame(table(x), stringsAsFactors=FALSE)
}返回的数据框包含一个名为"x“的节点名称列和一个名为"Freq”的对应核心计数列。
这使得创建和注册SOCK集群变得很简单,每个唯一节点都有一个工作节点:
nodes <- getnodes()
cl <- makeSOCKcluster(nodes$x)
registerDoSNOW(cl)我们现在可以很容易地执行一个foreach循环,每个worker有一个任务,但是不依赖于snow和doSNOW的一些实现细节,特别是与doSNOW使用的clusterApplyLB函数的实现相关的实现细节,将正确的分配核心数量传递给每个worker并非易事。当然,如果您碰巧知道每个节点上分配的核心数量是相同的,这很容易,但如果您想要一个问题的一般解决方案,则很难。
一种(不是很优雅的)通用解决方案是通过snow clusterApply函数将分配的核心数量分配给每个工作线程上的全局变量:
setcores <- function(cl, nodes) {
f <- function(cores) assign('allocated.cores', cores, pos=.GlobalEnv)
clusterApply(cl, nodes$Freq, f)
}
setcores(cl, nodes)这保证了每个工作线程上的"allocated.cores“变量的值等于该节点在PBS_NODEFILE中出现的次数。
现在,我们可以在注册doMC时使用该全局变量
r <- foreach(i=seq_along(nodes$x), .packages='doMC') %dopar% {
registerDoMC(allocated.cores)
foreach(j=1:allocated.cores, .combine='c') %dopar% {
i * j
}
}以下是可用于执行此R脚本的作业脚本示例:
#!/bin/sh
#PBS -l nodes=4:ppn=8
cd "$PBS_O_WORKDIR"
R --slave -f hybridSOCK.R当通过qsub命令提交时,R脚本将创建一个包含4个工作进程的套接字集群,每个工作进程将使用8个内核执行内部foreach循环。但是由于R代码是通用的,所以无论通过qsub请求的资源是什么,它都应该做正确的事情。
https://stackoverflow.com/questions/17288379
复制相似问题