测试 faster/slower 是 R 中的命令

Testing how faster/slower are commands in R

我想弄清楚fast/slow以下每个操作是如何计算从 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))

我得到的输出如下:

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

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

您可以通过保存摘要来处理 table 个结果:

results <- summary(microbenchmark(cs_for(1:100), cs_apply(100), cumsum(1:100)))

然后直接进行计算,例如计算中位数时间大于最快中位数的次数:

results[,"median"]/min(results[,"median"])
# [1] 131.35714  14.14286   1.00000
  1. 请注意您的基准测试是否正确:cs_appy(100)cs_for(100) 不同!遗憾的是“微基准测试”没有警告您这一点。相比之下,'bench' 包可以:

    bench::mark(cs_for(1 : 100), cs_apply(100), cumsum(1 : 100))
    # Error: Each result must equal the first result:
    # `cs_for(1:100)` does not equal `cs_apply(100)`
    
  2. 请注意,您的基准测试具有代表性:您正在查看单个数据点(长度为 100 的输入),并且它是一个 tiny 输入大小。结果很可能受噪声影响,即使重复测量也是如此。

  3. 始终使用几种不同的输入大小进行基准测试,以了解渐近增长。事实上,你的问题“多少次”一种方法比另一种方法快甚至没有意义,因为不同的函数没有相同的渐近行为:两个是线性的,一个是二次的。要看到这一点,您 必须 反复测量。 “microbenchmark”不支持开箱即用,但“bench”支持(但你当然可以手动完成)。

  4. 要执行apples-to-apples比较,cumsum()基准也应该包含在一个函数中,并且所有函数都应该接受相同的参数(我建议n, 输入向量的大小).

将所有这些放在一起,并使用“bench”包,我们得到以下结果。我还使用“vapply”添加了一个基准测试,因为它通常优于 sapply:

cs_for = function (n) {
  x = 1 : n
  for (i in x) {
    if (i == 1L) {
      xc = x[i]
    } else {
      xc = c(xc, sum(x[1 : i]))
    }
  }
  xc
}

cs_apply = function (n) {
  sapply(1 : n, \(x) sum(1 : x))
}

cs_vapply = function (n) {
  vapply(1 : n, \(x) sum(1 : x), integer(1L))
}

cs_cumsum = function (n) {
  cumsum(1 : n)
}

results = bench::press(
    n = 10L ^ (1 : 4), # from 10 to 10,000
    bench::mark(cs_for(n), cs_apply(n), cs_vapply(n), cs_cumsum(n))
)

由于数据科学的第一条规则是“可视化你的结果”,这里有一个情节:

results |>
  mutate(label = reorder(map_chr(expression, deparse), desc(median))) |>
  ggplot() +
  aes(x = n, y = median, color = label) +
  geom_line() +
  geom_point() +
  bench::scale_y_bench_time() +
  scale_x_log10()

...如您所见,并非所有的线都线性增加。事实上,cs_forcs_cumsum都增加了super-linearly。 cs_forquadratic runtime 因为逐步附加到 xc 向量需要分配新存储并复制旧值。 applyvapply 都避免了这种情况。当使用 for by pre-allocating 你的结果向量时,你也可以避免这种情况。 cumsum 表现出 super-linear 运行时的事实实际上让我感到惊讶 - 它绝对不应该那样做!这值得进一步调查。