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

Nic*_*Jay 9 memory garbage-collection memory-management r 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)
Run Code Online (Sandbox Code Playgroud)

a并且b仅相差约4kb但垃圾收集器报告情况1和2的峰值内存使用之间的差异为~30mb. c使用的内存比两者少,a并且c我认为在运行时没有相当大的损失.

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

这种行为可以使用复制lapply在data.frameS和也与替代的不同功能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)
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*den 4

我认为Hadley Wickham 的《高级 R 编程的内存章节》中的这句话最好地总结了差异的原因。

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

本章还有一个很好的函数叫做mem(),它可以让你更清楚地看到一段代码使用了多少内存而不是gc()允许的内存。如果时间允许,我会用Wickham函数重做测试mem()。

编辑: 正如彼得指出的,该mem()功能已被弃用。请改用pryrmem_used()包中的函数。

  • 该函数不再出现在书中,但现在作为 mem_used() 包含在 pryr 包中。 (2认同)