u <- rnorm(10000)
v <- rnorm(10000)
# `outer`
system.time(mat1 <- outer(u, v , `<`))
# user system elapsed
# 1.80 1.34 3.32
# `for` loop
system.time({
mat2 <- matrix(NA, nrow = length(u), ncol = length(v))
for(i in seq_along(v)) {
mat2[, i] <- u < v[i]
}
})
# user system elapsed
# 0.97 0.02 1.01
identical(mat1, mat2)
# [1] TRUE
1条答案
按热度按时间roejwanj1#
分配和销毁内存需要时间
如果使用
bench::press()
并设置四个选项,您会感觉到内存分配最多的方法花费的时间最长,正如David Arenburg在注解中所建议的那样。这四个选项是:
outer()
.1.一个
for
循环。vapply()
(来自sindri_baldur的注解)。(rep(x), rep(y))
(正是outer()
在引擎盖下所做的)。我喜欢
bench
,因为它显示了内存使用情况,图中的每个方面都用n*n
矩阵显示了这四种方法的速度,以及垃圾收集的级别。对于100行,
vapply
比其他方法慢,并且gc
(垃圾收集)没有区别。但是,一旦数据大于这个值,我们就可以看到
vapply()
执行的垃圾收集要少得多,而且要快得多。类似地,在最后一个方面(
1e4
行和列)中,我们可以看到for
循环的垃圾收集较少,而且往往比outer()
快。vapply()
使用的RAM最少你可能会怀疑
vapply()
的垃圾回收更少,因为它留下了更多的垃圾未回收,然而,如果我们看一下总的RAM使用情况,我们可以看到实际上它使用了outer()
的三分之一的RAM:注意:我不知道在创建一个1x1矩阵时,它实际上是如何使用0字节的--但是如果你真的比较两个标量,你可能根本就不使用矩阵。
垃圾收集级别的含义是什么?
请参阅R Internals一章,写屏障和垃圾收集器:
有三个级别的集合。级别0仅收集最年轻的代,级别1收集两个最年轻的代,级别2收集所有代。在20个级别0集合之后,下一个集合位于级别1,在5个级别1集合之后,下一个集合位于级别2。此外,如果级别n集合无法提供20%的可用空间(对于每个节点和向量堆),下一次收集将在第n +1层(R层函数gc()执行第2层收集)。
理解这一点的方法是,如果一个函数创建了更多的临时对象,然后销毁它们,那么它将执行更多的分配,并进行更多的垃圾收集。
运行模拟并生成第一个图的代码
第二个地块的代码
免责声明:这些是在一台机器上的结果(一台不起眼的相当旧的笔记本电脑)。我没有像你一样得到
outer()
和for
循环之间相同大小的差异,所以你的结果可能会不同。