当有更多元素要循环/应用时,为什么峰值内存使用会增加?

时间:2014-02-04 21:19:33

标签: r memory memory-management garbage-collection apply

我正在尝试减少R包的内存占用,并注意到我似乎无法抑制的行为。请参阅以下示例:

x <- matrix(runif(1.5e7), ncol = 200)

## CASE 1: Test with half of columns
gc(reset = TRUE)
a <- apply(x[, 1:100], 2, quantile)
gc()
#            used  (Mb) gc trigger  (Mb) max used  (Mb)
# Ncells   190549  10.2     407500  21.8   222055  11.9
# Vcells 15292303 116.7   35490421 270.8 35484249 270.8
object.size(a)
# 4696 bytes
rm(a)

## CASE 2: Test with all columns
gc(reset = TRUE)
b <- apply(x, 2, quantile)
gc()
#            used  (Mb) gc trigger  (Mb) max used  (Mb)
# Ncells   190824  10.2     407500  21.8   245786  13.2
# Vcells 15293740 116.7   39292189 299.8 39286529 299.8
object.size(b)
# 8696 bytes
rm(b)

## CASE 3: Test with all columns + call gc
gc(reset = TRUE)
c <- apply(x, 2, function(i) { r <- quantile(i); gc(); r })
gc()
#           used  (Mb) gc trigger  (Mb) max used  (Mb)
# Ncells   191396  10.3     407500  21.8   197511  10.6
# Vcells 15294307 116.7   45737818 349.0 30877185 235.6
object.size(c)
# 8696 bytes
rm(c)

ab相差仅约4kb但垃圾收集器报告案例1和2的峰值内存使用量之间的差异为~30mb。c使用的内存少于两者ac,我想在运行时不会受到相当大的惩罚。

峰值内存分配似乎与调用apply时考虑的列数正相关,但为什么呢?调用apply会导致内存分配超出迭代范围吗?我希望在每次迭代结束之前gc释放(或标记为未使用)任何内部临时值。

可以使用lapply替换data.frame以及使用不同的函数代替quantile来重现此行为。

我的印象是我忽略了R中内存使用行为的一个非常基本的方面,但仍然无法绕过它。最后,我的问题是:如上例所示,如何进一步减少内存占用?

提前致谢,不要犹豫,在我的问题中指出任何不准确之处。

编辑:

根据@ ChristopherLouden的建议,我使用mem来代替gc,并且所有三个案例被描述为占用~126.9182mb。

##  http://adv-r.had.co.nz/memory.html#garbarge-collection
mem <- function() {
  bit <- 8L * .Machine$sizeof.pointer
  if (!(bit == 32L || bit == 64L)) {
    stop("Unknown architecture", call. = FALSE)
  }

  node_size <- if (bit == 32L) 28L else 56L

  usage <- gc()
  sum(usage[, 1] * c(node_size, 8)) / (1024 ^ 2)
}

1 个答案:

答案 0 :(得分:4)

我认为Hadley Wickham的Memory Chapter of Advanced R Programming中的这句话最能总结出这种差异的原因。

  

垃圾收集通常会懒散地发生:当需要更多空间时,R会调用gc()。实际上,在函数终止后,R可能会保留在内存中,但它会在需要时立即释放它

本章还有一个名为mem()的好函数,可以让你更清楚地看到代码块使用的内存比gc()允许的多少。如果时间允许,我会用Wickham的mem()函数重新进行测试。

修改 正如彼得指出的那样,mem()函数已被弃用。请改用 pryr 包中的mem_used()功能。