std::system_clock 和 std::steady_clock 之间的区别?

Posted

技术标签:

【中文标题】std::system_clock 和 std::steady_clock 之间的区别?【英文标题】:Difference between std::system_clock and std::steady_clock? 【发布时间】:2012-10-27 03:05:21 【问题描述】:

std::system_clockstd::steady_clock 有什么区别? (举例说明不同的结果/行为会很棒)。

如果我的目标是精确测量函数的执行时间(如基准测试),std::system_clockstd::steady_clockstd::high_resolution_clock 之间的最佳选择是什么?

【问题讨论】:

首先,system_clock 可能不稳定。 @CharlesSalvia 我不能代表其他平台,但system_clock 在 Windows 上不稳定。在 Windows 上,任何具有足够权限的用户都可以将系统时间更改为任意值。此外,如果需要,时间同步服务可以向后调整系统时间。我希望大多数其他平台具有允许调整系统时间的类似功能。 @Charles:我知道的大多数 POSIX 盒子都受到类似影响,如果用户更改时间,它们的时间也会发生变化。 这个问题的视频回答:youtube.com/watch?v=P32hvk8b13M&t=48m44s @CharlesSalvia。根据我自己分析数十个 PC 数据采集系统的时序输出的经验,来自计算机的时间并不稳定。 Linux、Windows 和使用的具体系统调用未知,但共同点是后续时间值之间经常出现负时间差。直线时间不是常态。 【参考方案1】:

来自 N3376:

20.11.7.1 [time.clock.system]/1:

system_clock 类的对象表示来自系统范围实时时钟的挂钟时间。

20.11.7.2 [time.clock.steady]/1:

steady_clock 类的对象表示时钟,time_point 的值不会随着物理时间的增加而减少,time_point 的值相对于实时以稳定的速率增加。也就是说,时钟可能无法调整。

20.11.7.3 [time.clock.hires]/1:

high_resolution_clock 类的对象表示具有最短滴答周期的时钟。 high_resolution_clock 可能是 system_clocksteady_clock 的同义词。

例如,系统范围的时钟可能会受到夏令时之类的影响,此时列出的未来某个时间点的实际时间实际上可能是过去的时间。 (例如在美国,秋季时间向后移动一小时,因此同一小时会经历“两次”)但是,steady_clock 不允许受到此类事情的影响。

在这种情况下考虑“稳定”的另一种方式是在 20.11.3 [time.clock.req]/2 的表中定义的要求:

在表 59 中,C1C2 表示时钟类型。 t1t2C1::now() 返回的值,其中返回t1 的调用发生在返回t2 的调用之前,并且这两个调用都发生在C1::time_point::max() 之前。 [注意:这意味着C1 没有在t1t2 之间环绕。 ——尾注]

表达式:C1::is_steady 返回:const bool 操作语义:true 如果t1 <= t2 始终为真并且时钟滴答之间的时间是恒定的,否则false

这就是标准对它们的区别的全部内容。

如果您想进行基准测试,最好的选择可能是std::high_resolution_clock,因为您的平台很可能使用高分辨率计时器(例如 Windows 上的 QueryPerformanceCounter)来设置此时钟。但是,如果您要进行基准测试,您应该真正考虑使用特定于平台的计时器来进行基准测试,因为不同的平台对此的处理方式不同。例如,某些平台可能会为您提供一些方法来确定程序所需的实际时钟滴答数(独立于在同一 CPU 上运行的其他进程)。更好的是,获得真正的分析器并使用它。

【讨论】:

@Charles:在标准中指出是这种情况吗?它似乎清楚地表明了相反的情况。 @Charles:另外,POSIX 时间不是“稳定的”——如果用户更改他们计算机上的时间设置,POSIX 时间将会改变。如果您正在煮鸡蛋,并且需要一个持续 4 分钟的计时器,那么即使当前时间已更改,您也需要它持续 4 分钟。如果您为 5 日 3 点的会议设置了计时器,那么如果当地时间发生变化,您绝对需要更改该计时器。因此steady_clocksystem_clock 的区别就在这里。 @5gon:没有什么要求system_clock 是UTC。 @CharlesSalvia 另请注意,由于 POSIX 时间与 UTC 相关,而 UTC 有闰秒(参见en.wikipedia.org/wiki/Unix_time#Leap_seconds)。这意味着即使机器上的时间从未调整过,C/POSIX 时间也可能是非单调的。 更新 (Visual Studio 2015) stable_clock 的实现已经改变 [.....] stable_clock 现在基于 QueryPerformanceCounter() 并且 high_resolution_clock 现在是 stable_clock 的 typedef。引用自msdn.microsoft.com/en-us/library/hh874757.aspx【参考方案2】:

Billy 根据我完全同意的 ISO C++ 标准提供了一个很好的答案。然而,故事还有另一面——现实生活。看来现在流行编译器的实现中这些时钟之间确实没有区别:

gcc 4.8:

#ifdef _GLIBCXX_USE_CLOCK_MONOTONIC
   ...
#else
  typedef system_clock steady_clock;
#endif
  typedef system_clock high_resolution_clock;

Visual Studio 2012:

class steady_clock : public system_clock
   // wraps monotonic clock
public:
  static const bool is_monotonic = true;    // retained
  static const bool is_steady = true;
;

typedef system_clock high_resolution_clock;

在 gcc 的情况下,您可以通过检查 is_steady 来检查您是否处理稳定时钟并采取相应的行为。但是 VS2012 似乎在这里有点作弊:-)

如果您需要高精度时钟,我建议您现在编写符合 C++11 官方时钟接口的自己的时钟并等待实现赶上。这将比直接在代码中使用特定于操作系统的 API 更好。 对于 Windows,您可以这样做:

// Self-made Windows QueryPerformanceCounter based C++11 API compatible clock
struct qpc_clock 
  typedef std::chrono::nanoseconds                       duration;      // nanoseconds resolution
  typedef duration::rep                                  rep;
  typedef duration::period                               period;
  typedef std::chrono::time_point<qpc_clock, duration>   time_point;
  static bool is_steady;                                                // = true
  static time_point now()
  
    if(!is_inited) 
      init();
      is_inited = true;
    
    LARGE_INTEGER counter;
    QueryPerformanceCounter(&counter);
    return time_point(duration(static_cast<rep>((double)counter.QuadPart / frequency.QuadPart *
                                                period::den / period::num)));
  

private:
  static bool is_inited;                                                // = false
  static LARGE_INTEGER frequency;
  static void init()
  
    if(QueryPerformanceFrequency(&frequency) == 0)
      throw std::logic_error("QueryPerformanceCounter not supported: " + std::to_string(GetLastError()));
  
;

对于 Linux,它更容易。只需阅读clock_gettime 的手册页并修改上面的代码即可。

【讨论】:

VC++ 2012 实现已被 MS 的标准库维护者确认为错误。 感兴趣的朋友,this is a link to that bug Boost 使用 QueryPerformanceCounter,因此在 Visual Studio 14 发布之前使用 boost::chrono 是一个很好的解决方法 以下是 GCC 5.3.0 上的 POSIX 调用:***.com/a/36700301/895245【参考方案3】:

GCC 5.3.0 实施

C++ 标准库在 GCC 源代码中:

high_resolution_clocksystem_clock 的别名 system_clock 转发到以下可用的第一个: clock_gettime(CLOCK_REALTIME, ...) gettimeofday time steady_clock 转发到以下可用的第一个: clock_gettime(CLOCK_MONOTONIC, ...) system_clock

然后CLOCK_REALTIME vs CLOCK_MONOTONIC 解释为:Difference between CLOCK_REALTIME and CLOCK_MONOTONIC?

【讨论】:

【参考方案4】:

Relevant talk about chrono by Howard Hinnant,chrono的作者:

不要使用high_resolution_clock,因为它是其中之一的别名:

system_clock: 就像一个普通的时钟,用于时间/日期相关的东西 steady_clock:就像秒表,用来计时。

【讨论】:

【参考方案5】:

也许,最显着的区别在于std::chrono:system_clock 的起点是 1.1.1970,即所谓的 UNIX 时代。 另一方面,std::chrono::steady_clock 通常是您 PC 的启动时间,它最适合测量间隔。

【讨论】:

以上是关于std::system_clock 和 std::steady_clock 之间的区别?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 std::round 提供返回 long 和 long long 的版本,而 std::floor、std::ceil、std::trunc 不提供?

`std::optional` 比 `std::shared_ptr` 和 `std::unique_ptr` 有啥优势?

为啥 std::future 从 std::packaged_task 和 std::async 返回不同?

理解std::move和std::forward

为啥我们需要绑定 std::cin 和 std::cout?

使用 `std::copy()` 和 `std::back_inserter()`