【发布时间】:2011-02-19 00:41:50
【问题描述】:
timeGetTime 似乎很适合查询系统时间。但是,它的返回值仅为 32 位,因此它大约每 49 天循环一次。
在调用代码中检测翻转并不难,但它增加了一些复杂性,并且(更糟糕的是)需要保持状态。
是否有一些 timeGetTime 的替代品不会出现这种环绕问题(可能通过返回 64 位值),并且具有大致相同的精度和成本?
【问题讨论】:
timeGetTime 似乎很适合查询系统时间。但是,它的返回值仅为 32 位,因此它大约每 49 天循环一次。
在调用代码中检测翻转并不难,但它增加了一些复杂性,并且(更糟糕的是)需要保持状态。
是否有一些 timeGetTime 的替代品不会出现这种环绕问题(可能通过返回 64 位值),并且具有大致相同的精度和成本?
【问题讨论】:
您可以使用 RDTSC 内在函数。要获得以毫秒为单位的时间,您可以获得变换系数:
double get_rdtsc_coeff() {
static double coeff = 0.0;
if ( coeff < 1.0 ) { // count it only once
unsigned __int64 t00 = __rdtsc();
Sleep(1000);
unsigned __int64 t01 = __rdtsc();
coeff = (t01-t00)/1000.0;
}
return coeff; // transformation coefficient
}
现在您可以获得上次重置的毫秒数:
__int64 get_ms_from_start() {
return static_cast<__int64>(__rdtsc()/get_rdtsc_coeff());
}
如果您的系统使用 SpeedStep 或类似技术,您可以使用 QueryPerformanceCounter/QueryPerformanceFrequency 函数。 Windows 提供保证,然后在系统运行时频率不会改变。
【讨论】:
看看GetSystemTimeAsFileTime()。它填充了一个 FILETIME 结构,其中包含一个“64 位值,表示自 1601 年 1 月 1 日 (UTC) 以来 100 纳秒间隔的数量”
【讨论】:
什么平台?
如果您在 Vista 或更高版本上运行,您可以使用 GetTickCount64(),或者从 GetTickCount() 和计时器合成您自己的 GetTickCount64()...
我在 GetTickCount() 中处理翻转问题,并在不支持它的平台上合成 GetTickCount64() 在我的博客上关于测试非平凡代码:http://www.lenholgate.com/blog/2008/04/practical-testing-17---a-whole-new-approach.html
【讨论】:
GetTickCount 和 timeGetTime 是 not interchangeable。它们都有毫秒分辨率,但GetTickCount只有10-16毫秒精度,而timeGetTime通常有1-5毫秒精度。
您打算如何使用它?在检查我知道将在 49 天以下的持续时间时,我经常使用 Win32 等效项。例如,以下代码将始终有效。
DWORD start = timeGetTime();
DoSomthingThatTakesLessThen49Days();
DWORD duration = timeGetTime() - start;
即使在调用DoSomthingThatTakesLessThen49Days 时timeGetTime 翻转duration 仍然是正确的。
请注意,以下代码在翻转时可能会失败。
DWORD start = timeGetTime();
DoSomthingThatTakesLessThen49Days();
if (now + 5000 < timeGetTime())
{
}
但可以很容易地重写为如下工作
DWORD start = timeGetTime();
DoSomthingThatTakesLessThen49Days();
if (timeGetTime() - start < 5000)
{
}
【讨论】:
不,跟踪翻转需要状态。它可以像在每次回调时增加您自己的 64 位计数器一样简单。
想要以低至 1 毫秒的分辨率跟踪长达 49 天的时间段是非常不寻常的。您不得不担心经过这么长时间后准确性仍然存在。下一步是使用时钟,GetTickCount(64),GetSystemTimeAsFileTime 的分辨率为 15.625 毫秒,并通过时间服务器保持准确。
【讨论】:
假设你可以保证这个函数至少每 49 天调用一次,这样的事情会起作用:
// Returns current time in milliseconds
uint64_t timeGetTime64()
{
static uint32_t _prevVal = 0;
static uint64_t _wrapOffset = 0;
uint32_t newVal = (uint32_t) timeGetTime();
if (newVal < _prevVal) _wrapOffset += (((uint64_t)1)<<32);
_prevVal = newVal;
return _wrapOffset+newVal;
}
请注意,由于使用静态变量,此函数不是多线程安全的,因此如果您打算从多个线程调用它,您应该通过临界区或互斥锁或类似方法对其进行序列化。
【讨论】:
我不确定这是否完全满足您的需求,但是
std::chrono::system_clock
可能与您正在寻找的内容相符。
【讨论】:
除非您需要为超过 49 天的活动计时,您可以放心地忽略环绕。只需始终从当前 timeGetTime() 中减去上一个 timeGetTime(),您将始终获得准确的 delta 测量时间,即使跨环绕 - 前提是您正在计时事件总时长不到 49 天。这一切都归功于无符号模块化数学在计算机内部的工作方式。
// this code ALWAYS works, even with wrap-around!
DWORD dwStart = timeGetTime();
// provided the event timed here has a duration of less than 49 days
DWORD dwDuration = timeGetTime()-dwStart;
提示:查看 TimeBeginPeriod(1L) 以提高 timeGetTime() 的准确性。
但是...如果您想要 64 位版本的 timeGetTime,这里是:
__int64 timeGetTime64() {
static __int64 time64=0;
// warning: if multiple threads call this function, protect with a critical section!
return (time64 += (timeGetTime()-(DWORD)time64));
}
请注意,如果此函数未至少每 49 天调用一次,则此函数将无法正确检测回绕。
【讨论】:
__declspec(thread) 添加到您对time64 的定义中,作为比关键部分更有效的解决方案吗?