windows performancecounter C++ 的时序漂移

windows timing drift of performancecounter C++

我在我的应用程序中使用 QueryPerformanceCounter() 函数来测量时间。此功能还用于提供我的应用程序生命周期的时间线。最近我注意到其他时间函数的时间有偏差。最后,我写了一个小测试来检查漂移是否真实。 (我用的是VS2013编译器)

    #include <Windows.h>
    #include <chrono>
    #include <thread>
    #include <cstdio>

    static LARGE_INTEGER s_freq;

    using namespace std::chrono;

    inline double now_WinPerfCounter()
    {
        LARGE_INTEGER tt;
        if (TRUE != ::QueryPerformanceCounter(&tt))
        {
            printf("Error in QueryPerformanceCounter() - Err=%d\n", GetLastError());
            return -1.;
        }
        return (double)tt.QuadPart / s_freq.QuadPart;
    }

   inline double now_WinTick64()
   {
        return (double)GetTickCount64() / 1000.;
   }

   inline double now_WinFileTime()
   {
        FILETIME ft;
        ::GetSystemTimeAsFileTime(&ft);

        long long * pVal = reinterpret_cast<long long *>(&ft);

        return (double)(*pVal) / 10000000. - 11644473600LL;
   }


    int _tmain(int argc, _TCHAR* argv[])
    {
        if (TRUE != ::QueryPerformanceFrequency(&s_freq))
        {
            printf("Error in QueryPerformanceFrequency() - Err=#d\n", GetLastError());
            return -1;
        }

        // save all timetags at the beginning
        double t1_0 = now_WinPerfCounter();
        double t2_0 = now_WinTick64();
        double t3_0 = now_WinFileTime();
        steady_clock::time_point t4_0 = steady_clock::now();

        for (int i = 0;; ++i)   // forever
        {
            double t1 = now_WinPerfCounter();
            double t2 = now_WinTick64();
            double t3 = now_WinFileTime();
            steady_clock::time_point t4 = steady_clock::now();

            printf("%03d\t %.3lf %.3lf %.3lf %.3lf \n",
            i,
            t1 - t1_0,
            t2 - t2_0,
            t3 - t3_0,
            duration_cast<nanoseconds>(t4 - t4_0).count() * 1.e-9
            );

            std::this_thread::sleep_for(std::chrono::seconds(10));
        }
    return 0;
   }

输出令人困惑:

    000      0.000 0.000 0.000 0.000
    ...
    001      10.001 10.000 10.002 10.002
    ...
    015      150.006 150.010 150.010 150.010
    ...
    024      240.009 240.007 240.015 240.015
    ...
    025      250.010 250.007 250.015 250.015
    ...
    026      260.010 260.007 260.016 260.016
    ...
    070      700.027 700.039 700.041 700.041

为什么会有差异?使用不同的 API 函数时,一秒的持续时间看起来不一样?此外,在一天中,差异不是恒定的...

这是正常的,负担得起的时钟从来没有无限的精度。 GetSystemTime() 和 GetTickCount() 定期从 Internet 时间服务器重新校准,默认为 time.windows.com。它使用负担不起的原子钟来计时。追赶或减速的调整是渐进的,以避免让软件心脏病发作。他们报告的时间将偏离地球自转至多几秒钟,定期重新校准限制了长期漂移误差。

但是 QPF 没有经过校准,它的分辨率太高,无法让这些调整不影响它通常用于的短间隔测量。它源自芯片组上可用的频率源,由系统构建者选择。受制于典型的电子零件公差,温度尤其会影响精度并导致较长时间的可变漂移。因此QPF只适合测量比较短的区间。

不可避免地,两个时钟不能相同,您看到它们渐行渐远。您必须选择一个作为 "master" 时钟。如果长期精度很重要,那么您必须选择校准源,如果高分辨率而不是精度很重要,则选择 QPF。如果两者都很重要,那么您需要定制硬件,例如 GPS 时钟。

可能不同的函数以不同的方式测量时间。

QueryPerformanceCounter - 是高分辨率时间戳或测量时间间隔。

GetSystemTimeAsFileTime - 以 UTC 格式检索当前系统日期和时间。

所以使用这个函数并比较它们的时间是不太正确的。

编辑: 您观察到的漂移的主要原因是 性能计数器频率 不准确。系统为您提供了 QueryPerformanceFrequency 返回的常量值。这个恒定频率仅接近真实频率。它伴随着 offset 和可能的 drift。现代平台(Windows > 7,不变的 TSC)几乎没有偏移和漂移。

示例:如果您使用性能计数器频率将性能计数器值缩放到时间,则 5 ppm 偏移会导致时间比预期快 5 us/s 或 432 ms/day。

一般: QueryPerformanceCounterGetSystemTimeAsFileTime 正在使用不同的资源(硬件)。现代平台从 CPU 的时间戳计数器 (TSC) 派生 QueryPerformanceCounterGetSystemTimeAsFileTime 使用 PIT 计时器、ACPI PM 计时器或 HPET 硬件。有关详细信息,请参阅 Intel 64® and IA-32 Architectures Software Developer's Manual, Volume 3B: System Programming Guide, Part 2。 当两个 API 使用不同的硬件时,处理漂移是不可避免的。但是,您可以扩展您的测试代码来校准漂移。 频率生成硬件通常对温度敏感。因此,漂移可能会有所不同,具体取决于负载。看 The Windows Timestamp Project 了解详情。

使用多种测量方法可能不是一个好主意;不同的函数有不同的分辨率,你会有效地看到值四舍五入到它们的精度的噪音。当然,设置 t1..t4 时会经过少量时间,因为函数需要时间来执行,所以漂移是不可避免的。

您可能需要查看 GetSystemTimePreciseAsFileTime API。既高精度又可以从外部时间服务器周期性调整。