【问题标题】:Fastest timing resolution system最快的时序解析系统
【发布时间】:2026-01-15 00:05:01
【问题描述】:

C/C++ 程序员可以使用的最快的计时系统是什么?

例如:
time() 将给出自 1970 年 1 月 1 日 00:00 以来的秒数。
Windows 上的 GetTickCount() 将给出自系统启动以来的时间(以毫秒为单位),但仅限于 49.7 天(之后它会简单地回零)。

我想获取当前时间,或自系统/应用启动时间以来的滴答声,以毫秒为单位。

最大的担忧是方法的开销 - 我需要最轻的一个,因为我将每秒多次调用它。

我的情况是我有一个工作线程,我向该工作线程发布待处理的作业。每个作业都有一个“执行时间”。所以,我不在乎时间是当前的“真实”时间还是系统正常运行以来的时间——它必须是线性的和轻量级的。

编辑:

unsigned __int64 GetTickCountEx()
{
    static DWORD dwWraps = 0;
    static DWORD dwLast = 0;

    DWORD dwCurrent = 0;

    timeMutex.lock();

    dwCurrent = GetTickCount();
    if(dwLast > dwCurrent)
        dwWraps++;

    dwLast = dwCurrent;

    unsigned __int64 timeResult = ((unsigned __int64)0xFFFFFFFF * dwWraps) + dwCurrent;

    timeMutex.unlock();

    return timeResult;
}

【问题讨论】:

  • 您是否预计在正常运行时间 > 49.7 天的系统上运行此程序?
  • 您是在专门询问 Windows 吗? Posix 平台有gettimeofday(),可以以微秒级精度覆盖数十年。
  • 抱歉不清楚 - 是的,我问的是 Windows。
  • 哇,winfroze!是你自己想出来的吗?
  • 是的,我做到了,但我从总部位于华盛顿州的一家软件公司获得了很多灵感。

标签: c++ c windows time


【解决方案1】:

对于计时,current Microsoft recommendation 是使用QueryPerformanceCounter & QueryPerformanceFrequency

这将为您提供优于毫秒的时间。如果系统不支持高分辨率计时器,则默认为毫秒(与GetTickCount 相同)。

Here is a short Microsoft article with examples of why you should use it :)

【讨论】:

  • 在第一个链接的第 1 条中,它说:“开发人员应尝试让他们的游戏尽可能少地调用 QueryPerformanceCounter 以避免任何性能损失”。那么,我是否应该得出结论说我最好使用 RDTSC?这听起来像是一个非常依赖于平台的调用。
  • 另外,MSDN 建议不要多次调用 QueryPerformanceCounter。那么,我还剩下 GetTickCount 了吗?
  • 那么,哪一个提供的开销更少?
  • RDTSC 可以说是最快的,因为它只是一条指令。 GetTickCountQueryPerformanceCounter 都会变慢(并且可能达到相同的程度),因为它们需要经历所有的系统调用开销。但同样,除非您的程序对时间非常关键,否则这种开销可能并不重要。
  • @Poni:根据上一篇文章,调用它的开销比其他计数器的分辨率要小,所以我不用担心。如果您阅读第一篇文章,您会看到"[QueryPerformanceCounter] is an API that can be called several hundred times per frame without any noticeable impact"——请记住,这也是 5 年前的事了。 (第一篇文章还详细说明了您可能不想使用 RDTSC 的原因。)
【解决方案2】:

我最近有这个问题并做了一些研究。好消息是所有三个主要操作系统都提供了某种高分辨率计时器。坏消息是它在每个系统上都有不同的 API 调用。对于 POSIX 操作系统,您要使用 clock_gettime()。但是,如果您使用的是 Mac OS X,则不支持此功能,您必须使用 mach_get_time()。对于 Windows,请使用 QueryPerformanceCounter。或者,对于支持 OpenMP 的编译器,您可以使用 omp_get_wtime(),但它可能无法提供您正在寻找的分辨率。

我还发现 fftw.org (www.fftw.org/cycle.h) 中的 cycle.h 很有用。

这里有一些代码在每个操作系统上调用一个计时器,使用一些丑陋的#ifdef 语句。用法很简单:定时器t; t.tic();一些操作(); t.toc("消息");它会以秒为单位打印出经过的时间。

#ifndef TIMER_H
#define TIMER_H

#include <iostream>
#include <string>
#include <vector>

# if  (defined(__MACH__) && defined(__APPLE__))
#   define _MAC
# elif (defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(_WIN64))
#   define _WINDOWS
#   ifndef WIN32_LEAN_AND_MEAN
#     define WIN32_LEAN_AND_MEAN
#   endif
#endif

# if defined(_MAC)
#    include <mach/mach_time.h>
# elif defined(_WINDOWS)
#    include <windows.h>
# else
#    include <time.h>
# endif


#if defined(_MAC)
  typedef uint64_t timer_t;
  typedef double   timer_c;

#elif defined(_WINDOWS)
  typedef LONGLONG      timer_t;
  typedef LARGE_INTEGER timer_c;

#else
  typedef double   timer_t;
  typedef timespec timer_c;
#endif

  //==============================================================================
  // Timer
  // A quick class to do benchmarking.
  // Example: Timer t;  t.tic();  SomeSlowOp(); t.toc("Some Message");

  class Timer {
  public:
    Timer();

    inline void tic();
    inline void toc();
    inline void toc(const std::string &msg);

    void print(const std::string &msg);
    void print();
    void reset();
    double getTime();

  private:
    timer_t start;
    double duration;
    timer_c ts;
    double conv_factor;
    double elapsed_time;
  };



  Timer::Timer() {

#if defined(_MAC)
    mach_timebase_info_data_t info;
    mach_timebase_info(&info);

    conv_factor = (static_cast<double>(info.numer))/
                  (static_cast<double>(info.denom));
    conv_factor = conv_factor*1.0e-9;

#elif defined(_WINDOWS)
    timer_c freq;
    QueryPerformanceFrequency(&freq);
    conv_factor = 1.0/(static_cast<double>freq.QuadPart);

#else
    conv_factor = 1.0;
#endif

    reset();
  }

  inline void Timer::tic() {

#if defined(_MAC)
    start = mach_absolute_time();

#elif defined(_WINDOWS)
    QueryPerformanceCounter(&ts);
    start = ts.QuadPart;

#else
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts);
    start = static_cast<double>(ts.tv_sec) + 1.0e-9 *
            static_cast<double>(ts.tv_nsec);

#endif
  }

  inline void Timer::toc() {
#if defined(_MAC)
    duration =  static_cast<double>(mach_absolute_time() - start);

#elif defined(_WINDOWS)
    QueryPerformanceCounter(&qpc_t);
    duration = static_cast<double>(qpc_t.QuadPart - start);

#else
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts);
    duration = (static_cast<double>(ts.tv_sec) + 1.0e-9 *
                static_cast<double>(ts.tv_nsec)) - start;

#endif

    elapsed_time = duration*conv_factor;
  }

  inline void Timer::toc(const std::string &msg) { toc(); print(msg); };

  void Timer::print(const std::string &msg) {
    std::cout << msg << " "; print();
  }

  void Timer::print() {
    if(elapsed_time) {
      std::cout << "elapsed time: " << elapsed_time << " seconds\n";
    }
  }

  void Timer::reset() { start = 0; duration = 0; elapsed_time = 0; }
  double Timer::getTime() { return elapsed_time; }


#if defined(_WINDOWS)
# undef WIN32_LEAN_AND_MEAN
#endif

#endif // TIMER_H

【讨论】:

  • 不错,对测试很有用!至于在项目中使用它 - 我试图避免 QPC,因为它不是 100% 可靠的,正如一些人所说的那样。
  • 我找到了关于 mach_absolute_time 的更多信息,在 mach/mach_time.h 中声明,请参阅此技术developer.apple.com/library/mac/#qa/qa1398/_index.html
【解决方案3】:

GetSystemTimeAsFileTime 是最快的资源。可以得到它的粒度 通过调用GetSystemTimeAdjustment 填充lpTimeIncrement。作为文件时间的系统时间有 100ns 单位,并以 TimeIncrement 递增。 TimeIncrement 会有所不同,具体取决于多媒体定时器界面的设置。

致电timeGetDevCaps 将披露时间服务的功能。它返回 支持的最小中断周期的值 wPeriodMin。使用 wPeriodMin 作为参数调用timeBeginPeriod 将设置系统以可能的最高中断频率(通常为~1ms)运行。这将强制GetSystemTimeAsFileTime 返回的系统文件时间的时间增量更小。其粒度将在 1ms 范围内(10000 100ns 单位)。

出于您的目的,我建议采用这种方法。

QueryPerformanceCounter 的选择是有问题的,因为它的频率不是 通过两种方式准确:首先,它与QueryPerformanceFrequency 给出的值相差一个硬件特定的偏移量。这个偏移量可以很容易地 几百 ppm,这意味着转换为时间将包含每秒数百微秒的误差。其次,它具有热漂移。这种设备的漂移很容易达到几 ppm。这样另一个 - 热依赖 - 错误 添加了几个 us/s。

所以只要~1ms的分辨率就足够了,主要问题是开销, GetSystemTimeAsFileTime 是迄今为止最好的解决方案。

当微秒很重要时,您必须走更远的路才能看到更多细节。亚毫秒时间服务在Windows Timestamp Project

【讨论】:

  • GetSystemTimeAsFileTime 可能很危险,因为可以调整返回的时间以纠正漂移。这意味着如果你在两个时间点调用它,两个值之间的增量可能与实际经过的时间有很大不同,因为可能已经发生了时间调整......
  • @aggieNick02:它反映了“本地时钟”上经过的时间,无论是否进行调整。注意:什么是“实际时间”?以及它的进展?
  • 抱歉,我不太明白。 GetSystemTimeAdjustment 返回有关如何调整时间时钟以处理时钟漂移的信息。在某些模式下,对 GetSystemTimeAsFileTime 的两次调用(相隔一秒)可能会返回相隔一秒以上的时间。 GetSystemTimeAsFileTime 不是单调的,除非配置得当 - 它不适合测量时间增量。
【解决方案4】:

如果你只是担心GetTickCount() 溢出,那么你可以像这样包装它:

DWORDLONG GetLongTickCount(void)
{
    static DWORDLONG last_tick = 0;
    DWORD tick = GetTickCount();

    if (tick < (last_tick & 0xffffffff))
        last_tick += 0x100000000;

    last_tick = (last_tick & 0xffffffff00000000) | tick;
    return last_tick;
}

如果您想从多个线程调用它,您需要锁定对last_tick 变量的访问。只要您每 49.7 天至少调用一次GetLongTickCount(),它就会检测到溢出。

【讨论】:

    【解决方案5】:

    如果您专门针对 Windows,我建议您使用 GetSystemTimeAsFileTime API。它通常比GetSystemTime 更快,并且具有相同的精度(大约 10-15 毫秒 - 不要看分辨率);几年前我在 Windows XP 下进行基准测试时,它的速度提高了 50-100 倍。

    唯一的缺点是您可能必须使用例如将返回的 FILETIME 结构转换为时钟时间。 FileTimeToSystemTime 如果您需要以更人性化的格式访问返回的时间。另一方面,只要您不需要实时转换的时间,您始终可以离线或以“懒惰”的方式执行此操作(例如,仅转换您需要显示/处理的时间戳,并且仅当您真正需要它们时)。

    QueryPerformanceCounter 可能是其他人提到的一个不错的选择,但根据底层硬件支持,开销可能会相当大。在我上面提到的基准测试中,QueryPerformanceCounter 调用比对 GetSystemTimeAsFileTime 的调用慢 25-200 倍。此外,还有一些可靠性问题,例如举报here

    因此,总而言之:如果您可以处理 10-15 毫秒的精度,我建议您使用 GetSystemTimeAsFileTime。如果您需要比这更好的东西,我会选择 QueryPerformanceCounter。

    免责声明:我没有在 XP SP3 之后的 Windows 版本下执行任何基准测试。我建议你自己做一些基准测试。

    【讨论】:

    【解决方案6】:

    在 Linux 上你得到微秒:

    struct timeval tv;
    int res = gettimeofday(&tv, NULL);
    double tmp = (double) tv.tv_sec + 1e-6 * (double) tv.tv_usec;
    

    在 Windows 上,只有毫秒可用:

    SYSTEMTIME st;
    GetSystemTime(&st);
    tmp += 1e-3 * st.wMilliseconds;
    
    return tmp;
    

    这来自R's datetime.c(为简洁起见已被删减)。

    当然还有Boost's Date_Time,它在某些系统上可以达到纳秒级分辨率(详情herehere)。

    【讨论】:

      【解决方案7】:

      如果您的目标是足够晚的操作系统版本,那么您可以使用GetTickCount64(),它的环绕点比GetTickCount() 高得多。您也可以在GetTickCount() 之上简单地构建GetTickCount64() 的版本。

      【讨论】:

      • 做到了,但它迫使我用互斥锁包装代码,这似乎并不完全正确。为什么?因为最终,我认为,我使用了两个互斥体——一个是包装器,一个是 GetTickCount()。请参阅我的编辑。告诉我你的想法,谢谢 Len!
      • 我不会那样做的。我有一个计时器可以在适当的时间增加 ULONGLONG 的高位部分,可能使用无锁的互锁增量,然后只需在调用 GetTickCountEx() 时使用它。现在,您正在做 49.7 天的不必要工作,并同步所有使用它的线程(这是一个暴击而不是互斥锁?)您可能可以避免锁定,或者使用读取器/写入器锁并且只锁定当您要更新高位时...您可以使用我的“tick shifter”API拦截工具来测试代码lenholgate.com/archives/000647.html
      • 好点莱恩。我将不得不避免锁定(是的,这是一个关键部分)。增加高位的计时器是个好主意。
      【解决方案8】:

      您查看过这篇 MSDN 文章中的代码吗?

      http://msdn.microsoft.com/en-us/magazine/cc163996.aspx

      我在使用 VC2005 和 C++ Builder XE 的 Windows 7 64 位机器上编译了这段代码,但是在执行时,它锁定了我的机器;还没有调试到足以找出原因。似乎过于复杂。 UG的模板模板...

      【讨论】:

        【解决方案9】:

        在 Mac OS X 上,您可以简单地使用 UInt32 TickCount (void) 来获取刻度。

        【讨论】:

          【解决方案10】:

          POSIX 支持clock_gettime(),它使用具有纳秒分辨率的struct timespec。您的系统是否真的支持细粒度的分辨率更有争议,但我相信这是具有最高分辨率的标准调用。并非所有系统都支持它,而且它有时被很好地隐藏(Solaris 上的库“-lposix4”,IIRC)。


          更新(2016-09-20):

          • Mac OS X 10.6.4 不支持clock_gettime(),任何其他版本的 Mac OS X 也不支持(包括 Mac OS X 10.11.6 El Capitan)。但是,从 macOS Sierra 10.12(2016 年 9 月发布)开始,macOS 终于有了函数clock_gettime() 和它的手册页。实际分辨率(CLOCK_MONOTONIC)仍然是微秒;较小的单位全为零。 clock_getres() 证实了这一点,报告分辨率为 1000 纳秒,即 1 µs。

          macOS Sierra 上 clock_gettime() 的手册页提到 mach_absolute_time() 作为获得高分辨率计时的一种方式。有关更多信息,请参阅Technical Q&A QA1398: Mach Absolute Time Units 和(在 SO)What is mach_absolute_time() based on on iPhone?

          【讨论】: