Welcome

首页 / 软件开发 / C++ / 为Windows实现一个连续更新,高精度的时间供应器

为Windows实现一个连续更新,高精度的时间供应器2011-01-20lxhui本篇文章假定你熟悉 C++ 和 Win32 API

概要

从 Windows NT 里获得的时间戳(Timestamp),根据你所使用的硬件,其最大精度为 10 到 15 毫秒。但是, 有时候你需要时间标签频繁事件时,获得更高的精度更能令人满意。举个例子,如果你要与线程打交道,或者以间隔不低于 10 毫秒的频率实现某些其它任务时该怎么办?为了获得更好的精度,建议的方法包括使用性能计数器和系统时间一起去计算更小的时间增量。然而使用性能计数器技术有其自身的问题。本文将揭示一种可行的途径来克服该方法固有的局限。

你为什么会对获得小于1毫秒精度的系统时间感兴趣?在我工作期间,我发现有必要去确定我的进程里不同线程执行引发的事件的顺序。还需要把这些事件同绝对时间相 关联,但注意到系统时间的实际精度是不会超过10毫秒粒度的。

在本文随后的内容中,我将解释该系统时间精度的限制,解决的步骤,以及某些一般缺陷。例子程序的实现可以从本文开始链接处下载。这些文件的源代码是在 Visual C++? 7.1 和 Windows? XP 专业版下编写测试的。在编写本文时,我频繁地提到 Windows NT® 操作系统家族(Windows NT 4.0, Windows 2000, 或者 Windows XP)产品,而不是某一个特定的版本。 本文中用到的 Win32? APIs 的参数类型及用法,参见 MSDN library/Platform SDK 文档。

究竟谁有这样的需求?

最近我用“Windows NT millisecond time resolution”作为关键字在 Internet 上搜索了一番, 得到了 400 多个满足条件的结果。其中大多数是讨论如何获得高于10毫秒精度的系统时间,或者是如何让一个线程的休眠时间小于10毫秒。本文我将专注于为什么获得一个高于10毫秒精度的系统时间 会如此困难。你可能认为用 GetSystemTime API 很容易解决问题,这个 API 函数返回一个SYSTEMTIME 结构,该结构包含一个 wMilliseconds 域,在 MSDN 文档中说它保存 当前的毫秒时间。但实际上并不象这么简单。那么用 GetSystemTimeAsFileTime 获取 100 纳秒的精度如何呢?就让我们从一个小试验 开始吧:尝试重复获取系统时间,将它格式化并输出到屏幕上(见 Figure 1 )。

我的目标不是纳秒,而仅是毫秒精度,它应该能够从 SYSTEMTIME 结构中判断。让我们看一下输出结果:

20:12:23.479
20:12:23.479
20:12:23.494
20:12:23.494
[...有很多被移去了...]
20:12:23.494
20:12:23.509
20:12:23.509
20:12:23.509
...

正如你所看到的,我所能得到的最好的精度是15毫秒,这是 Windows NT 时钟周期的长度。每过一个时钟周期,Windows NT都会更新系统时间。Windows NT调度器也会 突然启动并可能选择一个新的线程来执行。关于这方面的更多信息,请看《Inside Windows 2000》第三版(Microsoft Press®, 2000),作者是 David Solomon 和 Mark Russinovich。

如果你运行我刚才所示的代码,你也许会看到时间大约是每10毫秒更新一次。如果是那样,可能意味着你是在单处理器的机器上运行 Windows NT,其时钟周期通常为10毫秒。正如你所看到的, 在这种方法中,系统时间更新频率不够快,不足以成为一种为我所用的技术。下面我们就来尝试找一个解决方案。

最初的尝试

当你询问如何得到一个比10毫秒精度更好的系统时间时,你也许会得到下面这样的回答:使用性能计数器,并让性能计数器值和即时变化的系统时间同步。结合这些值来计算一个 精度极高的当前时间。Figure 2 显示了实现方法。

性能计数器是一个高精度的硬件计数器,它能高精确、低开销地计量一个短周期时间。我通过在一个紧凑循环内不断重复把性能计数器值和对应的系统时间进行同步,等待系统时间变化。当系统时间 以变,我就保存计数器的值及系统时间。

使用这两个值作为参考,就有可能计算出一个高精度的当前系统时间(详情见 Figure 2 中的get_time),看一下结果:

...
21:23:22.296
21:23:22.297
21:23:22.297
21:23:22.298
21:23:22.298
21:23:22.299
21:23:22.300
21:23:22.300
21:23:22.301
21:23:22.301
21:23:22.302
21:23:22.302
21:23:22.303
...

尽管它看起来非常成功,但这个实现却有几个问题:同步实现(函数被命名为 "simplistic_synchronize"的一个很好的理由);QueryPerformanceFrequency 报告的频率 ;系统时间变化缺乏保护。在接下来的章节中,我们会考虑这些问题的一些可能的改进。