定时器:为 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上运行后,查询没有异常.可是在修改操作提交时,产生异常:提 ...
随机推荐
- Demo_CS(移动,切换枪支,发射子弹)
using UnityEngine; using System.Collections; public class Gun : MonoBehaviour { private Animator ani ...
- Cocostudio学习笔记(1) 扯扯蛋 + 环境搭建
转眼七月份就到了,2014已经过了一半,而我也最终算是有"一年工作经验"了,开心ing. 回想这一年Cocos2dx的游戏开发经历,去年下半年重心主要在游戏的逻辑上,而今年上半年重 ...
- 笔试之STL
1. map是如何实现的?它的keys是否经过排序?如何实现它的clear方法? A 实现: map是通过红黑树来实现的,keys是经过排序的: map的所有元素都是pair,同时拥有实值(value ...
- Sublime Text插件之Emmet
转载:http://www.w3cplus.com/tools/using-emmet-speed-front-end-web-development.html Emmet插件以前被称作为Zen Co ...
- php 写model层
<?php /** * @author Administrator * */ class User { private $id; private $admin; private $paw; pr ...
- 创建一个流(Stream)可以让Bitmap或Image保存到流里面(转)
创建一个流(Stream)可以让Bitmap或Image使用save方法将已经在bitmap上生成的图像 保存到流里面?不需要直接在硬盘上生成文件 -------------------------- ...
- scn转换为十进制
- 【USACO 1.5.1】数字金字塔
[题目描述] 观察下面的数字金字塔. 写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大.每一步可以走到左下方的点也可以到达右下方的点. 7 3 8 8 1 0 2 7 4 4 4 ...
- 字符串转换成JSON(js)
JSON.parse('{"site":"zlog"}'); 使用JSON.parse需严格遵守JSON规范, 属性都需用双引号引起来, 一定是双引号!! 相反 ...
- MySQL主从复制详细部署过程
环境介绍: 采用多实例进行主从复制测试,多实例方法请参考网上其它文档,其实多实例和双服务器对于测试环境来说是一样的. 当前采用3306端口进程为Master,3307端口进程为Slave. ...
概要