加快lapply的垃圾收集

时间:2013-04-20 18:31:22

标签: r garbage-collection lapply

lapply函数或循环中进行垃圾收集的最快方法是什么?对我来说,显而易见的事情让事情变得非常缓慢。我做错了吗?有更快的方法吗?

x <- 1:10000
system.time(xx <- lapply(1:length(x), function(xi) sum(x[1:xi])))
 user  system elapsed 
   0.02    0.00    0.02 
system.time(xx <- lapply(1:length(x), function(xi) sum(x[1:xi], invisible(gc(v=FALSE)))))
   user  system elapsed 
  22.49    0.00   22.57 # a thousand times increase in time taken!!

在我的实际用例中,函数有点复杂,并且在每个实例之后没有gc就失败了。我可以切换到具有更多RAM的机器,虽然它不太方便,所以我很好奇是否有更快的gc方法可用。

更新按照Martin Morgan的建议,重新排列事物会使速度接近lapply而没有gc(现在在不同的机器上工作,这就是为什么时间与上述不同):

x <- 1:10000
system.time(x1 <- lapply(1:length(x), function(xi) sum(x[1:xi])))
   user  system elapsed 
   3.47    0.00    3.56 
# define a function to make a sequence of a function followed by gc
sum_gc <- function(x) sum(x); invisible(gc(v=FALSE))
system.time(x3 <- lapply(1:length(x), function(xi) sum_gc(x[1:xi])))
   user  system elapsed 
   3.52    0.02    3.56 

1 个答案:

答案 0 :(得分:3)

不是答案,但比评论更长。本,这个

fun0 = function(x) sum(x, gc())

定义了一个函数,用于计算“x和gc()返回的值之和”。此

fun1 = function(x) sum(x); gc()

定义一个返回x之和的函数。 gc()在定义函数后运行,但不是函数定义的一部分。

fun2 = function(x) {
    result = sum(x)
    gc()
    result
}

定义一个函数,该函数计算x的总和并将其保存到函数内部存在的变量result。然后它评估函数gc()。然后它返回result中包含的值,即x的总和。除了时间之外,值得比较结果

test_case = 1:5
identical(sum(test_case), fun0(test_case))  # FALSE
identical(sum(test_case), fun1(test_case))  # TRUE, but no garbage collection
identical(sum(test_case), fun2(test_case))  # TRUE

在第一次评估gc()之后,在fun2中调用fun2并没有真正完成任何事情。没有已分配但不再与符号关联的内存,因此不会收集垃圾。这是我们分配一些内存,使用它,删除对它的引用,然后运行垃圾收集以释放内存的情况。

fun3 = function(x) {
   m = rnorm(length(x))
   result = sum(m * x)
   rm(m)
   gc()
   result
}

但是明显的垃圾收集并没有在这里做任何事情 - 当R需要的内存超过可用内存时,垃圾收集器会自动运行。如果多次调用fun3,则每个调用内部将使用一个不再被符号引用的内存,因此将在垃圾收集器自动运行时收集。通过直接调用gc(),您断言您的天真垃圾收集策略(一直这样做)比R更好(当需要更多内存时执行)。

哪一个可以做(写一个更好的垃圾收集器)。

但事实并非如此。

我提到过,当遇到性能或内存问题时,往往会付出代价来回顾并查看您的算法和实现。我知道这是一个“玩具”的例子,但无论如何我们都要看。你计算的是x元素的累积和。我已经将您的实现编写为

fun4 = function(i, x) sum(x[seq_len(i)])
sapply(seq_along(test_case), fun4, test_case)

给出

> x0 <- sapply(seq_along(test_case), fun4, test_case)
> x0
[1]  1  3  6 10 15

但是R有一个函数cumsum,可以在内存和速度方面更有效地完成这项任务。

> x1 <- cumsum(test_case)
> identical(x0, x1)
[1] TRUE
> test_case = seq_len(10000)
> system.time(x0 <- sapply(seq_along(test_case), fun4, test_case))
   user  system elapsed 
  2.508   0.000   2.517 
> system.time(x1 <- cumsum(test_case))
   user  system elapsed 
  0.004   0.000   0.002