1

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 
4

1 回答 1

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()in并没有真正完成任何事情。没有已分配但不再与符号关联的内存,因此无需收集垃圾。这是我们分配一些内存,使用它,删除对它的引用,然后运行垃圾收集以释放内存的情况。fun2fun2

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 
于 2013-04-20T21:35:31.737 回答