3

继续这个问题(https://stackoverflow.com/questions/17222942/allow-foreach-workers-to-register-and-distribute-sub-tasks-to-other-workers),什么是连接的最佳实践doSNOW 和 SOCK 集群到 Torque/MOAB 调度程序,以避免处理外部并行循环的某些部分代码的内部并行循环中的处理器亲和性?

史蒂夫对那个问题的回答来看,没有调度程序影响的基线代码可能是:

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)  
4

1 回答 1

3

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将正确数量的分配核心传递给每个工作人员并不容易由. 当然,如果您碰巧知道每个节点上分配的核心数量相同,这很容易,但如果您想要一个通用的解决方案,那就更难了。snowdoSNOWclusterApplyLBdoSNOW

一个(不太优雅)的通用解决方案是通过 snowclusterApply函数将分配的核心数分配给每个工作人员的全局变量:

setcores <- function(cl, nodes) {
  f <- function(cores) assign('allocated.cores', cores, pos=.GlobalEnv)
  clusterApply(cl, nodes$Freq, f)
}
setcores(cl, nodes)

这保证了每个worker上的“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 个 worker 的 SOCK 集群,每个 worker 将foreach使用 8 个内核执行内部循环。但是由于 R 代码是通用的,它应该做正确的事情,而不管通过qsub.

于 2013-06-25T13:57:56.887 回答