首页 > 解决方案 > 为什么循环计算更快?

问题描述

更新:

正如@Dave2e 所指出的,将start_time语句(在代码 2 中)移出for循环将使运行时间与代码 1 相当。即:

start_time <- Sys.time()
for (j in 1:1) {
   n <- 100
   result <- 1
   for (i in 1:n) {
     result <- result * i
   }
   result
   ## [1] 9.332622e+157
   end_time <- Sys.time()
}
end_time - start_time

for循环真的提高了性能还是假的?


原帖:

我有两段代码如下:

代码 1:

start_time <- Sys.time()
n <- 100
result <- 1
for (i in 1:n) {
   result <- result * i
}
result
## [1] 9.332622e+157
end_time <- Sys.time()
end_time - start_time

代码 2:

for (j in 1:1) {
   start_time <- Sys.time()
   n <- 100
   result <- 1
   for (i in 1:n){
      result <- result * i}
      result
      ## [1] 9.332622e+157
      end_time <- Sys.time()
   }
   end_time - start_time

我期待这两个代码运行相似,但代码 2 的运行速度始终比代码 1 快得多。在我的计算机上,代码 1 大约需要 10^-2 秒,而代码 2 大约需要 5*10^-6 秒。关于如何发生这种情况的任何见解?如果只是for在整个代码中添加循环可以减少程序运行时间,那么我将来会在我的所有代码中使用它。

标签: rperformancefor-looptime

解决方案


我认为您的比较不是很可靠。如果不多次运行以获得平均值,就很难说出非常快的代码的相对时间 - 太多的不可控因素会稍微改变运行时间。

我从下面的基准测试中得出的结论是,在冗余 for 循环中封装一个相当微不足道的计算并没有太大的伤害,但任何明显的优势都是微不足道的,可能只是噪音的影响。

我通过将每个代码块封装在一个函数 ( with_loopand without_loop)function() { ... }中。(请注意,这意味着我的时间不是基于您的Sys.time()比较,而是基于包中的内置时间microbenchmark。)

microbenchmark软件包更适合基准测试,尤其是非常短的计算任务:来自?microbenchmark::microbenchmark

'microbenchmark' 可以更准确地替代常见的 'system.time(replicate(1000, expr))' 表达式。它试图准确测量评估“expr”所需的时间。为了实现这一点,使用了大多数现代操作系统提供的亚毫秒(假定为纳秒)精确计时功能。此外,所有表达式的计算都是在 C 代码中完成的,以最大限度地减少任何开销。

library(microbenchmark)
m1 <- microbenchmark(with_loop, without_loop)
library(ggplot2)
autoplot(m1)+scale_y_log10()

分位数(lq、中位数、uq)实际上是相同的。

Unit: nanoseconds
         expr min lq   mean median uq   max neval cld
    with_loop  36 38  48.56     39 40   972   100   a
 without_loop  36 39 177.81     40 41 13363   100   a

在此处输入图像描述

平均而言,没有循环的代码确实更慢(即它的平均值更大),但这几乎完全是由几个异常值驱动的。

现在只关注小于 50 纳秒的值:

autoplot(m1)+scale_y_log10(limits=c(NA,50))

在此处输入图像描述

如果我们用times=1e6(一百万次迭代)再次这样做,我们会得到几乎相同的结果:循环的平均值快 3纳秒(同样可能几乎完全由上尾的小波动驱动)。

Unit: nanoseconds
         expr min lq     mean median uq     max neval cld
    with_loop  32 39 86.44248     41 61 2474675 1e+06   a
 without_loop  35 39 89.86294     41 61 2915836 1e+06   a

如果您需要运行循环十亿次,这将对应于运行时间的 3 秒差异。可能不值得担心。


推荐阅读