什么情况下 `vapply()` 比 `sapply()` 慢?

问题描述 投票:0回答:1

*apply()函数的

文档
指出:

vapply
sapply
类似,但有预先指定的返回值类型,因此使用起来更安全(并且有时更快)。 [强调我的。]

对我来说,为什么它会更快 - 浪费更少的时间检查类型 - 但是,考虑到他们可以说像 '

vapply()
一样快或比
sapply()
'
更快,但选择不这样做,我将他们选择的“有时更快”解释为“对于大多数任务来说,平均速度更快”,但在某些情况下,平均速度可能相同,甚至“更慢”,这看起来很奇怪大部头书。为什么它会变慢?高级 R 明确指出 '
vapply()
vapply()'
更快,相比之下,后者相当明确。 我是否误解了这一点,或者是否存在sapply()
vapply()
慢的情况,如果是这样,它们是什么?
例如,其基本原理可能是由于垃圾收集的差异,或处理某些类型的速度,或分配内存或其他原因(这些都是疯狂的猜测)。

我所做的研究:

令人惊讶的是,我在 StackOverflow 或其他地方找不到在线解决此问题的方法。有很多问题需要
参考 vapply
,以及

其安全性

。在

少数
比较

中,虽然sapply()vapply()一样快或更快,但有许多迭代比最慢的sapply()迭代更快(其中vapply()明显快于任一

apply()
lapply()
。长话短说,我有点迷失了!
您能提供的任何帮助将不胜感激!
    
使
vapply()
快速的支票不是免费的

为了设计

vapply()
r loops apply lapply sapply
1个回答
1
投票
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()
更快,例如立即分配一个原子向量作为输出,而不是分配一个列表并简化为一个向量。然而,它还包含这个:

sapply()

我们告诉
// 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 中运行

ALTREP

的成本要高得多,R 知道什么是
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()

值得指出的是,尽管这很有趣,但这是一个相当人为的案例。在任何人都会使用 R 执行的绝大多数任务中,
ggplot2::autoplot(results) +
    labs(title = "Comparison of results", x = "Time (seconds)", y = "Expression")
可能比

vapply() 快得多。此外,如您所知,还有其他好处,例如 sapply() 确保返回类型得到保证。


© www.soinside.com 2019 - 2024. All rights reserved.