【问题标题】:C++ high precision time measurement in WindowsWindows 中的 C++ 高精度时间测量
【发布时间】:2010-12-21 23:53:00
【问题描述】:

我有兴趣在 Windows 中使用 C++ 将特定时间点测量到纳秒级。这可能吗?如果不是,是否有可能至少以微秒为单位获得特定时间?任何库都应该这样做,除非我认为托管代码可以。 谢谢

【问题讨论】:

  • 在用户空间中,使用分时操作系统,您不可能以纳秒为单位计算准确的时间。不要听那些给你一个返回纳秒计数的函数的人——这些数据是无用的。内核模块可以做到这一点,如果你有硬件时间戳,那么你就设置好了。问题需要更多细节。

标签: c++ c windows


【解决方案1】:

如果您在多核计算机上运行线程应用程序,QueryPerformanceCounter 可以(并且将会)返回不同的值,具体取决于代码在哪个内核上执行。请参阅this MSDN 文章。 (rdtsc也有同样的问题)

这不仅仅是一个理论问题;我们在我们的应用程序中遇到了它,不得不得出结论,唯一可靠的时间源是 timeGetTime,它只有 ms 精度(幸运的是,这在我们的案例中就足够了)。我们还尝试为我们的线程固定线程亲和性,以确保每个线程始终从QueryPerformanceCounter 获得一致的值,这很有效,但它绝对会影响应用程序的性能。

总而言之,Windows 上没有一个可靠的计时器可以用来以微秒精度计时(至少在多核计算机上运行时不能)。

【讨论】:

  • +1 指出了 QueryPerformanceCounter 鲜为人知的陷阱。 Windows 上的高性能计时器只是不可靠地存在,这对于某些系统来说是一个巨大的 PITA。与基于 Unix 的系统进行比较和对比,后者可以轻松完成...
  • 2013 年还是这样吗? msdn.microsoft.com/en-us/library/windows/desktop/…一般来说,性能计数器结果在多核和多处理器系统中的所有处理器上都是一致的,即使是在不同的线程或进程上测量时也是如此。
  • @MartinThompson 实际上我不知道,我写这个答案已经四年多了,在那段时间里情况可能发生了变化。
  • 现在的多核计算机完美支持QPC。请参阅msdn.microsoft.com/en-us/library/windows/desktop/… 以获得更多精确度。基本上,处理器计数器长期以来一直是时间不变的,如果不是,Windows 将使用另一个(可能不太精确)源来测量时间。
【解决方案2】:

Windows 有一个high-performance counter API

您需要从QueryPerformanceCounter 获取刻度并除以QueryPerformanceFrequency 提供的处理器频率。

LARGE_INTEGER frequency;
if (::QueryPerformanceFrequency(&frequency) == FALSE)
    throw "foo";

LARGE_INTEGER start;
if (::QueryPerformanceCounter(&start) == FALSE)
    throw "foo";

// Calculation.


LARGE_INTEGER end;
if (::QueryPerformanceCounter(&end) == FALSE)
    throw "foo";

double interval = static_cast<double>(end.QuadPart - start.QuadPart) / frequency.QuadPart;

这个interval 应该以秒为单位。

【讨论】:

  • 请注意,英特尔的 SpeedStep 技术可能会在您的代码没有注意到的情况下改变 PerformanceFrequency...
  • @xtofl:说实话,QPF 的值可能并不是那么准确(在某处对风险进行了很好的描述)。但重复测量往往会相对较好地平均这一点。
  • QueryPerformanceFrequency/Coutner 并不一定因为这个原因而使用系统时钟。然而,这意味着它使用另一个频率较低的定时器。 PIC 计时器、ACPI 计时器或非常新系统上的 HPET。详情请见blogs.msdn.com/oldnewthing/archive/2007/07/05/3695356.aspx
  • 出于这个原因,最新版本的 Windows 不会将 rdtsc 用于 QPC,因此不会返回 QPF 的处理器频率。他们可能使用 1.193182 MHz 可编程中断定时器 (PIT) 或高速 (>10 MHz) HPET。
  • FWIW 似乎 Windows 7 使用了 HPET :) 获得更好的分辨率,因为我从 Vista 升级。
【解决方案3】:

为了将来参考,对于 Windows Vista、2008 及更高版本,Windows 需要硬件支持“HPET”。这独立于 CPU 及其时钟和频率运行。可以得到精确到亚微秒级的时间。

为了实现这一点,您确实需要使用 QPC/QPF。问题是 QPF(频率)是一个 NOMINAL 值,因此使用原始调用会导致时间漂移,每天可能超过分钟。为了解决这个问题,您必须测量实际频率并检查其随时间的漂移,因为热量和其他物理操作条件会对其产生影响。

可以在 MSDN(大约 2004 年!)的此链接上找到描述此问题的文章。 http://msdn.microsoft.com/en-us/magazine/cc163996.aspx

我自己确实实现了类似的东西(今天才找到上面的链接!)但不喜欢使用“微秒时间”,因为与其他 Windows 调用(如 GetSystemTimeAsFileTime)相比,QPC 调用本身相当长,并且同步增加了更多开销。所以我更喜欢使用毫秒时间戳(调用时间比使用 QPC 少大约 70%),尤其是当我试图每秒获得数十万次的时间时。

【讨论】:

    【解决方案4】:

    最好的选择是函数QueryPerformanceCounterQueryPerformanceFrequency

    微软最近(2014 年)发布了有关 QueryPerformanceCounter 的更多详细信息:

    详情请参阅Acquiring high-resolution time stamps (MSDN 2014)。

    这是一篇综合性文章,包含大量示例和详细说明。 QPC用户必读。

    【讨论】:

      【解决方案5】:

      我认为微秒有点不合理(没有硬件帮助)。毫秒是可行的,但由于各种邪恶的反解决问题,即使那样也不是那么准确。无论如何,我包含我自己的计时器类(基于 std::chrono)供您考虑:

      #include <type_traits>
      #include <chrono>
      
      
      class Stopwatch final
      {
      public:
      
          using elapsed_resolution = std::chrono::milliseconds;
      
          Stopwatch()
          {
              Reset();
          }
      
          void Reset()
          {
              reset_time = clock.now();
          }
      
          elapsed_resolution Elapsed()
          {
              return std::chrono::duration_cast<elapsed_resolution>(clock.now() - reset_time);
          }
      
      private:
      
          std::chrono::high_resolution_clock clock;
          std::chrono::high_resolution_clock::time_point reset_time;
      };
      

      请注意,Windows std::chrono::high_resolution_clock 的底层使用的是 QueryPerformanceCounter,因此它是相同的但可移植的。

      【讨论】:

      • 您是少数没有立即跳出带有&lt;chrono&gt;.count() 类型系统并返回一个裸整数类型的人之一。为此+1! :-)
      • 我可以在商业项目中使用你的代码吗?
      • 你当然是。
      【解决方案6】:

      MSDN 声称 -

      Scenario 对象是记录 ETW 事件的高精度计时器 (Windows 的事件跟踪)当您启动和停止它时。它的设计 用于性能检测和基准测试,并附带 在 C# 和 C++ 版本中。 ... 作为现代的经验法则 硬件,对 Begin() 或 End() 的调用采用 微秒,得到的时间戳精确到 100ns(即 0.1 微秒)。 ...版本可用于 .NET 3.5(用 C# 编写)和本机 C++,并在 x86 和 x64 上运行 平台。 Scenario 类最初是使用 Visual 开发的 Studio 2008,但现在面向使用 Visual Studio 的开发人员 2010.]

      来自Scenario Home Page。据我所知,它是由与 PPL 相同的人提供的。

      另外你可以阅读这个High Resolution Clocks and Timers for Performance Measurement in Windows

      【讨论】:

        【解决方案7】:

        在较新的 Windows 版本中 you probably want GetSystemTimePreciseAsFileTime。见Acquiring high resolution timestamps

        这其中很多会因硬件和操作系统版本的不同而产生相当大的差异。

        【讨论】:

          【解决方案8】:

          可以按照 Konrad Rudolf 的建议使用性能计数器 API,但应注意它基于 CPU 频率。这个频率不稳定,例如启用了省电模式。如果要使用此 API,请确保 CPU 处于恒定频率。

          否则,您可以创建某种“统计”系统,将 CPU 滴答声与 PC BIOS 时钟相关联。后者不太精确,但始终如一。

          【讨论】:

            【解决方案9】:

            如果你可以使用Visual Studio 2012或更高版本的编译器,你可以很好地使用std::chrono标准库。

            #include <chrono>
            
            ::std::chrono::steady_clock::time_point time = std::chrono::steady_clock::now();
            

            注意MSVC 2012 version may be only 1ms accurate.较新的版本应该精确到微秒。

            【讨论】:

            • 为了更好的可移植性,您可能希望使用std::chrono::high_resolution_clock 代替(它可能与steady_clock 相同也可能不同,但至少在语义上它是一个更好的选择)。
            【解决方案10】:

            使用QueryPerformanceCounter(适用于 Windows)

            【讨论】:

              【解决方案11】:

              关于 Konrad Rudolph 的回答,请注意,根据我的经验,性能计数器的频率约为 3.7MHz,即亚微秒,但肯定不是纳秒精度。实际频率取决于硬件(和省电模式)。在任何情况下,纳秒精度都有些不合理,因为中断延迟和进程/线程上下文切换时间远比这要长,这也是单个机器指令的数量级。

              【讨论】:

              • 根据我的经验没有。见rdtsc x86 instruction。它读取每个 CPU 时钟周期更新的计数器。
              • @JonasByström:Windows 性能计数器 API 不使用 TSC,原因描述为 here。 TSC 的分辨率要高得多,但一致性较差。此外,此答案已有 5 年历史,其内容可能仍然有效,也可能无效。
              • @JonasByström:同样在this discussion 中,可以看出我电脑上的结果与讨论中的问题有很大不同。 YMMV。它完全依赖于硬件实现。
              • 你说得对,它是特定于实现的,但我只见过它使用 TSC 或更好的实现。对于比 P5(21 年前推出?)更新的任何东西来说,3.7MHz 似乎是一个非常低的数字。
              【解决方案12】:

              rdtsc instruction 最准确。

              【讨论】:

              【解决方案13】:

              这是一个适用于 Windows 和 Linux 的 Timer 类:

              #ifndef INCLUDE_CTIMER_HPP_
              #define INCLUDE_CTIMER_HPP_
              
              #if defined(_MSC_VER)
              #  define NOMINMAX // workaround a bug in windows.h
              #  include <windows.h>
              #else
              #  include <sys/time.h>
              #endif
              
              namespace Utils
              {
                 class CTimer
                 {
                 private:
              #     if defined(_MSC_VER)
                       LARGE_INTEGER m_depart;
              #     else
                       timeval m_depart;
              #     endif
              
                 public:
                    inline void start()
                    {
              #        if defined(_MSC_VER)
                          QueryPerformanceCounter(&m_depart);
              #        else
                          gettimeofday(&m_depart, 0);
              #        endif
                    };
              
                    inline float GetSecondes() const
                    {
              #        if defined(_MSC_VER)
                          LARGE_INTEGER now;
                          LARGE_INTEGER freq;
              
                          QueryPerformanceCounter(&now);
                          QueryPerformanceFrequency(&freq);
              
                          return (now.QuadPart - m_depart.QuadPart) / static_cast<float>(freq.QuadPart);
              #        else
                          timeval now;
                          gettimeofday(&now, 0);
              
                          return now.tv_sec - m_depart.tv_sec + (now.tv_usec - m_depart.tv_usec) / 1000000.0f;
              #        endif
                    };
                 };
              }
              #endif // INCLUDE_CTIMER_HPP_
              

              【讨论】:

                【解决方案14】:

                感谢您的意见...虽然我无法获得本来不错的纳米或微秒分辨率,但我能够想出这个...也许其他人会发现它有用。

                    class N_Script_Timer
                {
                    public:
                        N_Script_Timer()
                        {
                            running = false;
                            milliseconds = 0;
                            seconds = 0;
                            start_t = 0;
                            end_t = 0;
                        }
                        void Start()
                        {
                            if(running)return;
                            running = true;
                            start_t = timeGetTime();
                        }
                        void End()
                        {
                            if(!running)return;
                            running = false;
                            end_t = timeGetTime();
                            milliseconds = end_t - start_t;
                            seconds = milliseconds / (float)1000;
                        }
                        float milliseconds;
                        float seconds;
                
                    private:
                        unsigned long start_t;
                        unsigned long end_t;
                        bool running;
                };
                

                【讨论】:

                • 为了提高分辨率,您可能需要分别在Start()End() 中添加timeBeginPeriod(1)timeEndPeriod(1)
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2018-03-17
                • 1970-01-01
                • 2017-10-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多