【问题标题】:Correctly measure time duration in Go在 Go 中正确测量持续时间
【发布时间】:2017-08-21 07:13:37
【问题描述】:

在 Go 中精确测量持续时间的正确方法是什么? 大多数应用程序只是使用标准时间包和以下方法:

var startTime = time.Now()
doSomeHardWork()
var duration = time.Since(startTime) // or: time.Now() - startTime

但是time.Now()返回的是当前系统时间,这会导致两个缺陷:

  1. 如果系统时间在测量过程中发生变化(例如,由于时区变化 (DST) 或闰秒),则生成的持续时间也是错误的.
  2. 系统时间可以故意比实际时间更快或更慢。当操作系统将内部时钟与 NTP 时间服务器同步时,总是会发生这种情况(每小时可能会发生几次!)

    来自MSDN

    [时间服务] 调整本地时钟频率以使其能够 收敛到正确的时间。如果两者之间的时间差 本地时钟和 [accurate time sample] 太大而无法更正 调整本地时钟速率,时间服务设置本地时钟 到正确的时间。

如果系统时间更改(手动或由于 DST),可能会检测到无效的持续时间并将其丢弃。但是如果系统时钟滴答作响,例如与世界时间同步速度快 10%,几乎无法检测到。这是预期的行为以及系统时钟的设计方式。

因此,大多数其他语言都提供专用 API 来测量持续时间:

在 Go 中精确测量执行时间的正确方法是什么?

【问题讨论】:

  • 从技术上讲,performance.now() 是一种 DOM 功能,而不是 JavaScript。如果您正在运行 JS,例如在服务器上,您将拥有一些其他 API。
  • 如果您尝试分析某些代码的性能,使用 CPU 性能计数器来测量核心时钟周期的时间而不是挂钟时间通常很有用,因此 CPU 频率缩放可以差异较小。 (如果它受 CPU 限制,而不是内存限制(因为内存频率通常保持恒定),并且绝对不受 I/O 限制,这是有意义的。)性能计数器还可以帮助您找到 为什么 它需要时间,例如缓存未命中 vs. 分支错误预测 vs. 高延迟长依赖链 vs. 实际最大化 CPU 前端或执行单元吞吐量。
  • 例如在 Linux 上 perf record ./a.out,然后是 perf report

标签: go time


【解决方案1】:

Package time

单调时钟

操作系统提供了一个“挂钟”,它受制于 时钟同步的变化,以及“单调时钟”,即 不是。一般规则是挂钟是用来报时的 单调时钟用于测量时间。而不是拆分 API, 在这个包中,时间返回的时间。现在包含一堵墙 时钟读数和单调时钟读数;后来的时间告诉 操作使用挂钟读数,但后来测量时间 运算,特别是比较和减法,使用 单调时钟读数。

例如,此代码总是计算一个正的经过时间 大约 20 毫秒,即使挂钟已更改 正在计时的操作中:

start := time.Now()
... operation that takes 20 milliseconds ...
t := time.Now()
elapsed := t.Sub(start)

其他成语,如time.Since(start), time.Until(deadline), and time.Now().Before(deadline),对挂钟同样强大 重置。

从 Go 1.9(2017 年 8 月 24 日发布)开始,Go 使用单调时钟来表示持续时间。

Proposal: Monotonic Elapsed Time Measurements in Go

【讨论】:

  • 这是一个非常巧妙的 API 设计。比提供一个单独的函数并依靠程序员选择正确的函数(很多人不这样做)要好得多。
  • @maja:我添加了设计文档(提案)的链接。
  • 这不是说如果ab是时间,b - a + a != b就有可能吗?
  • 您可以使用elapsed := time.Since(start) 代替elapsed := t.Sub(start) 跳过第二次调用Now()
【解决方案2】:

这在带有单调时钟的 Go 1.9(2017 年 8 月)中可用,您无需执行任何特殊操作即可从中受益:

https://tip.golang.org/pkg/time/#hdr-Monotonic_Clocks

操作系统提供了一个“挂钟”,它受制于 时钟同步的变化,以及“单调时钟”,即 不是。一般规则是挂钟是用来报时的 单调时钟用于测量时间。而不是拆分 API, 在这个包中,时间返回的时间。现在包含一堵墙 时钟读数和单调时钟读数;后来的时间告诉 操作使用挂钟读数,但后来测量时间 运算,特别是比较和减法,使用 单调时钟读数。

例如,此代码总是计算一个正的经过时间 大约 20 毫秒,即使挂钟已更改 正在计时的操作中:

start := time.Now()
... operation that takes 20 milliseconds ...
t := time.Now()
elapsed := t.Sub(start)

其他成语,如time.Since(start), time.Until(deadline), and time.Now().Before(deadline),对挂钟同样强大 重置。

time pkg 的此更改是由此 issue 触发的,这促使此 proposal 从 Russ Cox 进行更改:

时间观察到的时间的比较和减法。现在可以返回 如果系统挂钟在两者之间重置,则结果不正确 观察。我们建议将 time.Time 表示扩展到 持有一个额外的单调时钟读数,用于那些 计算。除其他好处外,这应该使 使用 time.Now 和 time.Since 的基本经过时间测量 报告负持续时间或其他不基于现实的结果。

【讨论】:

    【解决方案3】:

    对于 Go 1.8 及之前版本,正确的计时函数不在时间包内,而是在runtime package

    func nanotime() int64
    

    为了正确测量执行时间,应使用以下程序:

    var startTime = runtime.nanotime()
    doSomeHardWork()
    var duration = runtime.nanotime() - startTime
    

    不幸的是,该方法本身并没有很好地记录。它在long discussion if it was really neccessary 之后出现在this issue 中。 对于 Go 1.9 及更高版本,请参阅 Kenny Grant 的回答。

    【讨论】:

    猜你喜欢
    • 2020-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-11
    • 1970-01-01
    • 2016-04-05
    相关资源
    最近更新 更多