QueryPerformanceCounter和溢出

时间:2011-03-14 09:48:08

标签: c++ winapi timing deterministic

我正在使用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做了,代码原始来源的链接在代码中。

4 个答案:

答案 0 :(得分:13)

QueryPerformanceCounter以其不可靠性而臭名昭着。如果您准备处理异常结果,可以使用单个短时间间隔时间。它精确 - 它通常基于PCI总线频率,而负载很重的总线可能导致丢失的滴答声。

GetTickCount实际上更稳定,如果您拨打timeBeginPeriod,则可以为您提供1ms的分辨率。它最终会换行,所以你需要处理它。

不应使用

__rdtsc,除非您正在分析并控制正在运行的核心并准备好处理可变的CPU频率。

GetSystemTime适用于较长时间的测量,但会在系统时间调整后跳转。

此外,Sleep(0)没有按照您的想法执行。如果另一个上下文想要它,它将产生cpu - 否则它将立即返回。

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

你最好的选择可能只是使用GetTickCount或GetSystemTime,将其包装成可以调整时间跳跃/环绕的东西。

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

答案 1 :(得分:5)

性能计数器是64位的,因此它们足够大,可以连续运行多年。例如,如果假设性能计数器每秒增加20亿次(一些假想的2 GHz处理器),它将在大约290年内溢出。

答案 2 :(得分:4)

根据您的评论,您可能应该使用Waitable Timers代替。

请参阅以下示例:

答案 3 :(得分:3)

使用纳秒级计时器控制Sleep()之类的东西最好精确到几毫秒(通常是几十毫秒)无论如何都有点争议。

您可能考虑的另一种方法是使用WaitForSingleObject或类似的函数。这会减少CPU周期,导致一天中的上下文切换减少一万亿次,并且比Sleep(0)更可靠。

例如,您可以创建一个semapore,并且在正常操作中永远不会触摸它。信号量只存在,所以你可以等待某事,如果你没有更好的等待。然后,您可以使用单个系统调用指定长达49天的超时(以毫秒为单位)。并且,它不仅会减少工作量,而且很多也会更准确。

优势在于,如果“发生了某些事情”,那么你想要提前解体,你只需要发信号通知信号量。等待调用将立即返回,您将从WAIT_OBJECT_0返回值知道它将被发出信号,而不是由于时间耗尽。而所有这些都没有复杂的逻辑和计数周期。