【问题标题】:Why is the computation faster with a loop?为什么循环计算更快?
【发布时间】:2020-05-11 05:49:24
【问题描述】:

更新:

正如@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循环可以减少程序运行时间,我会在我的所有代码中使用它。

【问题讨论】:

  • 在代码 2 中将 start_time 语句移到“for”循环之外,然后时间是可比较的。 for 语句必须在实际执行之前发送一个信号来预评估循环。对 R 有更深入了解的人可能会提供更好的答案。
  • “for 循环是真的提高了性能还是假的?” 让情况变得更糟!这是可以理解的,因为您正在运行一些额外的东西。
  • 在做这件事之前,我的想法和你一模一样。但是上面例子中的运行时间让我很困惑,我不知道 R 的实现方式是否使用循环改变了它的运行方式,从而节省了某些计算的时间?

标签: r performance for-loop time


【解决方案1】:

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

我从下面的基准中得出的结论是,在冗余的 for 循环中封装相当微不足道的计算并不会造成太大的伤害,但任何明显的优势都是微不足道的,可能只是一种效果噪音。

我将您的每个代码块封装在一个函数中(with_loopwithout_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 秒的运行时间差异。可能不值得担心。

【讨论】:

  • 您的模拟结果很有帮助。基于这些,我认为可以安全地得出结论,使用“for”循环包含整个代码不会损害计算时间,相反,它有时会改进它。如果您可以在第二次模拟中发布运行时间的标准偏差,那将会更有趣。我想知道 3 纳秒是否重要。
  • 在我的研究中,我经常需要运行 1e6 次迭代的模拟,这很可能会遇到噪音。如果我只运行一两次迭代,我不会关心这些差异。从逻辑上讲,我从没想过添加一个无意义的循环可能会改变任何事情,直到我意外发现发布的情况。我不熟悉 R 的设计,但在将作业提交到集群之前,我会考虑使用“for”循环来包含所有内容作为比较计算时间的选项。
  • 对于一百万次迭代,每次迭代平均 3 纳秒的差异对应于整个运行的 0.001 秒的差异。在我看来,您担心这种差异是在浪费时间。
  • 你知道为什么使用 microbenchmark 和 Sys.time 会得到如此不同的结果吗?
  • 微基准所做的一些花哨的东西(见编辑)和单个评估通常非常嘈杂的事实。这些比较是基于您的“原始”代码还是“调整后”代码? 0.01 秒很容易就是您的计算机切换状态并将 R 移动到队列顶部所需的时间
猜你喜欢
  • 2020-12-25
  • 1970-01-01
  • 2019-05-30
  • 2021-11-28
  • 2017-04-10
  • 1970-01-01
  • 2017-04-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多