split-lapply-合并大型数据帧以避免内存问题?

时间:2018-12-10 14:49:18

标签: r memory runtime apply lapply

我遇到的情况是,使用split-apply-combine可能会遇到运行时内存问题。任务是识别所有模拟中的共同元素。

numListFull <- replicate(1000, sample(1:55000, sample(54900:55000), 
                                  replace = FALSE))
format(object.size(numListFull), units = "auto", standard = "SI")
# [1] "66 MB"

# Create list of nums shared by all simulations
numListAll <- numListFull[[1]]
numList <- lapply(numListFull[2:length(numListFull)],
                    function(x){intersect(x, numListAll)})
format(object.size(numList), units = "auto", standard = "SI")
# [1] "65.7 MB"

numListAll <- Reduce(intersect, numList)
format(object.size(numListAll), units = "auto", standard = "SI")
# [1] "166.4 kB"

当复制从300增加到1000时,大小为219.9 MB219.9 MB87.5 kB

有时,复制量甚至会超过10000,即后者的10倍。您知道这样做的更好方法来避免计算机中的内存问题吗?

像这样明智吗?

numList <- lapply(split(2:length(numListFull), rep_len(1:100,length(numListFull))), 
                  function(ind){
                    lapply(numListFull[ind], 
                           function(x){
                             intersect(x, numListAll)})})
format(object.size(numList), units = "auto", standard = "SI")  
# [1] "87.5 MB"

更新:当然,for循环的作用就像没有存储问题的魅力一样,但是以并行化为代价!

1 个答案:

答案 0 :(得分:0)

问题在于,您正在将苹果与苹果进行比较;-)

如果我理解正确,那么最终结果应该是仅包含所有样本中存在的项目的列表或向量,对吧?
因为现在,您正在将所有样本与第一个样本进行比较,所以工作量太大。让我们看一个较小的示例,以及您实际存储的内容。
我们将在您的代码中执行相同的操作,除了
numListFull <- replicate(10, sample(1:1, sample(8:10, size=1)))
(旁注:提供size=1也会加快代码的速度,尽管幅度不大)
无论如何,运行代码后,numList现在包含第一个样本中不存在的所有值,但是仍然有很多重复的值,并且这些值可能已被删除。 第二次尝试没有什么不同,尽管我不确定您在这里尝试做什么,但您仍将所有示例与第一个示例进行比较。

老实说,我认为for循环可能并不是最糟糕的情况。一旦您知道某些值不在第一个样本中,或者不在第二个样本中,那么将这些值与第三个样本中的值进行比较就没有用了。所以如果

firststep <- intersect(numListFull[[1]], numListFull[[2]])

然后最好的下一步是将numListFull[[3]]与该firststep进行比较,而不是再次查看numListFull[[1]]。这意味着您的结果会不断更新,并且for循环是最好的。
只需跟踪所有样本到该点为止的所有值,而不是存储各种中间结果即可。

或并行

可以并行处理,但是随后必须单独计算。因此,将sample1与sample2进行比较,同时将3与4进行比较; 5和6等 我认为这更多是本着处理大数据的方式的精神:在很小的块上​​进行计算,以便减少可传递的结果。但是将其可视化为二叉树:在每个步骤中,传递的结果最多是其一半。 我认为这就是您在第二个示例中尝试做的事情。至少我明白了:

numListFull <- lapply(split(numListFull, 
   rep(1:ceiling(length(numListFull)/2), each=2)[seq_len(length(numListFull))]), function(dfs) {
     if(length(dfs)==2) {
       return(intersect(dfs[[1]], dfs[[2]]))
     } else {
       # Can be just one
       return(dfs[[1]])
     }
   })

当然这只是一个步骤,但是如果将其放入while循环中,它将持续到长度仅为1。 对于内存,numListFull在每个循环中都会被覆盖,因此垃圾控制始终可以将内存使用量减少到比我们开始时少的数量。

另一方面,调试它可能比在for循环中更难,而且我不确定intersect中已经发生了多少并行化。无论如何,您必须将lapply替换为parallel::mclapply才能获得这些好处,并且如果您是在普通台式机或笔记本电脑上运行设备,那么还会遇到另一个内存问题,即缓存会带来更多好处很难。
在for循环中,处理器可以将相关数据保留在高速缓存中,甚至可以提前获取不久后需要的数据。但是,如果您坚持并行处理,则意味着要在内存的不同部分上进行工作,这意味着必须将所有数据写掉并重新检索。这样可以很好地平衡您使用多核所获得的任何收益。