*apply()
函数的
文档指出:
与vapply
类似,但有预先指定的返回值类型,因此使用起来更安全(并且有时更快)。 [强调我的。]sapply
对我来说,为什么它会更快 - 浪费更少的时间检查类型 - 但是,考虑到他们可以说像 '
vapply()
一样快或比 sapply()
' 更快,但选择不这样做,我将他们选择的“有时更快”解释为“对于大多数任务来说,平均速度更快”,但在某些情况下,平均速度可能相同,甚至“更慢”,这看起来很奇怪大部头书。为什么它会变慢?高级 R 明确指出 'vapply()
比 vapply()
' 更快,相比之下,后者相当明确。
我是否误解了这一点,或者是否存在sapply()
比
vapply()
慢的情况,如果是这样,它们是什么?例如,其基本原理可能是由于垃圾收集的差异,或处理某些类型的速度,或分配内存或其他原因(这些都是疯狂的猜测)。
我所做的研究:
令人惊讶的是,我在 StackOverflow 或其他地方找不到在线解决此问题的方法。有很多问题需要参考 vapply,以及其安全性
。在
少数比较中,虽然sapply()
与vapply()
一样快或更快,但有许多迭代比最慢的sapply()
迭代更快(其中vapply()
明显快于任一
) apply()
或 lapply()
。长话短说,我有点迷失了!您能提供的任何帮助将不胜感激!使
vapply()
快速的支票不是免费的
为了设计
vapply()
vapply()
源。
sapply()
中的大部分工作都是由
sapply()
完成的。 lapply()
的C代码非常简单。相关部分是(我的评论):lapply()
本质上,这创建了输入列表的长度,其中每个元素都是输入的结果。 int n = length(list); // Length of the input list
// Allocate a vector for the output with the length of the input vector/list
ans = PROTECT(allocVector(VECSXP, n));
// Loop through input list, apply relevant function and
// assign result to each respective element of the output list
for(int i = 0; i < n; i++) {
defineVar(install("x"), VECTOR_ELT(list, i), rho);
SET_VECTOR_ELT(ans, i, eval(expr, rho));
}
然后通过 sapply()
运行结果。
相反,
simplifytoarray()
的 C 代码做了更多的工作。其中很多都是优化,这使得它比
vapply()
更快,例如立即分配一个原子向量作为输出,而不是分配一个列表并简化为一个向量。然而,它还包含这个:
我们告诉
// Check that the result is the correct length for the output vector/list
if (length(val) != commonLen)
error(_("values must be length %d,\n but FUN(X[[%d]]) result is length %d"),
commonLen, i+1, length(val));
输出的长度和类型。这意味着,例如,如果我们告诉 vapply()
输出是 vapply()
,它需要检查每次迭代是否产生长度为 1 的整数向量。
这些支票价格昂贵的情况
创建成本高昂的检查的一种方法是返回一个检查长度成本高昂的值。考虑一个简单的例子:integer(1)
lapply(1, \(i) seq(1e9))
lapply()
产生一个
seq(1e9)
,一个替代表示
。这意味着它不必分配长度为
ALTREP
的向量,而是分配一个更小的对象,该对象本质上保存起始值、结束值和增量。然而,1e9
的文档指出:
对于现有的 C 代码 ALTREP 对象看起来就像普通的 R 对象。
这意味着 ALTREP
不知道这是一个 vapply()
,因此它需要以一种非常昂贵的方式检查长度(比在 R 中运行的成本要高得多,R 知道什么是
ALTREP
length()
是)。
ALTREP
还必须做一些代价高昂的事情,因为 sapply()
最终会将列表复制到向量中。问题是:哪个更贵?对人为案例进行基准测试
让我们来测试一下:
simplifyToArray()
结果
results <- bench::mark(
min_iterations = 1,
max_iterations = 100,
check = FALSE,
time_unit = "s",
lapply = {
lapply(1, \(i) seq(1e9))
},
sapply = {
sapply(1, \(i) seq(1e9))
},
vapply = {
vapply(1, \(i) seq(1e9), numeric(1e9))
}
)
我们可以在这里看到
# A data frame: 3 × 9
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
# <bch:expr> <dbl> <dbl> <dbl> <bch:byt> <dbl> <int> <dbl> <dbl>
# 1 lapply 0.00000930 0.0000166 41968. 138.6KB 0 100 0 0.00238
# 2 sapply 23.5 23.5 0.0425 11.2GB 0.0425 1 1 23.5
# 3 vapply 88.7 88.7 0.0113 22.4GB 0.0338 1 3 88.7
比
vapply()
sapply()
的向量,
1e9
仍然比
sapply()
更快。结果图vapply()
ggplot2::autoplot(results) +
labs(title = "Comparison of results", x = "Time (seconds)", y = "Expression")
可能比 vapply()
快得多。此外,如您所知,还有其他好处,例如 sapply()
确保返回类型得到保证。