【发布时间】:2015-05-13 01:06:37
【问题描述】:
所以我们习惯于对每个 R 新用户说“apply 没有矢量化,请查看 Patrick Burns R Inferno Circle 4”,它说(我引用):
一个常见的反应是使用 apply 系列中的一个函数。 这不是 向量化,它是循环隐藏的。 apply 函数有一个 for 循环 它的定义。 lapply 函数隐藏了循环,但执行 时间往往大致等于一个显式的 for 循环。
确实,快速浏览apply 源代码会发现循环:
grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] " for (i in 1L:d2) {" " else for (i in 1L:d2) {"
到目前为止还可以,但是看看lapply 或vapply 实际上会发现完全不同的情况:
lapply
## function (X, FUN, ...)
## {
## FUN <- match.fun(FUN)
## if (!is.vector(X) || is.object(X))
## X <- as.list(X)
## .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>
所以显然那里没有隐藏 R for 循环,而是它们正在调用内部 C 编写的函数。
此外,我们以colMeans 函数为例,它从未被指责为未矢量化
colMeans
# function (x, na.rm = FALSE, dims = 1L)
# {
# if (is.data.frame(x))
# x <- as.matrix(x)
# if (!is.array(x) || length(dn <- dim(x)) < 2L)
# stop("'x' must be an array of at least two dimensions")
# if (dims < 1L || dims > length(dn) - 1L)
# stop("invalid 'dims'")
# n <- prod(dn[1L:dims])
# dn <- dn[-(1L:dims)]
# z <- if (is.complex(x))
# .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) *
# .Internal(colMeans(Im(x), n, prod(dn), na.rm))
# else .Internal(colMeans(x, n, prod(dn), na.rm))
# if (length(dn) > 1L) {
# dim(z) <- dn
# dimnames(z) <- dimnames(x)[-(1L:dims)]
# }
# else names(z) <- dimnames(x)[[dims + 1]]
# z
# }
# <bytecode: 0x0000000008f89d20>
# <environment: namespace:base>
嗯?它也只是调用.Internal(colMeans(...,我们也可以在rabbit hole 中找到它。那么这与.Internal(lapply(.. 有什么不同呢?
实际上,一个快速的基准测试表明,sapply 的性能并不比colMeans 差,并且比for 循环在大数据集上的表现要好得多
m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user system elapsed
# 1.69 0.03 1.73
system.time(sapply(m, mean))
# user system elapsed
# 1.50 0.03 1.60
system.time(apply(m, 2, mean))
# user system elapsed
# 3.84 0.03 3.90
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user system elapsed
# 13.78 0.01 13.93
换句话说,说lapply 和vapply 实际上是矢量化的 是否正确(与apply 相比,for 循环也调用lapply)帕特里克·伯恩斯到底想说什么?
【问题讨论】:
-
这都是语义上的,但我不会认为它们是矢量化的。如果一个 R 函数只被调用一次并且可以传递一个值向量,我会考虑一种向量化的方法。
*apply函数重复调用 R 函数,这使得它们循环。关于sapply(m, mean)的良好性能:可能lapply的C 代码只分派一次方法,然后重复调用该方法?mean.default非常优化。 -
很好的问题,感谢您检查底层代码。我正在查看它最近是否已更改,但从 2.13.0 版开始的 R 发行说明中没有关于此的内容。
-
性能在多大程度上取决于平台以及使用的 C 编译器和链接器标志?
-
@DavidArenburg 实际上,我认为它的定义并不明确。至少我不知道规范的参考。语言定义提到了“向量化”操作,但没有定义向量化。
-
非常相关:Is R's apply family more than syntactic sugar?(而且,就像这些答案一样,也是一本好书。)
标签: r performance loops vectorization apply