为什么mean()函数运行如此缓慢?

29

问题就在这里!我只是尝试进行了一些优化,找到了瓶颈所在。出于好奇,我尝试了这个:

t1 <- rnorm(10)
microbenchmark(
  mean(t1),
  sum(t1)/length(t1),
  times = 10000)

结果表明,使用mean()函数要比手动计算慢6倍以上!

这是由于mean()函数在调用Internal(mean)之前的代码开销较高还是C代码本身较慢引起的?为什么?有没有好的理由和使用场景?


1
我在这里简要地看到了讨论(第3节):http://rwiki.sciviews.org/doku.php?id=packages:cran:data.table#method_dispatch_takes_time - Frank
1
此外,在此博客文章的“标准和内部函数”下:http://www.noamross.net/blog/2013/4/25/faster-talk.html - Frank
值得一提的是,@hadley的回答也值得一看。https://dev59.com/E2Qo5IYBdhLWcg3wBLjy - mnel
4
因为它所使用的算法与sum(t)/length(t)不同。请参考https://dev59.com/Q2Mm5IYBdhLWcg3wG8HA。 - Karl Forner
2个回答

36
由于方法中的s3查找以及在mean.default中解析参数所需,导致了这个问题。(还有其他mean函数中的代码)
sum和length都是原始函数,因此速度很快(但你如何处理NA值?)
t1 <- rnorm(10)
microbenchmark(
  mean(t1),
  sum(t1)/length(t1),
  mean.default(t1),
  .Internal(mean(t1)),
  times = 10000)

Unit: nanoseconds
                expr   min    lq median    uq     max neval
            mean(t1) 10266 10951  11293 11635 1470714 10000
  sum(t1)/length(t1)   684  1027   1369  1711  104367 10000
    mean.default(t1)  2053  2396   2738  2739 1167195 10000
 .Internal(mean(t1))   342   343    685   685   86574 10000
mean 的内部位比 sum/length 还要快。详见 http://rwiki.sciviews.org/doku.php?id=packages:cran:data.table#method_dispatch_takes_time备份)了解更多细节(以及避免使用 .Internal 的 data.table 解决方案)。请注意,如果我们增加向量的长度,则基础方法最快。
t1 <- rnorm(1e7)
microbenchmark(
     mean(t1),
     sum(t1)/length(t1),
     mean.default(t1),
     .Internal(mean(t1)),
+     times = 100)

Unit: milliseconds
                expr      min       lq   median       uq      max neval
            mean(t1) 25.79873 26.39242 26.56608 26.85523 33.36137   100
  sum(t1)/length(t1) 15.02399 15.22948 15.31383 15.43239 19.20824   100
    mean.default(t1) 25.69402 26.21466 26.44683 26.84257 33.62896   100
 .Internal(mean(t1)) 25.70497 26.16247 26.39396 26.63982 35.21054   100

现在方法调度只是整个“时间”所需的一小部分。


不同长度的输入向量如何影响基准测试的扩展性? - A5C1D2H2I1M1N2O1R2T1
你有没有想过为什么(对于长向量而言)sum(t1)/length(t2)比其他所有方法都快? - Josh O'Brien
4
base::mean 在向量上执行两次遍历(在 C 语言级别),以校正数值精度问题(请参见 src/summary.c)。顺便说一句,data.table 优化的均值也这样做,以保留这个好用的特性并且与基本的均值函数完全相同。sum 是单次遍历,而 length 存储在向量头部(没有循环)中。 - Matt Dowle
@JoshO'Brien:请查看我的回答和链接的问题。 - Joshua Ulrich
感谢所有这些好的回答和评论!对于dispatch >> .default overhead >> internal以及与向量长度有关的变化进行了很好的分解。 - Antoine Lizée
显示剩余2条评论

24

mean 的速度比手动计算要慢,原因有以下几点:

  1. S3 方法派发
  2. NA 处理
  3. 错误校正

前两点已经解释过了。第三点在这里有讨论,基本上是为了校正浮点误差,mean 需要对向量进行两次遍历,而 sum 只需要一次。

请注意,由于这些精度问题,identical(sum(t1)/length(t1), mean(t1)) 可能会返回 FALSE

> set.seed(21); t1 <- rnorm(1e7,,21)
> identical(sum(t1)/length(t1), mean(t1))
[1] FALSE
> sum(t1)/length(t1) - mean(t1)
[1] 2.539201e-16

这是否也适用于rowMeanscolMeans - Ferdinand.kraft
@Ferdinand.kraft:我不确定“this”指的是什么,所以我无法回答“是”或“否”... :) 但是rowMeanscolMeans不进行任何错误校正。 - Joshua Ulrich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接