【问题标题】:What's the best replacement for timeGetTime to avoid wrap-around?什么是 timeGetTime 避免环绕的最佳替代品?
【发布时间】:2011-02-19 00:41:50
【问题描述】:

timeGetTime 似乎很适合查询系统时间。但是,它的返回值仅为 32 位,因此它大约每 49 天循环一次。

在调用代码中检测翻转并不难,但它增加了一些复杂性,并且(更糟糕的是)需要保持状态。

是否有一些 timeGetTime 的替代品不会出现这种环绕问题(可能通过返回 64 位值),并且具有大致相同的精度和成本?

【问题讨论】:

    标签: c++ windows time


    【解决方案1】:

    您可以使用 RDTSC 内在函数。要获得以毫秒为单位的时间,您可以获得变换系数:

    double get_rdtsc_coeff() {
      static double coeff = 0.0;
      if ( coeff < 1.0 ) { // count it only once
        unsigned __int64 t00 = __rdtsc();
        Sleep(1000);
        unsigned __int64 t01 = __rdtsc();
        coeff = (t01-t00)/1000.0;
      }
    
      return coeff; // transformation coefficient
    }
    

    现在您可以获得上次重置的毫秒数:

    __int64 get_ms_from_start() {
      return static_cast<__int64>(__rdtsc()/get_rdtsc_coeff());
    }
    

    如果您的系统使用 SpeedStep 或类似技术,您可以使用 QueryPerformanceCounter/QueryPerformanceFrequency 函数。 Windows 提供保证,然后在系统运行时频率不会改变。

    【讨论】:

    • SpeedStep 但结束了这种方法的用处。 en.wikipedia.org/wiki/SpeedStep
    • 是的,对于 SpeedStep 系统,这将不起作用。更新了我的答案。
    【解决方案2】:

    看看GetSystemTimeAsFileTime()。它填充了一个 FILETIME 结构,其中包含一个“64 位值,表示自 1601 年 1 月 1 日 (UTC) 以来 100 纳秒间隔的数量”

    【讨论】:

      【解决方案3】:

      什么平台?

      如果您在 Vista 或更高版本上运行,您可以使用 GetTickCount64(),或者从 GetTickCount() 和计时器合成您自己的 GetTickCount64()...

      我在 GetTickCount() 中处理翻转问题,并在不支持它的平台上合成 GetTickCount64() 在我的博客上关于测试非平凡代码:http://www.lenholgate.com/blog/2008/04/practical-testing-17---a-whole-new-approach.html

      【讨论】:

      • FWIW、GetTickCounttimeGetTimenot interchangeable。它们都有毫秒分辨率,但GetTickCount只有10-16毫秒精度,而timeGetTime通常有1-5毫秒精度
      【解决方案4】:

      您打算如何使用它?在检查我知道将在 49 天以下的持续时间时,我经常使用 Win32 等效项。例如,以下代码将始终有效。

      DWORD start = timeGetTime();
      DoSomthingThatTakesLessThen49Days();
      DWORD duration = timeGetTime() - start;
      

      即使在调用DoSomthingThatTakesLessThen49DaystimeGetTime 翻转duration 仍然是正确的。

      请注意,以下代码在翻转时可能会失败。

      DWORD start = timeGetTime();
      DoSomthingThatTakesLessThen49Days();
      if (now + 5000 < timeGetTime())
      {
      }
      

      但可以很容易地重写为如下工作

      DWORD start = timeGetTime();
      DoSomthingThatTakesLessThen49Days();
      if (timeGetTime() - start < 5000)
      {
      }
      

      【讨论】:

      • Thomas 的评论(上图)适用于您的情况。
      【解决方案5】:

      不,跟踪翻转需要状态。它可以像在每次回调时增加您自己的 64 位计数器一样简单。

      想要以低至 1 毫秒的分辨率跟踪长达 49 天的时间段是非常不寻常的。您不得不担心经过这么长时间后准确性仍然存在。下一步是使用时钟,GetTickCount(64),GetSystemTimeAsFileTime 的分辨率为 15.625 毫秒,并通过时间服务器保持准确。

      【讨论】:

      • 系统启动时开始计算翻转,因此程序不必运行 49 天才能看到翻转。
      【解决方案6】:

      假设你可以保证这个函数至少每 49 天调用一次,这样的事情会起作用:

      // Returns current time in milliseconds
      uint64_t timeGetTime64()
      {
         static uint32_t _prevVal    = 0;
         static uint64_t _wrapOffset = 0;
      
         uint32_t newVal = (uint32_t) timeGetTime();
         if (newVal < _prevVal) _wrapOffset += (((uint64_t)1)<<32);
         _prevVal = newVal;
         return _wrapOffset+newVal;
      }
      

      请注意,由于使用静态变量,此函数不是多线程安全的,因此如果您打算从多个线程调用它,您应该通过临界区或互斥锁或类似方法对其进行序列化。

      【讨论】:

        【解决方案7】:

        我不确定这是否完全满足您的需求,但是

        std::chrono::system_clock
        

        可能与您正在寻找的内容相符。

        http://en.cppreference.com/w/cpp/chrono/system_clock

        【讨论】:

        • 更多信息here,但是在不同平台上对两个库的功能进行完整的基准比较将是更好的参考。
        【解决方案8】:

        除非您需要为超过 49 天的活动计时,您可以放心地忽略环绕。只需始终从当前 timeGetTime() 中减去上一个 timeGetTime(),您将始终获得准确的 delta 测量时间,即使跨环绕 - 前提是您正在计时事件总时长不到 49 天。这一切都归功于无符号模块化数学在计算机内部的工作方式。

        // this code ALWAYS works, even with wrap-around!
        DWORD dwStart = timeGetTime();
        // provided the event timed here has a duration of less than 49 days
        DWORD dwDuration = timeGetTime()-dwStart;
        

        提示:查看 TimeBeginPeriod(1L) 以提高 timeGetTime() 的准确性。

        但是...如果您想要 64 位版本的 timeGetTime,这里是:

        __int64 timeGetTime64() {
            static __int64 time64=0;
            // warning: if multiple threads call this function, protect with a critical section!
            return (time64 += (timeGetTime()-(DWORD)time64));
            }
        

        请注意,如果此函数未至少每 49 天调用一次,则此函数将无法正确检测回绕。

        【讨论】:

        • 我可以建议将__declspec(thread) 添加到您对time64 的定义中,作为比关键部分更有效的解决方案吗?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-12
        • 2014-01-15
        • 1970-01-01
        • 2011-01-12
        相关资源
        最近更新 更多