我试图更深入地了解 R 中的循环与 *apply 函数。在这里,我做了一个实验,用 3 种不同的方式计算前 10,000 个三角形数。
unwrapped:一个简单的for循环wrapped:我采用与之前完全相同的循环,但将其包装在一个函数中。vapply:使用vapply匿名函数。结果在两个方面让我感到惊讶。
wrapped比 (?!?!) 快 8 倍,unwrapped我的直觉是给定wrapped实际上做了更多的事情(定义一个函数然后调用它),它应该更慢。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
Run Code Online (Sandbox Code Playgroud)
创建于 2023-01-04,使用reprex v2.0.2
它正在对您的函数进行字节编译。
\n我们可以通过以下方式确认即时 (JIT) 编译:
\ncompiler::enableJIT(-1)\n# [1] 3 # <--- this is the previous JIT level\nRun Code Online (Sandbox Code Playgroud)\n其中负数返回当前级别不变,值 3 表示最高 JIT 编译级别。我不确定每个级别都在做什么步骤,但我们可以做一个简单的测试来比较它们。(看?enableJIT参考资料 了解更多信息。)
compiler::enableJIT(0)\n# [1] 3\ntri_nums <- function(n) {\n x <- numeric(n)\n for (i in 1:n) {\n x[i] <- i * (i + 1) / 2\n }\n x\n}\nbench::mark(\n unwrapped = {\n x <- numeric(10000)\n for (i in 1:10000) {\n x[i] <- i * (i + 1) / 2\n }\n x\n },\n JIT0 = tri_nums(10000),\n vapply = vapply(1:10000, \\(i) i * (i + 1) / 2, numeric(1))\n)\n# # A tibble: 3 \xc3\x97 13\n# expression min median `itr/sec` mem_al\xe2\x80\xa6\xc2\xb9 gc/se\xe2\x80\xa6\xc2\xb2 n_itr n_gc total\xe2\x80\xa6\xc2\xb3 result memory time gc \n# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:by> <dbl> <int> <dbl> <bch:t> <list> <list> <list> <list> \n# 1 unwrapped 8.21ms 8.7ms 113. 78.2KB 7.07 48 3 424ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# 2 JIT0 7.26ms 7.72ms 128. 78.2KB 9.84 52 4 407ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# 3 vapply 5.97ms 6.5ms 152. 78.2KB 9.51 64 4 421ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# # \xe2\x80\xa6 with abbreviated variable names \xc2\xb9\xe2\x80\x8bmem_alloc, \xc2\xb2\xe2\x80\x8b`gc/sec`, \xc2\xb3\xe2\x80\x8btotal_time\nRun Code Online (Sandbox Code Playgroud)\n(我不能同时放入所有三个级别,因为我相信 JIT 检查在我们调用它以及定义它时就完成了。我真的没有资格谈论 R 内部的这个级别,所以...请纠正我和/或添加放大信息。)
\n对级别 1-3 再次执行此操作并复制/粘贴相关bench::mark行,我们看到:
# # A tibble: 3 \xc3\x97 13\n# expression min median `itr/sec` mem_al\xe2\x80\xa6\xc2\xb9 gc/se\xe2\x80\xa6\xc2\xb2 n_itr n_gc total\xe2\x80\xa6\xc2\xb3 result memory time gc \n# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:by> <dbl> <int> <dbl> <bch:t> <list> <list> <list> <list> \n# 1 unwrapped 8.21ms 8.7ms 113. 78.2KB 7.07 48 3 424ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# 2 JIT0 7.26ms 7.72ms 128. 78.2KB 9.84 52 4 407ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# 2 JIT1 419.6\xc2\xb5s 502.5\xc2\xb5s 1923. 108.7KB 0 962 0 500ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# 2 JIT2 413.4\xc2\xb5s 494.3\xc2\xb5s 1971. 108.7KB 0 986 0 500ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# 2 JIT3 426.7\xc2\xb5s 498.3\xc2\xb5s 1981. 108.7KB 0 991 0 500ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# 3 vapply 5.97ms 6.5ms 152. 78.2KB 9.51 64 4 421ms <dbl> <Rprofmem> <bench_tm> <tibble>\n# # \xe2\x80\xa6 with abbreviated variable names \xc2\xb9\xe2\x80\x8bmem_alloc, \xc2\xb2\xe2\x80\x8b`gc/sec`, \xc2\xb3\xe2\x80\x8btotal_time\nRun Code Online (Sandbox Code Playgroud)\n表明绝大多数收益都在字节编译的第一级(考虑到该函数的简单性,这并不奇怪)。
\n注意:对于任何实际测试部分代码的人,您可能需要确保回到默认级别 3:
\ncompiler::enableJIT(3)\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
1003 次 |
| 最近记录: |