为何R中循环封装为函数提速8倍且快于vapply?
R语言循环与
*apply函数的性能差异疑问 我正在研究R语言中循环与*apply函数的性能差异,通过三种方式计算前10000个三角形数:
unwrapped:全局环境下的简单for循环wrapped:将上述循环封装为自定义函数vapply:使用vapply配合匿名函数
测试结果带来两个核心疑问:
- 为何
wrapped比unwrapped快近8倍?按直觉封装函数会涉及额外的调用操作,理应更慢; - 为何循环(无论是否封装)的性能都远优于
vapply?原本预期vapply具备不弱于循环的优化能力。
测试代码及结果
microbenchmark::microbenchmark( unwrapped = { x <- numeric(10000) for (i in 1:10000) { x[i] <- i * (i + 1) / 2 } x }, wrapped = { tri_nums <- function(n) { x <- numeric(n) for (i in 1:n) { x[i] <- i * (i + 1) / 2 } x } tri_nums(10000) }, vapply = vapply(1:10000, \(i) i * (i + 1) / 2, numeric(1)), check = 'equal' ) #> Unit: microseconds #> expr min lq mean median uq max neval #> unwrapped 2652.487 3006.888 3445.896 3150.7555 3832.094 7029.949 100 #> wrapped 398.534 414.010 455.333 439.7445 469.307 656.074 100 #> vapply 4942.000 5154.639 5937.333 5453.2880 5969.760 13730.718 100
Created on 2023-01-04 with reprex v2.0.2
问题解答
1. 封装函数的循环为何更快?
R的字节码编译器会自动对函数体内的代码进行编译优化,而全局环境中的代码是逐行解释执行的,没有编译步骤。函数定义时,R会将函数体转换为字节码,执行时直接运行编译后的字节码,速度远快于解释执行全局环境的循环。这就是为什么看似多了函数调用步骤,实际执行反而快很多的原因。
2. 为何vapply比循环慢?
vapply的本质是遍历每个元素并调用一次匿名函数,每次函数调用都有固定的开销(比如环境切换、参数传递)。而for循环是在同一个环境中连续执行计算,没有额外的函数调用开销。此外,vapply还要对每个返回值做类型检查(确保符合numeric(1)的要求),这也会增加少量额外开销。对于这种计算逻辑极简单的场景,函数调用的开销占比极高,导致vapply的性能远不如循环。
内容的提问来源于stack exchange,提问作者crf
相关产品推荐
相关产品推荐

