如何正确地计算经过时间(elapsed time)
假设我们要计算一段代码的执行时间,你会怎么做?在 Java 中,我们通常会这么做:
在绝大部分情况下,这样做是没有任何问题的,但如果我们遇到了时钟回拨,就可能出现elaspedTime
为负数的情况,这是为什么?什么是始终回拨?
在解释时钟回拨前,我们先来了解下计算机中的时钟。每台计算机都有一个硬件设备(通常是石英晶体振荡器)用来计算时钟,但这些设备计算出来的时钟时间并不是准确的,就会导致有的机器时间快一点,有的机器时间慢一点,为了保证机器间的时间同步,每个机器都会通过 NTP(网络时间协议)与专用的时间服务器进行同步。
计算机中有两种时钟:日历时钟(time-of-day clock)和单调时钟(monotonic clock),二者都是用来衡量时间的,但却有着不同的目的。
日历时钟
日历时钟也叫挂钟时间(wall-clock time),是根据某个日历返回当前日期和时间,比如 Linux 中的 clock_gettime 和 Java 中的 System.currentTimeMillis() 返回自 epoch(UTC 时间 1970 年 1 月 1 日午夜))以来的毫秒数。
日历时钟通常需要和 NTP 时间服务器同步,来修正自己的时间。如果本地时间比服务器时间慢,就把时间往前调;如果本地时间快于服务器时间,就把时间往后调,这就是时钟回拨。不管是往前调还是往后调,都会导致时间跳跃。
单调时钟
单调时钟的时间是始终往前走的,不会往前或往后调,因此单调时钟适用于测量持续时间(经过时间),比如服务器响应时间、代码执行时间等。
Linux 上的 clock_gettime,和 Java 中的 System.nanoTime() 都是单调时钟。
现在我们回到开头的问题,如何计算一段代码的执行时间?正确的做法是用单调时钟,而不是日历时钟。以 Java 为例,我们应该这么做:
如何模拟时钟回拨
以 Java 语言为例,运行下面这段代码
睡眠的目的是让我们有足够的时间去修改本地电脑的时间,模拟时钟回拨,如果在 t1 后将系统时间修改到过去某个时间,程序运行结束后,你会看到 elaspedTime 是一个小于 0 的值。
如果你把 System.currentTimeMillis() 换成 System.nanoTime(),不管你怎么调整时间,elaspedTime 永远是一个略大于 10s 的数值。
版权声明: 本文为 InfoQ 作者【redcoder54】的原创文章。
原文链接:【http://xie.infoq.cn/article/1b6051ffc7c63ff910c12f2c9】。文章转载请联系作者。
评论