【问题标题】:doParallel and foreach fails to parallelise merge operationdoParallel 和 foreach 无法并行化合并操作
【发布时间】:2014-11-21 14:46:09
【问题描述】:

我正在尝试将一个大的 data.frame 与一个小的 data.frame 合并,并并行化计算。下面的代码完美运行,最大限度地利用了我机器的所有内核:

len <- 2000000
set.seed(666)
dat = paste(sample(letters, len, rep = T), sample(0:9, len, rep = T), sample(letters, len, rep = T), sep = '') # create a vector of strings that are 3-long
head(dat)
set.seed(777)
num <- sample(0:9, len, replace = T)
bigDF <-  data.frame(dat = dat, num = num)
smallDF <- data.frame(num = 0:9, caps = toupper(letters[1:10]))
startP <- 1
chunk <- 10000
nodes <- detectCores()
cl <- makeCluster(nodes)
registerDoParallel(cl)
mergedList <- foreach(i = 0:(len/chunk - 1)) %dopar% {
    tmpDF = bigDF[(startP + i * chunk):(startP - 1 + (i + 1) * chunk), ]
    merge(tmpDF, smallDF, by = 'num', all.x = T)
}
stopCluster(cl)

一旦我将向量 dat 更改为包含 5 长的字符串,并行性就会崩溃,尽管没有错误或警告,但只有 1 个内核参与计算:

len <- 2000000
set.seed(666)
dat = paste(sample(letters, len, rep = T), sample(0:9, len, rep = T), sample(letters, len, rep = T), sample(letters, len, rep = T), sample(letters, len, rep = T), sample(letters, len, rep = T), sep = '') # create a vector of strings that are 6-long
head(dat)
set.seed(777)
num <- sample(0:9, len, replace = T)
bigDF <-  data.frame(dat = dat, num = num)
smallDF <- data.frame(num = 0:9, caps = toupper(letters[1:10]))
startP <- 1
chunk <- 10000
nodes <- detectCores()
cl <- makeCluster(nodes)
registerDoParallel(cl)
mergedList <- foreach(i = 0:(len/chunk - 1)) %dopar% {
    tmpDF = bigDF[(startP + i * chunk):(startP - 1 + (i + 1) * chunk), ]
    merge(tmpDF, smallDF, by = 'num', all.x = T)
}
stopCluster(cl)

为什么会出现这种不一致,如何解决?在特定示例中,如果将dat 索引为整数,则代码有效。但索引并不是所有情况下的答案。为什么字符串的长度对所使用的核心数量很重要?

【问题讨论】:

  • 是否为非工作案例生成了子 R 进程?你的空闲内存怎么样?
  • 看来我可以在 Win7-64bit, R3.1.1 上重现这个问题。大量可用内存; Rscript 孩子永远不会启动。稍后会有更多新闻:-)
  • @blindJesse 我有千兆字节的空闲内存,所以不是问题。 @Carl:我的系统规格与您的相同。请注意,奇怪的是,如果dat 由 4 长字符串组成,则第二个核心对计算的部分贡献。对于 5 长或更长的琴弦,只有一个核心在工作。

标签: r foreach parallel-processing doparallel


【解决方案1】:

我相信不同之处在于,在第一种情况下,“bigDF”的第一列是一个有 6,760 个级别的因子,而在第二种情况下,它有 1,983,234 个级别。拥有大量级别会导致许多性能问题。当我使用stringsAsFactors=FALSE 创建“bigDF”时,性能要好得多。

bigDF <- data.frame(dat=dat, num=num, stringsAsFactors=FALSE)

我还使用了 itertools 包中的“isplitRows”函数来避免将所有“bigDF”发送给每个工人:

library(itertools)
mergedList <- foreach(splitDF=isplitRows(bigDF, chunkSize=chunk)) %dopar% {
    merge(splitDF, smallDF, by = 'num', all.x = T)
}

在我运行 R 3.1.1 的 6 核 Linux 机器上,您的第二个示例运行了大约 332 秒。当我使用stringsAsFactors=FALSE 时,它运行了大约 50 秒。当我还使用 isplitRows 时,时间下降到 5.5 秒,比您的第二个示例快了大约 60 倍。

【讨论】:

  • 这可能是其中的一部分,因为在我的 i7 机器上运行这两种情况都不是并行(使用%do% 而不是%dopar%)几乎没有时间完成。也许一直花费的时间是将这些因素级别分配给从属内核。猜猜我们应该做一个配置文件,然后再试一次,但将列转换为“字符”。
  • 谢谢史蒂夫。 iSplitRows 绝对值得一看。但是,我特别感兴趣的是最大化所有内核的计算输出,而不是仅仅减少系统时间。 @CarlWitthoft:同样适用于 %do%,我已经完成并且速度更快。字符确实比因子计算更快,但仍然只部署了 1 个核心。
  • 确认 - iSplitRows() 是一个简洁的函数,但对计算中涉及的核心数量没有影响。
  • 奇怪:当我尝试原始设置但将 bigDF$datsmallDF$caps 转换为 character 类时,我最多有两个内核处于活动状态。当我有机会时,我会在这些上尝试mclapply
  • @SteveWenston stringsAsCharacters = F 似乎对我有用,所有核心都参与其中!?我认为仅受资源上字符串成本(而不是因素)的限制。
【解决方案2】:

还没有答案,但是: 如果我运行您的代码但使用%do% 以免并行化,则除了dat 名称之外,这两种情况我会得到相同(成功)的结果。如果我使用 %dopar% 运行短名称和使用 %do% 运行长名称,则相同。

这开始看起来像是其中一个支持包中的一个微妙错误,因此您可能希望在这个包上 ping 开发人员。

9 月 29 日更新:我运行了我认为相同的设置,但使用了 ClusterMap:

dffunc <-function(i=i,bigDF=bigDF,smallDF=smallDF,startP=startP,chunk=chunk) {
tmpDF <- bigDF[(startP + i * chunk):(startP - 1 + (i + 1) * chunk), ]
    merge(tmpDF, smallDF, by = 'num', all.x = T)
    }


clusmerge<- clusterMap(cl,  function(i) {dffunc(i=i)}, 0:(len/chunk-1),MoreArgs=list(bigDF=bigDF,smallDF=smallDF,startP=startP,chunk=chunk) )

在这种情况下,无论dat 名称字符串的长度如何,我都会启动并运行所有节点。我又开始怀疑%dopar%foreach 包中的其他地方存在一些错误。

作为旁注,我可以建议不要这样做

nodes <- detectCores()
cl <- makeCluster(nodes)

因为这可能会挂起您的整个机器。更好cl &lt;- makeCluster(nodes-1) :-)

【讨论】:

  • +1 表示明智的makeCluster(nodes-1) :-)。 clusterMap() 给了我一个Error in checkForRemoteErrors(val)。只需使用bigDF &lt;- data.frame(dat = dat, num = num, stringsAsFactors = F) 运行代码,所有内核似乎都按照@SteveWeston 的建议参与,请参阅下面的评论。
  • 我从未听说过 makeCluster(detectNodes()) 挂起 Linux 或 Mac。由于主服务器不执行任何计算,因此每个内核启动一个工作程序会很有意义,这就是“mclapply”在 multicore 包中默认所做的。你是说它可以挂起对“makeCluster”的调用或随后的并行操作?除了 Windows 之外,您是否看到过挂起?
  • @SteveWeston 我夸大了 slightly :由于 Rscript 集群占用了 99.99% 的可用 CPU,几乎所有其他东西都“暂停”等待有机会获得几个周期。是的,集群完成后机器恢复正常,但同时大多数进程必须等待,等待,等待......(cue Rick's Cafe)
  • 我明白你的意思。在集群或专用工作站上使用 99.99% 的内核被认为是一件好事,但在您的个人笔记本电脑上却相当烦人。
猜你喜欢
  • 1970-01-01
  • 2016-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-01
  • 2014-06-14
  • 2021-03-18
  • 2018-12-16
相关资源
最近更新 更多