C ++窗口时间

我在使用时间方面有问题。 我想在使用C ++的窗口上使用微秒。

我找不到路。

“规范”的答案是放松

一种流行的方法是使用QueryPerformanceCounter()调用。

然而这个方法有一些问题:

  1. 它的目的是测量时间间隔,而不是时间。 这意味着你必须编写代码来建立“纪元时间”,从中你将测量精确的时间间隔。 这就是所谓的校准
  2. 在校准时钟时,还需要定期对其进行调整,以避免与系统时钟不同步(这称为漂移 )。
  3. QueryPerformanceCounter没有在用户空间中实现; 这意味着需要上下文切换来调用实现的内核侧,并且这相对昂贵(大约0.7微秒)。 这似乎是支持传统硬件所必需的。

不过,并不是所有的东西都输了 要点1和2是你可以用一些编码做的事情,3,可以直接调用RDTSC(可以通过__rdtsc()在更新版本的Visual C ++中__rdtsc()__rdtsc() ,只要你知道准确的CPU时钟频率。 尽管在较老的CPU上,这样的调用会受到CPU内部时钟速度的变化的影响,但在所有较新的Intel和AMD CPU中,保证给出相当准确的结果,并且不会受到CPU时钟变化的影响(例如省电特性)。

让我们开始1.这里是数据结构来保存校准数据:

 struct init { long long stamp; // last adjustment time long long epoch; // last sync time as FILETIME long long start; // counter ticks to match epoch long long freq; // counter frequency (ticks per 10ms) void sync(int sleep); }; init data_[2] = {}; const init* volatile init_ = &data_[0]; 

这里是初始校准的代码; 必须给予时间(以毫秒为单位)等待时钟移动; 我发现500毫秒可以得到相当好的结果(时间越短,校准越不准确)。 为了进行callibration,我们将使用QueryPerformanceCounter()等。您只需要为data_[0]调用它,因为data_[1]将通过周期性时钟调整(下面)进行更新。

 void init::sync(int sleep) { LARGE_INTEGER t1, t2, p1, p2, r1, r2, f; int cpu[4] = {}; // prepare for rdtsc calibration - affinity and priority SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); SetThreadAffinityMask(GetCurrentThread(), 2); Sleep(10); // frequency for time measurement during calibration QueryPerformanceFrequency(&f); // for explanation why RDTSC is safe on modern CPUs, look for "Constant TSC" and "Invariant TSC" in // Intel(R) 64 and IA-32 Architectures Software Developer's Manual (document 253668.pdf) __cpuid(cpu, 0); // flush CPU pipeline r1.QuadPart = __rdtsc(); __cpuid(cpu, 0); QueryPerformanceCounter(&p1); // sleep some time, doesn't matter it's not accurate. Sleep(sleep); // wait for the system clock to move, so we have exact epoch GetSystemTimeAsFileTime((FILETIME*) (&t1.u)); do { Sleep(0); GetSystemTimeAsFileTime((FILETIME*) (&t2.u)); __cpuid(cpu, 0); // flush CPU pipeline r2.QuadPart = __rdtsc(); } while(t2.QuadPart == t1.QuadPart); // measure how much time has passed exactly, using more expensive QPC __cpuid(cpu, 0); QueryPerformanceCounter(&p2); stamp = t2.QuadPart; epoch = t2.QuadPart; start = r2.QuadPart; // calculate counter ticks per 10ms freq = f.QuadPart * (r2.QuadPart-r1.QuadPart) / 100 / (p2.QuadPart-p1.QuadPart); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); SetThreadAffinityMask(GetCurrentThread(), 0xFF); } 

有了良好的校准数据,你可以从廉价的RDTSC(我测量的电话和计算是在我的机器上约25纳秒)计算确切的时间。 有三件事要注意:

  1. 返回类型与FILETIME结构是二进制兼容的,并且精确到100ns,与GetSystemTimeAsFileTime (在10-30ms左右的间隔或1毫秒内增加)不同。

  2. 为了避免昂贵的转换整数倍到整数,整个计算是以64位整数进行的。 即使这些数字可以保持很大的数字,但仍然存在真正的整数溢出风险,因此必须定期提前start以避免出现这种情况。 这是在时钟调整完成的。

  3. 我们正在制作校准数据的副本,因为它可能在我们的调用期间通过另一个线程中的时钟调整进行了更新。

下面是以高精度读取当前时间的代码。 返回值与FILETIME二进制兼容,即1601年1月1日以来的100纳秒间隔。

 long long now() { // must make a copy const init* it = init_; // __cpuid(cpu, 0) - no need to flush CPU pipeline here const long long p = __rdtsc(); // time passed from epoch in counter ticks long long d = (p - it->start); if (d > 0x80000000000ll) { // closing to integer overflow, must adjust now adjust(); } // convert 10ms to 100ns periods d *= 100000ll; d /= it->freq; // and add to epoch, so we have proper FILETIME d += it->epoch; return d; } 

对于时钟调整,我们需要捕捉确切的时间(由系统时钟提供),并将其与我们的时钟进行比较; 这会给我们漂移的价值。 接下来我们用简单的公式来计算“调整好”的CPU频率,使我们的时钟在下一次调整时达到系统时钟。 因此,定期调用调整是非常重要的。 我发现,如果以15分钟的时间间隔进行呼叫,它就可以正常工作 我使用CreateTimerQueueTimer ,在程序启动时调用一次调度调整(这里没有演示)。

捕捉精确的系统时间(为了计算漂移)的一个小问题是,我们需要等待系统时钟的移动,这可能需要30毫秒左右(这是一个很长的时间)。 如果不进行调整,则会在函数now()内冒出整数溢出的风险,更不用说系统时钟的未校正偏移。 now()有内建的溢出保护,但是我们真的不想在错误的时刻调用now()的线程中同步触发它。

这里是周期性时钟调整的代码,时钟漂移在r->epoch - r->stamp

 void adjust() { // must make a copy const init* it = init_; init* r = (init_ == &data_[0] ? &data_[1] : &data_[0]); LARGE_INTEGER t1, t2; // wait for the system clock to move, so we have exact time to compare against GetSystemTimeAsFileTime((FILETIME*) (&t1.u)); long long p = 0; int cpu[4] = {}; do { Sleep(0); GetSystemTimeAsFileTime((FILETIME*) (&t2.u)); __cpuid(cpu, 0); // flush CPU pipeline p = __rdtsc(); } while (t2.QuadPart == t1.QuadPart); long long d = (p - it->start); // convert 10ms to 100ns periods d *= 100000ll; d /= it->freq; r->start = p; r->epoch = d + it->epoch; r->stamp = t2.QuadPart; const long long dt1 = t2.QuadPart - it->epoch; const long long dt2 = t2.QuadPart - it->stamp; const double s1 = (double) d / dt1; const double s2 = (double) d / dt2; r->freq = (long long) (it->freq * (s1 + s2 - 1) + 0.5); InterlockedExchangePointer((volatile PVOID*) &init_, r); // if you have log output, here is good point to log calibration results } 

最后是两个实用功能。 一个将FILETIME(包括从now()输出)转换为SYSTEMTIME,同时保留微秒来分隔int 。 其他的将返回频率,所以你的程序可以直接使用__rdtsc()来精确测量时间间隔(精确到纳秒级)。

 void convert(SYSTEMTIME& s, int &us, long long f) { LARGE_INTEGER i; i.QuadPart = f; FileTimeToSystemTime((FILETIME*) (&i.u), &s); s.wMilliseconds = 0; LARGE_INTEGER t; SystemTimeToFileTime(&s, (FILETIME*) (&t.u)); us = (int) (i.QuadPart - t.QuadPart)/10; } long long frequency() { // must make a copy const init* it = init_; return it->freq * 100; } 

当然以上都不是比你的系统时钟更准确 ,这不可能比几百毫秒更准确。 精确时钟的目的(相对于精确 )如上所述,是提供可用于以下两者的单一度量:

  1. 廉价和非常准确的时间间隔测量(不是挂墙时间),
  2. 更不准确 ,但是与上面的墙壁时间的量度单调和一致

我认为它做得很好。 使用示例是日志,其中不仅可以使用时间戳来查找事件的时间,还可以使用内部程序计时,延迟(以微秒)等等的原因。

我离开管道(呼叫初始校准,调度调整)作为一个温和的读者的练习。

您可以使用增强日期时间库。

你可以使用boost :: posix_time :: hours,boost :: posix_time :: minutes,boost :: posix_time :: seconds,boost :: posix_time :: millisec,boost :: posix_time :: nanosec

http://www.boost.org/doc/libs/1_39_0/doc/html/date_time.html

一种流行的方法是使用QueryPerformanceCounter()调用。 如果您需要高精度定时,如测量持续时间仅为微秒级,则此功能非常有用。 我相信这是使用RDTSC机器指令实现的。

可能会有一些问题,例如计数器频率随节能而变化,以及多个内核之间的同步。 有关这些问题的详细信息,请参阅上面的Wikipedia链接。

看看Windows API的GetSystemTime() / GetLocalTime()GetSystemTimeAsFileTime()

GetSystemTimeAsFileTime()以100纳秒的间隔表示时间,即1/10微秒。 所有功能以毫秒级精度提供当前时间。

编辑:

请记住,在大多数Windows系统上,系统时间仅每1毫秒更新一次。 所以,即使以微秒的精度表示你的时间,仍然有必要以这样的精度获得时间。

看看这个: http : //www.decompile.com/cpp/faq/windows_timer_api.htm

可能是这样可以帮助:

 NTSTATUS WINAPI NtQuerySystemTime(__out PLARGE_INTEGER SystemTime); 

SystemTime [out] – 一个指向接收系统时间的LARGE_INTEGER结构的指针。 这是自1601年1月1日(UTC)以来代表100纳秒间隔数的64位值。