C++窗口时间

Bor*_*kov 2 c++ windows time

我在使用时间方面遇到了问题.我想使用C++在Windows上使用并获得微秒.

我找不到方法.

bro*_*ekk 12

放松地给出了"规范"答案:

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

但是这种方法几乎没有问题:

  1. 它用于测量时间间隔,而不是时间.这意味着您必须编写代码来建立"纪元时间",您将从中测量精确的间隔.这称为校准.
  2. 在校准时钟时,您还需要定期对其进行调整,以便系统时钟不会过于同步(这称为漂移).
  3. 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];
Run Code Online (Sandbox Code Playgroud)

这是初始校准的代码; 它必须有时间(以毫秒为单位)等待时钟移动; 我发现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);
}
Run Code Online (Sandbox Code Playgroud)

有了良好的校准数据,您可以从便宜的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;
}
Run Code Online (Sandbox Code Playgroud)

对于时钟调整,我们需要捕获准确的时间(由系统时钟提供)并将其与我们的时钟进行比较; 这将给我们带来漂移价值.接下来我们使用简单的公式计算"调整后的"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
}
Run Code Online (Sandbox Code Playgroud)

最后有两个实用功能.一个将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;
}
Run Code Online (Sandbox Code Playgroud)

当然,上述任何一个都不比你的系统时钟更精确,这不可能比几百毫秒更准确.的目的精确时钟(与精确如上实现),是提供一种可用于单一度量二者:

  1. 便宜且非常准确地测量时间间隔(不是墙壁时间),
  2. 更不准确,但单调且与上述一致,衡量墙壁时间

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

我将管道(调用初始校准,调度调整)作为温和读者的练习.


Dav*_*yan 8

您可以使用提升日期时间库.

你可以使用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


unw*_*ind 5

一种流行的方式是使用QueryPerformanceCounter()呼叫.如果您需要高精度定时,例如测量仅需要几微秒的持续时间,这将非常有用.我相信这是使用RDTSC机器指令实现的.

但是可能存在问题,例如计数器频率随着节能而变化,以及多个核心之间的同步.有关这些问题的详细信息,请参阅上面的Wikipedia链接.


Fra*_*ack 5

看一下Windows API GetSystemTime()/ GetLocalTime()GetSystemTimeAsFileTime().

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

编辑:

请记住,在大多数Windows系统上,系统时间仅在每1毫秒更新一次.因此,即使以微秒精度表示您的时间,仍然需要以如此精确的方式获得时间.