Correctly measure time duration in Go

huangapple go评论102阅读模式
英文:

Correctly measure time duration in Go

问题

在Go语言中,精确测量时间持续时间的正确方法是使用标准的time包和以下方法:

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

然而,time.Now()返回的是当前系统时间,这会导致两个问题:

  1. 如果在测量期间系统时间发生变化(例如由于时区变更(夏令时)或闰秒),得到的持续时间也是错误的。
  2. 系统时间可能故意比实际时间快或慢。当操作系统将内部时钟与NTP时间服务器同步时(可能每小时发生多次),就会发生这种情况。

根据MSDN

> [时间服务]调整本地时钟速率,使其逐渐接近正确时间。如果本地时钟与[准确时间样本]之间的时间差太大,无法通过调整本地时钟速率来纠正,时间服务将将本地时钟设置为正确时间。

如果系统时间发生变化(无论是手动更改还是由于夏令时),可能可以检测到无效的持续时间并丢弃它。但是,如果系统时钟以比世界时间快10%的速度运行以进行同步,几乎不可能检测到。这是预期的行为,也是系统时钟的设计方式。

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

在Go语言中,精确测量执行时间的正确方法是使用time.Since()函数来计算时间间隔。

英文:

What is the correct way to precisely measure a time duration in Go?
Most application just use the standard time package and the following approach:

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

However, time.Now() returns the current system time, which leads to two flaws:

  1. If the system time is changed during the measurement (for example due to a time zone change (DST) or a leap second), the resulting duration is also wrong.
  2. The system time can tick deliberately faster or slower than the real time. This always happens when the operating system synchronizes the internal clock with NTP time servers (which might happen several times an hour!)

From MSDN:

> [The time service] adjusts the local clock rate to allow it to
> converge toward the correct time. If the time difference between the
> local clock and the [accurate time sample] is too large to correct by
> adjusting the local clock rate, the time service sets the local clock
> to the correct time.

If the system time changes (either manually or due to DST), it might be possible to detect the invalid duration and discard it. But if the system clock ticks e.g. 10% faster to synchronize with world-time, it is practically impossible to detect. That's intended behaviour and how the system clock is designed.

For that reason, most other languages offer a dedicated API for measuring durations:

What is the correct way to precisely measure execution time in Go?

答案1

得分: 44

时间包

单调时钟

操作系统提供了“墙钟”和“单调时钟”两种时钟。墙钟受到时钟同步的影响,而单调时钟则不受影响。一般规则是,墙钟用于显示时间,而单调时钟用于测量时间。在这个包中,time.Now返回的Time类型包含墙钟读数和单调时钟读数;后续的时间显示操作使用墙钟读数,而后续的时间测量操作,比如比较和减法,使用单调时钟读数。

例如,下面的代码始终计算出大约20毫秒的正值时间间隔,即使在计时操作期间墙钟发生了变化:

start := time.Now()
... 需要20毫秒的操作 ...
t := time.Now()
elapsed := t.Sub(start)

其他习惯用法,比如time.Since(start),time.Until(deadline)和time.Now().Before(deadline),同样可以抵御墙钟重置的影响。

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

参见提案:Go中的单调经过时间测量

英文:

> Package time
>
> Monotonic Clocks
>
> Operating systems provide both a “wall clock,” which is subject to
> changes for clock synchronization, and a “monotonic clock,” which is
> not. The general rule is that the wall clock is for telling time and
> the monotonic clock is for measuring time. Rather than split the API,
> in this package the Time returned by time.Now contains both a wall
> clock reading and a monotonic clock reading; later time-telling
> operations use the wall clock reading, but later time-measuring
> operations, specifically comparisons and subtractions, use the
> monotonic clock reading.
>
> For example, this code always computes a positive elapsed time of
> approximately 20 milliseconds, even if the wall clock is changed
> during the operation being timed:
>
> start := time.Now()
> ... operation that takes 20 milliseconds ...
> t := time.Now()
> elapsed := t.Sub(start)
>
> Other idioms, such as time.Since(start), time.Until(deadline), and
> time.Now().Before(deadline), are similarly robust against wall clock
> resets.

Starting with Go 1.9 (released August 24, 2017), Go uses a monotonic clock for durations.

See Proposal: Monotonic Elapsed Time Measurements in Go.

答案2

得分: 8

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

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

操作系统提供了“墙钟”和“单调钟”,墙钟受到时钟同步的影响,而单调钟则不受影响。一般规则是,墙钟用于显示时间,而单调钟用于测量时间。在这个包中,time.Now返回的Time对象包含墙钟读数和单调钟读数;后续的时间显示操作使用墙钟读数,而后续的时间测量操作(比如比较和减法)使用单调钟读数。

例如,下面的代码始终计算出大约20毫秒的正值时间间隔,即使在计时操作期间墙钟发生了变化:

start := time.Now()
... 需要20毫秒的操作 ...
t := time.Now()
elapsed := t.Sub(start)

其他习惯用法,如time.Since(start),time.Until(deadline)和time.Now().Before(deadline),也同样能够在墙钟重置时保持稳健。

对time pkg的这一更改是由这个问题触发的,该问题促使Russ Cox提出了这个更改的提案:

比较和减法操作观察到的time.Now时间可能会返回不正确的结果,如果系统墙钟在两次观察之间被重置。我们建议扩展time.Time表示形式,以保存额外的单调钟读数,用于这些计算。除了其他好处外,这应该使使用time.Now和time.Since进行基本经过时间测量的负值持续时间或其他不现实结果的报告变得不可能。

英文:

This is available in Go 1.9 (August 2017) with monotonic clocks, you won't have to do anything special to benefit from it:

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

> Operating systems provide both a “wall clock,” which is subject to
> changes for clock synchronization, and a “monotonic clock,” which is
> not. The general rule is that the wall clock is for telling time and
> the monotonic clock is for measuring time. Rather than split the API,
> in this package the Time returned by time.Now contains both a wall
> clock reading and a monotonic clock reading; later time-telling
> operations use the wall clock reading, but later time-measuring
> operations, specifically comparisons and subtractions, use the
> monotonic clock reading.
>
> For example, this code always computes a positive elapsed time of
> approximately 20 milliseconds, even if the wall clock is changed
> during the operation being timed:

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

> Other idioms, such as time.Since(start), time.Until(deadline), and
> time.Now().Before(deadline), are similarly robust against wall clock
> resets.

This change to the time pkg was triggered by this issue, which prompted this proposal for change from Russ Cox:

> Comparison and subtraction of times observed by time.Now can return
> incorrect results if the system wall clock is reset between the two
> observations. We propose to extend the time.Time representation to
> hold an additional monotonic clock reading for use in those
> calculations. Among other benefits, this should make it impossible for
> a basic elapsed time measurement using time.Now and time.Since to
> report a negative duration or other result not grounded in reality.

答案3

得分: 6

对于Go 1.8及之前的版本,正确的计时函数不在time包中,而是在runtime包中:

func nanotime() int64

为了正确测量执行时间,应该按照以下步骤进行:

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

不幸的是,这个方法本身没有很好的文档说明。它在这个问题的讨论中出现,经过了长时间的讨论,讨论是否真的有必要。

对于Go 1.9及更新的版本,请参考Kenny Grant的回答。

英文:

For Go 1.8 and before, the correct timing function is not inside the time package, but instead in the runtime package:

func nanotime() int64

In order to correctly measure execution time, the following procedure should be used:

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

Unfortunately, the method itself is not documented very well. It emerged in this issue after a long discussion if it was really neccessary.
For Go 1.9 and newer, refer to Kenny Grant's answer.

huangapple
  • 本文由 发表于 2017年8月21日 15:13:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/45791241.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定