使用R中的并行函数最小化开销

时间:2016-09-02 20:37:28

标签: r parallel-processing overhead mclapply

我尝试报告我在mclapply中遇到的关于不允许大回报值的错误。

显然这个错误已在开发版本中得到修复,但我对响应者的评论更感兴趣:

  

序列化对象的大小有2GB限制,例如mclapply可以从分叉进程返回,此示例尝试16GB。在R-devel中已经取消了(对于64位版本),但是这种使用非常不寻常且效率很低(该示例需要大约150GB,因为(un)序列化中涉及所有副本)

如果使用mclapply对大数据进行并行计算是低效的,那么有什么更好的方法呢?我做这种事情的需要只是增加,而且我到处都遇到了瓶颈。我已经看到的教程是关于如何使用这些功能的非常基本的介绍,但不一定是如何有效地使用这些功能来管理权衡。这篇文档在这种权衡中有一个小小的模糊:

  

mc.preschedule:如果设置为“TRUE”,则首先进行计算             到(最多)尽可能多的工作是核心,然后是             工作开始,每项工作可能涵盖多个工作             值。如果设置为“FALSE”,则为每个作业分叉一个作业             'X'的值。前者更适合短期计算或             'X'中的大量值,后者更适合工作             完成时间差异很大而且不太多             'X'的值与'mc.cores'相比

  

默认情况下('mc.preschedule = TRUE')输入'X'被分割成        与核心一样多的部分(目前价值是分散的        顺序地跨核心,即第一个值到核心1,第二个        核心2,...(核心+ 1) - 核心1等的值)然后一个        进程分叉到每个核心并收集结果。

     

如果没有预先安排,就会为每个值分配一个单独的作业        'X'。确保运行的“mc.cores”作业不超过“mc.cores”        一旦这个数字被分叉,主进程就会等待        让孩子在下一个叉子之前完成

可靠地对这些事情进行基准测试需要花费很多时间,因为有些问题只能在规模上表现出来,然后很难弄清楚究竟发生了什么。因此,更好地了解函数的行为将会有所帮助。

编辑:

我没有具体的例子,因为我经常使用mclapply,并希望更好地了解如何考虑性能影响。虽然写入磁盘可以解决错误,但我认为它不会对必须发生的(反)串行有所帮助,这也必须通过磁盘IO。

一个工作流程如下: 获取一个大的稀疏矩阵M,并将其以块(例如M1-M100)写入磁盘,因为M本身不适合内存。

现在说,对于i中的每个用户ICi中有M个列,我想在用户级别添加和聚合。对于较小的数据,这将是相对微不足道的:

m = matrix(runif(25), ncol=5)
df = data.frame(I=sample(1:6, 20, replace=T), C=sample(1:5, 20, replace=T))
somefun = function(m) rowSums(m)
res = sapply(sort(unique(df$I)), function(i) somefun(m[,df[df$I == i,]$C]))

但是对于更大的数据,我的方法是将用户/列的data.frame拆分为不同的data.frames,基于列所在的矩阵M1-M100,对这些data.frames进行并行循环。 ,读取关联的矩阵,然后遍历用户,提取列并应用我的函数,然后获取输出列表,再次循环并重新聚合。

如果我有一个不能像那样被重新聚合的功能(目前,这不是一个问题),这是不理想的,但我显然用这种方法改变了太多的数据。

2 个答案:

答案 0 :(得分:2)

我希望我的回答还为时不晚,但我认为您的示例可以通过bigmemory包使用共享内存/文件来处理。

让我们创建数据

library(bigmemory)
library(parallel)

#your large file-backed matrix (all values initialized to 0)
#it can hold more than your RAM as it is written to a file
m=filebacked.big.matrix(nrow=5,
                        ncol=5,
                        type="double",
                        descriptorfile="file_backed_matrix.desc",
                        backingfile="file_backed_matrix",
                        backingpath="~")

#be careful how to fill the large matrix with data
set.seed(1234)
m[]=c(matrix(runif(25), ncol=5))
#print the data to the console
m[]

#your user-col mapping
#I have added a unique idx that will be used below
df = data.frame(unique_idx=1:20,
                I=sample(1:6, 20, replace=T),
                C=sample(1:5, 20, replace=T))

#the file-backed matrix that will hold the results
resm=filebacked.big.matrix(nrow=nrow(df),
                           ncol=2,
                           type="double",init = NA_real_,
                           descriptorfile="res_matrix.desc",
                           backingfile="res_backed_matrix",
                           backingpath="~")

#the first column of resm will hold the unique idx of df
resm[,1]=df$unique_idx
resm[]

现在,让我们转到您要执行的功能。您撰写了rowSums但是从您的文字中推断出您的意思是colSums。我相应改变了。

somefun = function(x) {
  #attach the file-backed big.matrix
  #it makes the matrix "known" to the R process (no copying involved!)
  #input
  tmp=attach.big.matrix("~/file_backed_matrix.desc")
  #output
  tmp_out=attach.big.matrix("~/res_matrix.desc")

  #store the output in the file-backed matrix resm
  tmp_out[x$unique_idx,2]=c(colSums(tmp[,x$C,drop=FALSE]))
  #return a little more than the colSum result
  list(pid=Sys.getpid(),
       user=x$I[1],
       col_idx=x$C)
}

对所有核进行并行计算

#perform colSums using different threads
res=mclapply(split(df,df$I),somefun,mc.cores = detectCores())

检查结果

#processes IDs
unname(sapply(res,function(x) x$pid))
#28231 28232 28233 28234 28231 28232

#users
unname(sapply(res,function(x) x$user))
#1 2 3 4 5 6 

#column indexes
identical(sort(unname(unlist(sapply(res,function(x) x$col_idx)))),sort(df$C))
#[1] TRUE

#check result of colSums
identical(lapply(split(df,df$I),function(x) resm[x$unique_idx,2]),
          lapply(split(df,df$I),function(x) colSums(m[,x$C,drop=FALSE])))
#[1] TRUE

编辑:我在编辑中发表了您的评论。将结果存储在文件支持的输出矩阵resm中可以正常工作。

答案 1 :(得分:0)

为了限制适度大N的开销,使用mc.preschedule = TRUE几乎总是更好(即将工作分成与核心一样多的块)。

看来你的主要权衡是在内存使用和CPU之间。也就是说,您只能进行并行化,直到正在进行的进程最大化RAM。需要考虑的一件事是,不同的工作人员可以在R会话中读取相同的对象而不会重复。因此,只有在并行函数调用中修改/创建的对象才会为每个核心添加内存占用。

如果你最大化内存,我的建议是将你的整个计算划分为多个子工作并循环(例如,使用lapply),在该循环中调用mclapply来并行化每个subjob,并且可能保存subjob输出到磁盘以避免将其全部保存在内存中。