当循环/应用的元素增加时,为什么峰值内存使用量会增加?

9

我正在尝试减少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的内存使用量比ab都少,但我想这不是没有运行时间上的显著惩罚。

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

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

我有一种感觉,我正在忽略R中内存使用行为的一个非常基本的方面,但仍然无法理解。最终,我的问题是:如何在像上面示例中的情况下进一步减少内存占用?

提前感谢您的帮助,如有不准确之处请指出。

编辑:

根据@ChristopherLouden的建议,我使用了对gc的调用来代替mem,结果三种情况都被描述为使用了约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个回答

4
我认为Hadley Wickham在Advanced R Programming的Memory章节中的这句话最好地概括了差异的原因。
垃圾回收通常是惰性的:R在需要更多空间时才调用gc()。实际上,R可能会在函数终止后仍然保留内存,但只要需要,就会立即释放它。
该章节还有一个名为mem()的好函数,可以比gc()更清楚地查看代码块使用了多少内存。如果时间允许,我会使用Wickham的mem()函数重新进行测试。
编辑: 正如Peter所指出的那样,mem()函数已被弃用。请改用pryr包中的mem_used()函数。

2
该函数已不再书中,但现在已包含在pryr软件包中,命名为mem_used()。 - Peter Verbeet
@PeterVerbeet:谢谢。我已经更新了答案以反映这一点。 - Christopher Louden
当然,@Christopher-Louden。请注意它在“pryr”包中,而不是“plyr”(只有1个字符的区别...)。 pryr包有更多很酷的东西可以查看正在发生的事情。例如,我经常使用“address(x)”来查找对象x的内存位置。这在检查x是否被就地修改(-->在对x进行操作之前和之后具有相同的内存位置)或者在对x进行修改之前是否由x复制(--> x的内存位置在操作后将不同)非常有用。 - Peter Verbeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接