126*_*7b9 1 r processing-efficiency percentage microbenchmark
我试图弄清楚以下每个操作计算从 1 到 100 的累积和的速度有多快/慢。
install.package('microbenchmark')
library(microbenchmark)
#Method 1
cs_for = function(x) {
for (i in x) {
if (i == 1) {
xc = x[i]
} else {
xc = c(xc, sum(x[1:i]))
}
}
xc
}
cs_for(1:100)
# Method 2: with apply (3 lines)
cs_apply = function(x) {
sapply(x, function(x) sum(1:x))
}
cs_apply(100)
# Method 3:
cumsum (1:100)
microbenchmark(cs_for(1:100), cs_apply(100), cumsum(1:100))
Run Code Online (Sandbox Code Playgroud)
我得到的输出如下:
Unit: nanoseconds
expr min lq mean median uq max neval cld
cs_for(1:100) 97702 100551 106129.05 102500.5 105151 199801 100 c
cs_apply(100) 8700 9101 10639.99 9701.0 10651 54201 100 b
cumsum(1:100) 501 701 835.96 801.0 801 3201 100 a
Run Code Online (Sandbox Code Playgroud)
这表明与其他两种方法相比,最后一种方法具有最高的效率。在这种情况下,我感兴趣的是每种方法的速度快多少(无论是百分比还是速度快),我想知道您建议应用哪种操作?
请小心地对正确的东西进行基准测试:cs_appy(100)is\xe2\x80\x99t 与cs_for(100)! 遗憾的是,\xe2\x80\x99microbenchmark\xe2\x80\x99 没有\xe2\x80\x99 警告您这一点。相比之下, \xe2\x80\x98bench\xe2\x80\x99 包执行以下操作:
bench::mark(cs_for(1 : 100), cs_apply(100), cumsum(1 : 100))\n# Error: Each result must equal the first result:\n# `cs_for(1:100)` does not equal `cs_apply(100)`\nRun Code Online (Sandbox Code Playgroud)\n请注意,您的基准测试具有代表性:您正在查看单个数据点(长度为 100 的输入),并且它\xe2\x80\x99 是一个很小的输入大小。即使重复测量,结果也很可能受到噪声的影响。
\n始终使用几种不同的输入大小进行基准测试,以了解渐近增长。事实上,你的问题 \xe2\x80\x9chow 很多次 \xe2\x80\x9d 一种方法比另一种方法更快 \xe2\x80\x99t 甚至有意义,因为不同的函数不具有相同的渐近行为:两个一个是线性的,一个是二次的。要看到这一点,您必须反复测量。\xe2\x80\x98microbenchmark\xe2\x80\x99 不\xe2\x80\x99t 支持开箱即用,但 \xe2\x80\x98bench\xe2\x80\x99 支持(但你当然可以手动执行) 。
\n要进行同类比较,基准cumsum()测试也应该包装在一个函数内,并且所有函数都应该接受相同的参数(我建议n输入向量的大小)。
将所有这些放在一起,并使用 \xe2\x80\x98bench\xe2\x80\x99 包,我们得到以下结果。我还使用 \xe2\x80\x98vapply\xe2\x80\x99 添加了一个基准,因为 \xe2\x80\x99s 通常更喜欢sapply:
cs_for = function (n) {\n x = 1 : n\n for (i in x) {\n if (i == 1L) {\n xc = x[i]\n } else {\n xc = c(xc, sum(x[1 : i]))\n }\n }\n xc\n}\n\ncs_apply = function (n) {\n sapply(1 : n, \\(x) sum(1 : x))\n}\n\ncs_vapply = function (n) {\n vapply(1 : n, \\(x) sum(1 : x), integer(1L))\n}\n\ncs_cumsum = function (n) {\n cumsum(1 : n)\n}\n\nresults = bench::press(\n n = 10L ^ (1 : 4), # from 10 to 10,000\n bench::mark(cs_for(n), cs_apply(n), cs_vapply(n), cs_cumsum(n))\n)\nRun Code Online (Sandbox Code Playgroud)\n由于数据科学的第一条规则是\xe2\x80\x9c可视化你的结果\xe2\x80\x9d,这里\xe2\x80\x99是一个图:
\nresults |>\n mutate(label = reorder(map_chr(expression, deparse), desc(median))) |>\n ggplot() +\n aes(x = n, y = median, color = label) +\n geom_line() +\n geom_point() +\n bench::scale_y_bench_time() +\n scale_x_log10()\nRun Code Online (Sandbox Code Playgroud)\n\n\xe2\x80\xa6\xc2\xa0 如您所见,并非所有行都线性增加。事实上,cs_for和都呈cs_cumsum超线性增长。cs_for具有二次运行时间,因为逐步附加到xc向量需要分配新的存储并复制旧值。两者apply并vapply避免这种情况。使用时您还可以for通过预先分配结果向量来避免这种情况。事实cumsum 也表明出超线性运行时间,这实际上让我感到惊讶 \xe2\x80\x94 它绝对不应该\xe2\x80\x99 这样做!这值得进一步调查。