【发布时间】:2009-09-30 12:03:57
【问题描述】:
我在使用时间上有问题。 我想在使用 C++ 的 Windows 上使用并获得微秒。
我找不到路。
【问题讨论】:
-
您是指以微秒表示的时间还是以微秒为单位的时间?
-
时间以微秒表示
我在使用时间上有问题。 我想在使用 C++ 的 Windows 上使用并获得微秒。
我找不到路。
【问题讨论】:
unwind 给出了“规范”的答案:
一种流行的方法是使用 QueryPerformanceCounter() 调用。
不过这种方法的问题很少:
QueryPerformanceCounter 未在用户空间实现;这意味着需要上下文切换来调用内核端的实现,这相对昂贵(大约 0.7 微秒)。这似乎是支持旧硬件所必需的。不过,并非一切都丢失了。第 1 点和第 2 点是您可以通过一些编码来完成的事情,3. 可以用直接调用 RDTSC 来替换(通过 __rdtsc() 内在的 Visual C++ 的较新版本中可用),只要您知道准确的 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 毫秒给出了很好的结果(时间越短,校准越不准确)。出于校准的目的,我们将使用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 纳秒)。需要注意三点:
返回类型与 FILETIME 结构二进制兼容,精确到 100ns,这与 GetSystemTimeAsFileTime 不同(它以 10-30ms 左右的间隔递增,或最多 1 毫秒)。
为了避免从整数到双精度到整数的昂贵转换,整个计算以 64 位整数执行。即使这些可以容纳大量数字,也存在整数溢出的真正风险,因此必须定期提出start 以避免它。这是在时钟调整中完成的。
我们正在制作校准数据的副本,因为它可能在我们调用期间通过另一个线程中的时钟调整进行了更新。
这是高精度读取当前时间的代码。返回值与 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;
}
当然,以上都没有比您的系统时钟更准确,它不太可能比几百毫秒更准确。上面实现的精确时钟(相对于准确)的目的是提供可用于两者的单一测量:
我认为它做得很好。示例用途是日志,其中不仅可以使用时间戳来查找事件时间,还可以了解内部程序时间、延迟(以微秒为单位)等原因。
我将管道(调用初始校准、调度调整)留给温和的读者作为练习。
【讨论】:
您可以使用提升日期时间库。
你可以使用 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
【讨论】:
一种流行的方法是使用@987654321@ 调用。如果您需要高精度计时,例如测量仅需要微秒级的持续时间,这将非常有用。我相信这是使用@987654322@ 机器指令实现的。
但可能存在一些问题,例如计数器频率随省电而变化,以及多个内核之间的同步。有关这些问题的详细信息,请参阅上面的 Wikipedia 链接。
【讨论】:
查看 Windows API GetSystemTime() / GetLocalTime() 或 GetSystemTimeAsFileTime()。
GetSystemTimeAsFileTime() 以 100 纳秒为间隔表示时间,即 1/10 微秒。所有函数都以毫秒为单位提供当前时间。
编辑:
请记住,在大多数 Windows 系统上,系统时间大约每 1 毫秒更新一次。因此,即使以微秒精度表示您的时间,仍然需要以如此精确的精度获取时间。
【讨论】:
NtSetTimerResolution 或 timeBeginPeriod 可用于启用 1ms 的最高分辨率;这里有一篇关于这个主题的有趣文章decuslib.com/decus/vmslt97a/ntstuff/timer.txt。默认分辨率在 10-30 毫秒之间,具体取决于 Windows 版本和硬件。下面我提出了更准确的时间测量方法,而不影响系统的其余部分。
【讨论】:
这可能会有所帮助:
NTSTATUS WINAPI NtQuerySystemTime(__out PLARGE_INTEGER SystemTime);
SystemTime [out] - 指向接收系统时间的 LARGE_INTEGER 结构的指针。这是一个 64 位值,表示自 1601 年 1 月 1 日 (UTC) 以来的 100 纳秒间隔数。
【讨论】: