测试 R 中命令的快/慢程度

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)

这表明与其他两种方法相比,最后一种方法具有最高的效率。在这种情况下,我感兴趣的是每种方法的速度快多少(无论是百分比还是速度快),我想知道您建议应用哪种操作?

Kon*_*lph 5

    \n
  1. 请小心地对正确的东西进行基准测试: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 包执行以下操作:

    \n
    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)`\n
    Run Code Online (Sandbox Code Playgroud)\n
  2. \n
  3. 请注意,您的基准测试具有代表性:您正在查看单个数据点(长度为 100 的输入),并且它\xe2\x80\x99 是一个很小的输入大小。即使重复测量,结果也很可能受到噪声的影响。

    \n
  4. \n
  5. 始终使用几种不同的输入大小进行基准测试,以了解渐近增长。事实上,你的问题 \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
  6. \n
  7. 要进行同类比较,基准cumsum()测试也应该包装在一个函数内,并且所有函数都应该接受相同的参数(我建议n输入向量的大小)。

    \n
  8. \n
\n

将所有这些放在一起,并使用 \xe2\x80\x98bench\xe2\x80\x99 包,我们得到以下结果。我还使用 \xe2\x80\x98vapply\xe2\x80\x99 添加了一个基准,因为 \xe2\x80\x99s 通常更喜欢sapply

\n
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)\n
Run Code Online (Sandbox Code Playgroud)\n

由于数据科学的第一条规则是\xe2\x80\x9c可视化你的结果\xe2\x80\x9d,这里\xe2\x80\x99是一个图:

\n
results |>\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()\n
Run Code Online (Sandbox Code Playgroud)\n

基准图

\n

\xe2\x80\xa6\xc2\xa0 如您所见,并非所有行都线性增加。事实上,cs_for和都呈cs_cumsum超线性增长。cs_for具有二次运行时间,因为逐步附加到xc向量需要分配新的存储并复制旧值。两者applyvapply避免这种情况。使用时您还可以for通过预先分配结果向量来避免这种情况。事实cumsum 也表明出超线性运行时间,这实际上让我感到惊讶 \xe2\x80\x94 它绝对不应该\xe2\x80\x99 这样做!这值得进一步调查。

\n