maja
maja

Reputation: 18034

Correctly measure time duration in Go

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?

Upvotes: 38

Views: 18769

Answers (3)

Kenny Grant
Kenny Grant

Reputation: 9623

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.

Upvotes: 9

peterSO
peterSO

Reputation: 166529

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.

Upvotes: 47

maja
maja

Reputation: 18034

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.

Upvotes: 6

Related Questions