如何在Windows上创建在暂停期间不打勾的单调时钟?

Nic*_*son 3 time winapi

我正在寻找一种获得保证单调时钟的方法,它可以排除暂停期间所花费的时间,就像POSIX一样CLOCK_MONOTONIC.

需要Windows 7(或更高版本)的解决方案是可接受的.

这是一个不起作用的例子:

LONGLONG suspendTime, uiTime1, uiTime2;
do {
  QueryUnbiasedInterruptTime((ULONGLONG*)&uiTime1);
  suspendTime = GetTickCount64()*10000 - uiTime1;
  QueryUnbiasedInterruptTime((ULONGLONG*)&uiTime2);
} while (uiTime1 != uiTime2);
static LARGE_INTEGER firstSuspend = suspendTime;
static LARGE_INTERER lastSuspend = suspendTime;
assert(suspendTime > lastSuspend);
lastSuspend = suspendTime;

LARGE_INTEGER now;
QueryPerformanceCounter(&now);
static LONGLONG firstQpc = now.QuadPart;

return (now.QuadPart - firstQpc)*qpcFreqNumer/qpcFreqDenom -
    (suspendTime - firstSuspend);
Run Code Online (Sandbox Code Playgroud)

这个(我的第一次尝试)的问题是GetTickCount每15ms只会打勾,而且QueryUnbiasedInterruptTime似乎更频繁地打勾,所以我的方法偶尔会观察暂停时间稍微回过头来.

我也尝试过使用CallNtPowerInformation,但目前尚不清楚如何使用这些值来获得一个不错的,无竞争的暂停时间度量.

Eri*_*own 5

暂停偏置时间在内核模式下可用(ntddk.h中为_KUSER_SHARED_DATA.QpcBias).在用户模式下可以使用只读副本.

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>

LONGLONG suspendTime, uiTime1, uiTime2;
QueryUnbiasedInterruptTime((ULONGLONG*)&uiTime1);
uiTime1 -= USER_SHARED_DATA->QpcBias;  // subtract off the suspend bias
Run Code Online (Sandbox Code Playgroud)


Nic*_*son 5

计算单调时间(在挂起期间不计时)的完整过程如下:

typedef struct _KSYSTEM_TIME {
  ULONG LowPart;
  LONG High1Time;
  LONG High2Time;
} KSYSTEM_TIME;
#define KUSER_SHARED_DATA 0x7ffe0000
#define InterruptTime ((KSYSTEM_TIME volatile*)(KUSER_SHARED_DATA + 0x08))
#define InterruptTimeBias ((ULONGLONG volatile*)(KUSER_SHARED_DATA + 0x3b0))

static LONGLONG readInterruptTime() {
  // Reading the InterruptTime from KUSER_SHARED_DATA is much better than
  // using GetTickCount() because it doesn't wrap, and is even a little quicker.
  // This works on all Windows NT versions (NT4 and up).
  LONG timeHigh;
  ULONG timeLow;
  do {
    timeHigh = InterruptTime->High1Time;
    timeLow = InterruptTime->LowPart;
  } while (timeHigh != InterruptTime->High2Time);
  LONGLONG now = ((LONGLONG)timeHigh << 32) + timeLow;
  static LONGLONG d = now;
  return now - d;
}

static LONGLONG scaleQpc(LONGLONG qpc) {
  // We do the actual scaling in fixed-point rather than floating, to make sure
  // that we don't violate monotonicity due to rounding errors.  There's no
  // need to cache QueryPerformanceFrequency().
  LARGE_INTEGER frequency;
  QueryPerformanceFrequency(&frequency);
  double fraction = 10000000/double(frequency.QuadPart);
  LONGLONG denom = 1024;
  LONGLONG numer = std::max(1LL, (LONGLONG)(fraction*denom + 0.5));
  return qpc * numer / denom;
}

static ULONGLONG readUnbiasedQpc() {
  // We remove the suspend bias added to QueryPerformanceCounter results by
  // subtracting the interrupt time bias, which is not strictly speaking legal,
  // but the units are correct and I think it's impossible for the resulting
  // "unbiased QPC" value to go backwards.
  LONGLONG interruptTimeBias, qpc;
  do {
    interruptTimeBias = *InterruptTimeBias;
    LARGE_INTEGER counter;
    QueryPerformanceCounter(&counter);
    qpc = counter.QuadPart;
  } while (interruptTimeBias != *InterruptTimeBias);
  static std::pair<LONGLONG,LONGLONG> d(qpc, interruptTimeBias);
  return scaleQpc(qpc - d.first) - (interruptTimeBias - d.second);
}

/// getMonotonicTime() returns the time elapsed since the application's first
/// call to getMonotonicTime(), in 100ns units.  The values returned are
/// guaranteed to be monotonic.  The time ticks in 15ms resolution and advances
/// during suspend on XP and Vista, but we manage to avoid this on Windows 7
/// and 8, which also use a high-precision timer.  The time does not wrap after
/// 49 days.
uint64_t getMonotonicTime()
{
  OSVERSIONINFOEX ver = { sizeof(OSVERSIONINFOEX), };
  GetVersionEx(&ver);
  bool win7OrLater = (ver.dwMajorVersion > 6 ||
      (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 1));
  // On Windows XP and earlier, QueryPerformanceCounter is not monotonic so we
  // steer well clear of it; on Vista, it's just a bit slow.
  return win7OrLater ? readUnbiasedQpc() : readInterruptTime();
}
Run Code Online (Sandbox Code Playgroud)