QueryPerformanceCounter和溢出

ron*_*nag 5 c++ winapi deterministic timing

我正在使用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_;
};
Run Code Online (Sandbox Code Playgroud)

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

如果不是问题出在哪里?我以为溢出是由处理的

if (t.QuadPart < time_.QuadPart)    // time wrap
    done = 1;
Run Code Online (Sandbox Code Playgroud)

但也许这还不够?

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

Eri*_*rik 15

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

GetTickCount实际上更稳定,如果你打电话,可以给你1ms的分辨率timeBeginPeriod.它最终会换行,所以你需要处理它.

__rdtsc 不应该使用,除非您正在分析并控制正在运行的核心,并准备处理可变的CPU频率.

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

此外,Sleep(0)不会做你认为它做的事情.如果另一个上下文需要它将产生cpu - 否则它将立即返回.

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

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

此外,您应该将您转换double interval为a int64 milliseconds然后仅使用整数数学 - 这可以避免由于浮点类型根据其内容的不同精度而导致的问题.


sha*_*oth 5

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

  • 我怀疑在你的代码中可能隐藏了一个隐藏的转换为double的地方.double只有52位有符号精度,所以除了int64_t(我说int64_t,而不是uint64_t,因为LARGE_INTEGER也被签名,虽然有趣的是tick计数器不是),它会在8.6天(~1周)内溢出3GHz机器. (2认同)

dal*_*lle 5

根据您的评论,您可能应该改为使用等待计时器

请参阅以下示例: