如何避免使用foreach复制对象

bai*_*dao 7 parallel-processing r mpi

我有一个非常庞大的字符串向量,并希望使用foreachdosnow包进行并行计算.我注意到foreach会为每个进程复制矢量,从而快速耗尽系统内存.我试图将矢量分解为列表对象中的较小部分,但仍然没有看到任何内存使用量减少.有没有人有这个想法?以下是一些演示代码:

library(foreach)
library(doSNOW)
library(snow)

x<-rep('some string', 200000000)
# split x into smaller pieces in a list object
splits<-getsplits(x, mode='bysize', size=1000000) 
tt<-vector('list', length(splits$start))  
for (i in 1:length(tt)) tt[[i]]<-x[splits$start[i]: splits$end[i]]

ret<-foreach(i = 1:length(splits$start), .export=c('somefun'), .combine=c)   %dopar% somefun(tt[[i]])
Run Code Online (Sandbox Code Playgroud)

Ste*_*ton 5

您使用的迭代风格通常适用于doMC后端,因为工作人员可以tt通过fork. 但是 with doSNOW,tt将自动导出给工作人员,使用大量内存,即使他们实际上只需要其中的一小部分。@Beasterfield 提出的直接迭代的建议tt解决了这个问题,但通过使用迭代器和适当的并行后端,可以提高内存效率。

在这种情况下,我使用包中的isplitVector函数itertools。它将一个向量拆分为一系列子向量,允许它们并行处理而不会失去向量化的好处。不幸的是,使用doSNOW,它会将这些子向量放入一个列表中以便调用该clusterApplyLB函数,snow因为clusterApplyLB它不支持迭代器。然而,doMPIdoRedis后端不会那么做的。他们将从迭代器直接将子向量发送给工作人员,使用几乎一半的内存。

这是一个使用的完整示例doMPI

suppressMessages(library(doMPI))
library(itertools)
cl <- startMPIcluster()
registerDoMPI(cl)
n <- 20000000
chunkSize <- 1000000
x <- rep('some string', n)
somefun <- function(s) toupper(s)
ret <- foreach(s=isplitVector(x, chunkSize=chunkSize), .combine='c') %dopar% {
  somefun(s)
}
print(length(ret))
closeCluster(cl)
mpi.quit()
Run Code Online (Sandbox Code Playgroud)

当我在具有 4 GB 内存的 MacBook Pro 上运行此程序时

$ time mpirun -n 5 R --slave -f split.R 
Run Code Online (Sandbox Code Playgroud)

大约需要 16 秒。

您必须小心在同一台机器上创建的工人数量,尽管减少 的值chunkSize可能会让您开始更多。

如果您能够使用不需要所有字符串同时在内存中的迭代器,则可以进一步减少内存使用量。例如,如果字符串位于名为“strings.txt”的文件中,则可以使用s=ireadLines('strings.txt', n=chunkSize).