QueryPerformanceCounter

QueryPerformanceCounter

我正在使用QueryPerformanceCounter在我的应用程序中进行一些计时。但是,运行了几天后,该应用程序似乎无法正常运行。如果我只是重新启动该应用程序,它将再次开始工作。这使我相信我的时序代码中有一个溢出问题。

// Author: Ryan M. Geiss
// http://www.geisswerks.com/ryan/FAQS/timing.html
class timer
{
public:
    timer()
    {
        QueryPerformanceFrequency(&freq_);
        QueryPerformanceCounter(&time_);
    }

    void tick(double interval)
    {
        LARGE_INTEGER t;
        QueryPerformanceCounter(&t);

        if (time_.QuadPart != 0)
        {
            int ticks_to_wait = static_cast<int>(static_cast<double>(freq_.QuadPart) * interval);
            int done = 0;
            do
            {
                QueryPerformanceCounter(&t);

                int ticks_passed = static_cast<int>(static_cast<__int64>(t.QuadPart) - static_cast<__int64>(time_.QuadPart));
                int ticks_left = ticks_to_wait - ticks_passed;

                if (t.QuadPart < time_.QuadPart)    // time wrap
                    done = 1;
                if (ticks_passed >= ticks_to_wait)
                    done = 1;

                if (!done)
                {
                    // if > 0.002s left, do Sleep(1), which will actually sleep some
                    //   steady amount, probably 1-2 ms,
                    //   and do so in a nice way (cpu meter drops; laptop battery spared).
                    // otherwise, do a few Sleep(0)'s, which just give up the timeslice,
                    //   but don't really save cpu or battery, but do pass a tiny
                    //   amount of time.
                    if (ticks_left > static_cast<int>((freq_.QuadPart*2)/1000))
                        Sleep(1);
                    else
                        for (int i = 0; i < 10; ++i)
                            Sleep(0);  // causes thread to give up its timeslice
                }
            }
            while (!done);
        }

        time_ = t;
    }
private:
    LARGE_INTEGER freq_;
    LARGE_INTEGER time_;
};

我的问题是,上面的代码是否应该在连续运行数周的过程中确定性地工作?

如果不是,问题出在哪里?我以为溢出是由
if (t.QuadPart < time_.QuadPart)    // time wrap
    done = 1;

但是也许那还不够?

编辑:请注意,我没有写原始代码,Ryan M. Geiss做了,指向代码原始源的链接在代码中。

最佳答案

QueryPerformanceCounter因其不可靠而臭名昭著。如果您准备处理异常结果,则可以将其用于单个短间隔计时。它是而不是精确-通常基于PCI总线频率,而负载过重的总线可能会导致滴答声丢失。
GetTickCount实际上更稳定,如果您调用timeBeginPeriod,则可以为您提供1ms的分辨率。它将最终包装好,因此您需要进行处理。

除非您正在分析并且可以控制正在运行的内核并准备处理可变的CPU频率,否则不应该使用__rdtsc
GetSystemTime对于较长的测量周期来说是不错的,但是在调整系统时间时会跳转。

另外,Sleep(0)不会执行您认为的操作。如果另一个上下文需要它,它将产生cpu -否则它将立即返回。

简而言之,在Windows上计时是一团糟。有人会认为,今天有可能从计算机上获得准确的长期时间,而无需经历麻烦。但是事实并非如此。在我们的游戏框架中,我们使用来自服务器的多个时间源和更正,以确保所有连接的客户端具有相同的游戏时间,并且那里有很多糟糕的时钟。

最好的选择是仅使用GetTickCount或GetSystemTime,将其包装为可随时间跳转/环绕而调整的内容。

另外,您应该将double interval转换为int64 milliseconds,然后仅使用整数数学-这样可以避免由于浮点类型根据其内容而变化的准确性而引起的问题。

10-08 00:41