英文:
Correctly measure time duration in Go
问题
在Go语言中,精确测量时间持续时间的正确方法是使用标准的time包和以下方法:
var startTime = time.Now()
doSomeHardWork()
var duration = time.Since(startTime) // 或者:time.Now() - startTime
然而,time.Now()
返回的是当前系统时间,这会导致两个问题:
- 如果在测量期间系统时间发生变化(例如由于时区变更(夏令时)或闰秒),得到的持续时间也是错误的。
- 系统时间可能故意比实际时间快或慢。当操作系统将内部时钟与NTP时间服务器同步时(可能每小时发生多次),就会发生这种情况。
根据MSDN:
> [时间服务]调整本地时钟速率,使其逐渐接近正确时间。如果本地时钟与[准确时间样本]之间的时间差太大,无法通过调整本地时钟速率来纠正,时间服务将将本地时钟设置为正确时间。
如果系统时间发生变化(无论是手动更改还是由于夏令时),可能可以检测到无效的持续时间并丢弃它。但是,如果系统时钟以比世界时间快10%的速度运行以进行同步,几乎不可能检测到。这是预期的行为,也是系统时钟的设计方式。
因此,大多数其他编程语言都提供了专用的API来测量持续时间:
- Java有
System.nanoTime()
,System.currentTimeMillis()
相当于time.Now()
,但是是错误的。 - C#有
System.Diagnostics.Stopwatch
- Windows上的C/C++有
QueryPerformanceCounter
和QueryPerformanceFrequency
- C++11及更高版本有
std::chrono::steady_clock
,或者当其is_steady
成员常量为true
时有std::chrono::high_resolution_clock
- JavaScript有
performance.now()
,而使用new Date()
是错误的。
在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:
- 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.
- 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:
- Java has
System.nanoTime()
,System.currentTimeMillis()
would be equivalent totime.Now()
and is wrong - C# has
System.Diagnostics.Stopwatch
- C/C++ on Windows has
QueryPerformanceCounter
andQueryPerformanceFrequency
- C++11 and up have
std::chrono::steady_clock
, orstd::chrono::high_resolution_clock
when itsis_steady
member constant istrue
- JavaScript has
performance.now()
, while the use ofnew Date()
is wrong
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使用单调时钟来表示时间间隔。
英文:
> 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.
答案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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论