std::system_clock和std::steady_clock有什么区别?

114

什么是std::system_clockstd::steady_clock之间的区别?(一个展示不同结果/行为的例子将会很好)。

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


11
首先,系统时钟可能不稳定。 - James McNellis
@James McNellis,没错,但实际上system_clock只会在C++标准试图适应的幻想计算机上是不稳定的。问题是,system_clock映射到std::time_t值(C语言风格的时间),而我不知道任何平台(至少不包括非外来或古老的平台)的C std::time()函数不是与POSIX时间相同。而POSIX时间根据定义是“稳定的”。 - Charles Salvia
12
我不能代表其他平台,但 system_clock 在 Windows 上并不稳定。在Windows上,系统时间可能会被任何具备足够特权的用户更改为任意值。此外,时间同步服务可能会根据需要将系统时间向后调整。我预计大多数其他平台都有类似的功能允许调整系统时间。 - James McNellis
3
@Charles:我知道的大多数POSIX系统都会受到类似影响,如果用户更改时间,它们的时间也会随之改变。 - Billy ONeal
6
我可以翻译这段视频,并尽力使其内容更加通俗易懂,但不会添加任何解释或其他信息。以下是需要翻译的内容:在此问题的视频回答中:http://www.youtube.com/watch?v=P32hvk8b13M&t=48m44s - Howard Hinnant
1
根据我自己分析数十个PC数据采集系统的时间输出经验,计算机的时间并不稳定。Linux、Windows和使用的特定系统调用是未知的,但共同点是连续时间值之间频繁出现负时间差异。直线时间并不是常态。 - Tyson Hilmer
5个回答

81

来自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表示时钟类型。t1t2是由C1::now()返回的值,其中返回t1的调用发生在返回t2之前,这两个调用都发生在C1::time_point::max()之前。[注意:这意味着C1t1t2之间没有跨越。—注解结束]

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

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

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


10
@Charles:此外,POSIX时间并不是“稳定的”——如果用户更改了计算机上的时间设置,POSIX时间将会改变。例如,如果您正在煮鸡蛋,并需要持续4分钟的计时器,那么即使当前时间被更改,您也需要它持续4分钟。如果您为第5天下午3点的会议设置了一个计时器,那么如果当地时间发生变化,您绝对需要该计时器进行更改。因此,在这里steady_clocksystem_clock之间存在差异。 - Billy ONeal
2
@5gon:没有要求system_clock必须是UTC。 - Billy ONeal
1
请注意,由于POSIX时间与协调世界时(UTC)相结合,并且UTC拥有闰秒(参见https://en.wikipedia.org/wiki/Unix_time#Leap_seconds)。这意味着即使机器上的时间从未被调整过,C/POSIX时间可能是非单调的。 - Michael Schlottke-Lakemper
1
@anton_rh:没有这样的要求,即不调整夏令时的周期数。 - Billy ONeal
3
更新(Visual Studio 2015)steady_clock的实现已经改变[...]steady_clock现在基于QueryPerformanceCounter(),而high_resolution_clock现在是steady_clock的一个typedef。引自https://msdn.microsoft.com/en-us/library/hh874757.aspx。 - felix-b
显示剩余6条评论

51

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 的 man 页面并修改上面的代码即可。


20
MS的标准库维护者已经确认VC++ 2012的实现存在缺陷。 - ildjarn
5
对于有兴趣的人,这是该错误的链接 - Ben Voigt
1
Boost使用QueryPerformanceCounter,因此在Visual Studio 14发布之前,使用boost::chrono是解决此错误的好方法。 - Mohamed ElNakeep
这里是转发到GCC 5.3.0上的POSIX调用:https://dev59.com/q2ct5IYBdhLWcg3wCZMT#36700301 - Ciro Santilli OurBigBook.com
2
最新的 MS STL std::system_clock 包装了 GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime 函数,std::steady_clock/std::high_resolution_clock 包装了 QueryPerformanceCounter 函数。https://github.com/microsoft/STL/blob/2f03bdf361f7f153b4216c60a0d9491c0be13a73/stl/inc/__msvc_chrono.hpp#L638-L697 - KindDragon

25

注:MSVC:high_resolution_clock是std::chrono中steady_clock的别名,而steady_clock使用QueryPerformanceCounterQueryPerformanceFrequency。(适用于Visual Studio 2015和(可能)更高版本) - starriet

7

相关的关于chrono的演讲,演讲人是Howard Hinnant,也是chrono的作者:

不要使用high_resolution_clock,因为它是以下某一项的别名之一:

  • system_clock: 它像一个普通的时钟,用于处理与时间/日期相关的事情
  • steady_clock: 它就像一个秒表,用于计时。

6

或许最重要的不同之处就是,std::chrono:system_clock 的起始点是1970年1月1日被称为UNIX纪元时间。 而对于std::chrono::steady_clock来说,它通常是您个人电脑的启动时间,因此非常适合用于测量间隔。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接