定时器:为 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
定时器:为 Windows 实现一个连续更新,高精度的时间供应器的更多相关文章
- 安装Office时出现windows installer服务不能更新一个或多个受保护的windows文件错误的解决方法
今天在Windows XP上安装Microsoft Office 2010时,总是遇到“Windows Installer服务不能更新一个或多个受保护的windows文件,安装失败,正在回滚更改”提示 ...
- office2010安装出错,windows installer服务不能更新一个或多个受保护的windows文件
转自:http://www.08lr.cn/article/1985.html office2010安装过程中出现如下图错误:windows installer 服务不能更新一个或多个受保护的wind ...
- 一个 C# 获取高精度时间类(调用API QueryP*)
如果你觉得用 DotNet 自带的 DateTime 获取的时间精度不够,解决的方法是通过调用 QueryPerformanceFrequency 和 QueryPerformanceCounter这 ...
- .NET一个线程更新另一个线程的UI(两种实现方法及若干简化)
Winform中的控件是绑定到特定的线程的(一般是主线程),这意味着从另一个线程更新主线程的控件不能直接调用该控件的成员. 控件绑定到特定的线程这个概念如下: 为了从另一个线程更新主线程的Window ...
- 如何在Windows应用商店中提交您的Windows 8.1 应用更新
翘首以盼的Windows 8.1 不负众望的与大家见面了,与此同时也带来了全新的应用商店,小伙伴儿们要赶紧升级系统啦! 今天给大家介绍下如何提交一个Windows 8.1 的应用,其实微软针对这次系统 ...
- C#使用oledb连接excel运行Insert Into语句出现“操作必须使用一个可更新的查询”的解决的方法
我错误发生时的环境:Windows 7,Framework 4.0,Microsoft Office 2007,VS2010,c# WinForm. 部分代码: string strConn = &q ...
- Windows 程序安装与更新方案: Clowd.Squirrel
我的Notion Clowd.Squirrel Squirrel.Windows 是一组工具和适用于.Net的库,用于管理 Desktop Windows 应用程序的安装和更新. Squirrel.W ...
- 记Windows的一个存在了十多年的bug
bug Windows有一个bug,持续了十多年,从Windows Visita开始(2007年),一直存在,直到Windows11(2021年)才修复(其实也不叫修复,后面我再具体说),而Windo ...
- Access提示“操作必须使用一个可更新的查询”的解决办法
问题:软件工程师开发了一个asp.net+access网站,本地调试增.删.改和查都没有异常.部署到服务器windows2008 R2的IIS上运行后,查询没有异常.可是在修改操作提交时,产生异常:提 ...
随机推荐
- typedef struct
突然忘了这玩意儿了..今天就来搞一发 typedef是类型定义的意思.typedef struct 是为了使用这个结构体方便.具体区别在于:若struct node {}这样来定义结构体的话.在申请n ...
- PHP学习之[第09讲]PHP 的 Mysql 数据库函数 (微型博客系统)
一.数据库函数: mysql_connect(数据库地址,用户名,密码) mysql_select_db(数据库名) mysql_set_chartset(‘编码’) //PHP5.2.3以后的函数 ...
- WebChat 清爽来袭 -- JavaChat系统Ⅳ
题记: WebChat 依然搭配的是 JavaChat 服务器,在不修改原来服务器代码的情况下进行使用终端的扩展. 此次项目的难点在于,Action/Servlet ...
- [置顶] 数据持久层(DAO)常用功能–通用API的实现
在Web开发中,一般都分3层. Controller/Action 控制层, Service/Business 服务层/业务逻辑层, Dao 数据访问层/数据持久层. 在学习和工作的实践过程中,我发现 ...
- Composite 组合模式
简介 <大话设计模式>一书中组合模式的定义为:将对象组合成[树]形结构以表示[部分-整体]的层次结构,组合模式使得用户对[单个对象]和对[组合对象]的使用具有一致性. ...
- 制作Android Demo GIF:程序演示效果GIF图录制
[转] 制作Android Demo GIF:程序演示效果GIF图录制 在平时写博客或者分享自己写的程序效果的时候经常需要做成GIF图,以下就是介绍几种常用的GIF录制方法: 一.录制工具 1.( ...
- (转)PHP数组的总结(很全面啊)
一.什么是数组数组就是一组数据的集合,把一系列数据组织起来,形成一个可操作的整体.数组的每个实体都包含两项:键和值. 二.声明数据在PHP中声明数组的方式主要有两种:一是应用array()函数声明数组 ...
- this、new、apply和call详解
讲解this指针的原理是个很复杂的问题,如果我们从javascript里this的实现机制来说明this,很多朋友可能会越来越糊涂,因此本篇打算换一个思路从应用的角度来讲解this指针,从这个角度理解 ...
- 【USACO 3.1.6】邮票
[描述] 已知一个N枚邮票的面值集合(如,{1分,3分})和一个上限K ——表示信封上能够贴K张邮票.计算从1到M的最大连续可贴出的邮资. 例如,假设有1分和3分的邮票:你最多可以贴5张邮票.很容易贴 ...
- CSS样式的面向对象思想(一)
今天来谈一谈CSS样式的面向对象写法.顾名思义,面向对象是高级语言具备的特性,主要是为了程序高可复用,解决模块之间的耦合关系,那么像CSS这样的脚本语言是否也可以使用面向对象的思想,来提高代码的可维护 ...