【问题标题】:split-lapply- combine a large dataframe to avoid memory issues?split-lapply - 组合一个大数据帧以避免内存问题?
【发布时间】:2019-05-11 11:55:08
【问题描述】:

我有一种情况,使用 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 循环的工作原理就像没有内存问题的魅力,但是以并行化为代价!

【问题讨论】:

    标签: r memory runtime apply lapply


    【解决方案1】:

    问题是你在比较苹果和苹果;-)

    如果我猜对了,最终结果应该是一个列表或向量,其中仅包含 所有 样本中存在的那些项目,对吧?
    因为现在,您正在将所有样本与第一个样本进行比较,这工作量太大了。让我们看一个较小的示例,以及您实际存储的内容。
    我们将在您的代码中执行相同的操作,除了
    numListFull &lt;- 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 循环中,处理器可以将相关数据保存在缓存中,甚至可以提前获取稍后需要的数据。但是,如果您坚持并行处理,则意味着要处理内存的不同部分,这意味着必须将所有数据写掉并再次检索。这可以很好地平衡您通过使用多个内核获得的任何收益。

    【讨论】:

      猜你喜欢
      • 2017-11-13
      • 1970-01-01
      • 2020-09-28
      • 1970-01-01
      • 2016-08-22
      • 2018-10-07
      • 2019-08-04
      • 2017-10-01
      • 1970-01-01
      相关资源
      最近更新 更多