定时器:为 Windows 实现一个连续更新,高精度的时间供应器
翻译:lxhui 原文出处:MSDN Magazine March 2004(Timers...) 原代码下载: HighResolutionTimer.exe (404KB) 本篇文章假定你熟悉 C++ 和 Win32 API 概要 你为什么会对获得小于1毫秒精度的系统时间感兴趣?在我工作期间,我发现有必要去确定我的进程里不同线程执行引发的事件的顺序。还需要把这些事件同绝对时间相 关联,但注意到系统时间的实际精度是不会超过10毫秒粒度的。 在本文随后的内容中,我将解释该系统时间精度的限制,解决的步骤,以及某些一般缺陷。例子程序的实现可以从本文开始链接处下载。这些文件的源代码是在 Visual C++? 7.1 和 Windows? XP 专业版下编写测试的。在编写本文时,我频繁地提到 Windows NT® 操作系统家族(Windows NT 4.0, Windows 2000, 或者 Windows XP)产品,而不是某一个特定的版本。 本文中用到的 Win32? APIs 的参数类型及用法,参见 MSDN library/Platform SDK 文档。 究竟谁有这样的需求? 20:12:23.479 正如你所看到的,我所能得到的最好的精度是15毫秒,这是 Windows NT 时钟周期的长度。每过一个时钟周期,Windows NT都会更新系统时间。Windows NT调度器也会 突然启动并可能选择一个新的线程来执行。关于这方面的更多信息,请看《Inside Windows 2000》第三版(Microsoft Press®, 2000),作者是 David Solomon 和 Mark Russinovich。 最初的尝试 ... 尽管它看起来非常成功,但这个实现却有几个问题:同步实现(函数被命名为 "simplistic_synchronize"的一个很好的理由);QueryPerformanceFrequency 报告的频率 ;系统时间变化缺乏保护。在接下来的章节中,我们会考虑这些问题的一些可能的改进。 实现同步的可靠方法 ::GetSystemTimeAsFileTime(&ft1); 大多时候只要满足下面的条件,这个过分单纯化的同步函数还是成功的:
|
作者简介 Johan Nilsson是在 Esrange 的瑞士空间公司的一个系统工程师,位于北极圈之上。自从Windows NT 4.0发布以来他就一直使用C++为Windows NT开发软件,从Windows 3.1起为Windows/DOS编程。和他联系:johan.nilsson@esrange.ssc.se |
本文由 VCKBASE MTT翻译
Figure 1 获得和输出系统时间
#include <windows.h>
#include <iostream>
#include <iomanip> int main(int argc, char* argv[])
{
SYSTEMTIME st;
while (true)
{
::GetSystemTime(&st);
std::cout << std::setw(2) << st.wHour << ':'
<< std::setw(2) << st.wMinute << ':'
<< std::setw(2) << st.wSecond << '.'
<< std::setw(3) << st.wMilliseconds << '/n';
} return 0;
}
Figure 2 初始尝试
#include <windows.h>
#include <iostream>
#include <iomanip> struct reference_point
{
FILETIME file_time;
LARGE_INTEGER counter;
}; void simplistic_synchronize(reference_point& ref_point)
{
FILETIME ft0 = {0, 0},
ft1 = {0, 0};
LARGE_INTEGER li; //
// Spin waiting for a change in system time. Get the matching
// performace counter value for that time.
//
::GetSystemTimeAsFileTime(&ft0);
do
{
::GetSystemTimeAsFileTime(&ft1);
::QueryPerformanceCounter(&li);
}
while((ft0.dwHighDateTime == ft1.dwHighDateTime) &&
(ft0.dwLowDateTime == ft1.dwLowDateTime)); ref_point.file_time = ft1;
ref_point.counter = li;
} void get_time(LARGE_INTEGER frequency, const reference_point&
reference, FILETIME& current_time)
{
LARGE_INTEGER li; ::QueryPerformanceCounter(&li); //
// Calculate performance counter ticks elapsed
//
LARGE_INTEGER ticks_elapsed; ticks_elapsed.QuadPart = li.QuadPart -
reference.counter.QuadPart; //
// Translate to 100-nanosecondsintervals (FILETIME
// resolution) and add to
// reference FILETIME to get current FILETIME.
//
ULARGE_INTEGER filetime_ticks,
filetime_ref_as_ul; filetime_ticks.QuadPart =
(ULONGLONG)((((double)ticks_elapsed.QuadPart/(double)
frequency.QuadPart)*10000000.0)+0.5);
filetime_ref_as_ul.HighPart = reference.file_time.dwHighDateTime;
filetime_ref_as_ul.LowPart = reference.file_time.dwLowDateTime;
filetime_ref_as_ul.QuadPart += filetime_ticks.QuadPart; //
// Copy to result
//
current_time.dwHighDateTime = filetime_ref_as_ul.HighPart;
current_time.dwLowDateTime = filetime_ref_as_ul.LowPart;
} int main(int argc, char* argv[])
{
reference_point ref_point;
LARGE_INTEGER frequency;
FILETIME file_time;
SYSTEMTIME system_time; ::QueryPerformanceFrequency(&frequency);
simplistic_synchronize(ref_point);
while (true)
{
get_time(frequency, ref_point, file_time);
::FileTimeToSystemTime(&file_time, &system_time);
std::cout << std::setw(2) << system_time.wHour << ':'
<< std::setw(2) << system_time.wMinute << ':'
<< std::setw(2) << system_time.wSecond << ':'
<< std::setw(3) << system_time.wMilliseconds << '/n';
} return 0;
}
Figure 7 Time_provider 参数和成员
模板参数 |
---|
counter_type 代表高精度,高频率的计数器。它必须提供静态成员值和频率,同value_type定义一样。 KEEP_WITHIN_MICROS 定义时间供应器最大可以偏离实际系统时间的微秒个数。它也影响再同步线程的同步频率。 SYNCHRONIZE_THREAD_PRIORITY 定义同步线程在执行同步时应该设置的自身优先级。这个不应该被修改除非你的程序不断的在一个高优先级上执行。缺省的是THREAD_PRIORITY_BELOW_NORMAL,这样不会打扰正常或高优先级线程的正常执行。 TUNING_LIMIT_PARTSPERBILLION 当前时间供应器的实现是连续的测量计数器频率。这个频率在内部被维护,允许较少频率的再同步和更准确的定时。当测量的频率的精确度达到一定阈值时,就不会再执行调整(但周期性再同步总是活动的)。这个极限的单位是计算频率的错误比率,对应的缺省值是每10亿100单位。 MAX_WAIT_MILLIS 定义允许的最大调谐间隔,毫秒为单位——也就是,检查高精度时间偏离系统时间有多远前的等待时间。调谐间隔是自动调整的,但只能达到这个极限。这个参数一般不应该被修改。 MIN_WAIT_MILLIS 定义最小允许的调谐间隔,毫秒为单位。细节见MAX_WAIT_MILLS |
类型定义 |
raw_value_type 能够存储“原始”时戳的类型 |
成员函数 |
instance 返回这个类的唯一实例的引用 systemtime返回当前的系统时间,格式是SYSTEMTIME结构 filetime 返回当前系统时间,格式是FILETIME结构 rawtime 返回当前系统时间,用最小的负荷返回“原始”时戳。为了把它转为绝对时间使用filetime_from_rawtime或者systemtime_from_rawtime systemtime_from_rawtime 把“原始”时戳转为绝对时间,用SYSTEMTIME结构表示 filetime_from_rawtime 把“原始”时戳转为绝对时间,用FILETIME结构表示 |
Figure 8 使用time_provider类
#include <hrt/performance_counter.hpp>
#include <hrt/time_provider.hpp>
#include <hrt/system_time.hpp>
#include <vector>
#include <iostream>
#include <iomanip> using namespace hrt; typedef time_provider<performance_counter> time_provider_type;
typedef time_provider_type::raw_value_type raw_time_type;
typedef std::vector<raw_time_type> raw_vector; const int NUMBER_OF_SAMPLES = 1000; int main(int argc, char* argv[])
{
raw_vector samples;
time_provider_type& provider = time_provider_type::instance(); samples.reserve(NUMBER_OF_SAMPLES); for (int i = 0; i < NUMBER_OF_SAMPLES; ++i)
{
samples.push_back(provider.rawtime());
} system_time st; for (raw_vector::iterator iter = samples.begin();
iter != samples.end(); ++iter)
{
provider.systemtime_from_rawtime(*iter, st.pointer());
std::cout << std::setfill('0')
<< std::setw(2) << st.hour() << ':'
<< std::setw(2) << st.minute() << ':'
<< std::setw(2) << st.second() << '.'
<< std::setw(3) << st.millis() << '/n';
} return 0;
}
Figure 9 Win32 时间函数和性能
Win32 API | 执行时间 | time_provider | 执行时间 |
---|---|---|---|
GetSystemTimeAsFileTime | 1.9% (~0%) | filetime | 135% (900%) |
GetSystemTime | 100% (100%) | systemtime | 234% (1001%) |
QueryPerformanceCounter | 55% (400%) | rawtime | 57% (400%) |
同步:有多好?
使用我在文中描述的同步方法,你可以指定你想要的结果精度。然而,实际上,你能得到的结果的质量有平台相关性(硬件和软件)限制。在 Windows NT 中时钟中断处理器需要花费时间来执行,大大地限制了你的精度不可能优于时钟中断处理器的执行时间,加上线程上下文切换时间,还有当时间变化时调用函数进行检查所花的时间。如果你在对称多处理(SMP)机器上运行,你可以通过在另一个 CPU 上运行同步线程来避免时钟中断问题。
在 SMP 机器上禁止同步线程运行在处理时钟中断的 CPU 上可以产生数十倍差异的同步精度。唯一的问题是你要首先知道哪个 CPU 在处理实际的时钟中断。从我有限的经验来看我只能告诉你好像是CPU#0来处理(我想这种感觉有些怪怪的)。假设这是真的,你可以仅仅使用 SetThreadAffinityMask API 从允许处理器的线程列表中移去 CPU#0。你应该通过预先检查 GetProcessAffinityMask 的调用结果来确认该进程被允许在另一个处理器上运行。
http://blog.csdn.net/jiangxinyu/article/details/2728416
最新文章
- iOS系列 基础篇 08 文本与键盘
- Express4+Mongodb超简单入门实例
- Xcode 8 Swift 类似插件方法
- HTTP权威指南阅读笔记二:URL与资源
- java几道简单的面试题目
- 深入理解PHP原理之变量分离/引用
- 【转】图片IMG标记的alt属性和title属性的使用
- sql server触发器的例子
- C#和asp.net中链接数据库中 参数的几种传递方法
- 【剑指offer】面试题30:最小的 k 个数
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(88)-Excel导入和导出-主从表结构导出
- Spring怎么引入多个xml配置文件
- JS实现图片&#39;&#39;推拉门&#39;&#39;效果
- 浅谈_依赖注入 asp.net core
- Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?
- tcpdump高级过滤
- mysql5.6.40单实例安装二进制快捷安装
- 自windows8以后,所有版本(专业版、企业版、旗舰版)都支持从 vhd 启动
- priority_queue<;int>;q;
- Supported Values for @SuppressWarnings(转)