【问题标题】:Difference between steady_clock vs system_clock?稳定时钟与系统时钟之间的区别?
【发布时间】:2015-10-11 16:23:12
【问题描述】:

我试图通过查看数据的时间戳来查看我的数据是否存在 120 秒,因此我有以下代码:

uint64_t now = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
bool is_old = (120 * 1000 < (now - data_holder->getTimestamp()));

在上面的代码中,data_holder-&gt;getTimestamp() 是 uint64_t,它以毫秒为单位返回时间戳。

现在,当我打印出now 变量值时,我看到了这个10011360,而当我打印出data_holder-&gt;getTimestamp() 的值是1437520382241 时,现在和数据持有者时间戳之间的差异应该是负数吧?为什么它会像下面的日志中显示的那样积极?

2015-07-21 16:13:02,530 WARN 0x7f35312d1700 data_check - now value: 10011360 , data holder timestamp: 1437520382241 , difference: 18446742636199180735

我上面的代码看起来对吗?从上面的数据持有者时间戳来看,它看起来不是 120 秒的旧数据,所以我觉得我的代码有问题?因为如果我将该数据持有者的时间戳转换为实际时间(使用纪元转换器),然后将其与如上所示的日志时间进行比较,它几乎是相同的。

我正在使用steady_clock,如上图所示。我需要在这里使用system_clock 吗? steady_clocksystem_clock 在外行术语中有什么区别。我在 Ubuntu 14.04 机器上运行此代码。

【问题讨论】:

标签: c++ c++11 timestamp


【解决方案1】:

以相反的顺序回答问题:

steady_clocksystem_clock 之间的区别是什么? 条款。

如果你手里拿着system_clock,你会称它为手表,它会告诉你现在几点了。

如果你手里拿着steady_clock,你会称它为秒表,它会告诉你某人跑了一圈的速度,但它不会告诉你什么时间是的。

如果需要,您可以使用手表为某人跑一圈计时。但是,如果您的手表(如我的手表)定期与另一台机器(如 Boulder CO 的原子钟)通信以将其自身校正到当前时间,它可能会在计时该圈时出现小错误。秒表不会犯这个错误,但它也不能告诉你正确的当前时间。

我上面的代码看起来对吗?

没有。即使它给了你合理的答案,我也不会说它是正确的。不要难过,这是很多人在使用 &lt;chrono&gt; 库时犯的初学者错误。

&lt;chrono&gt; 库我遵循一个简单的规则。该规则实际上并不完全正确(因此它是一个准则)。但它已经足够接近纠正,成为几乎总是遵循的准则:

不要使用count()

还有一个推论:

不要使用time_since_epoch()

&lt;chrono&gt; 库是围绕类型安全 系统设计的,旨在保护您免受单位转换错误的影响。如果您不小心尝试了不安全的转换,则会在编译时捕获错误(而不是运行时错误)。

成员函数count()time_since_epoch() 是这种类型安全系统的“逃生舱”......仅在紧急情况下使用。当(例如)委员会忽略为&lt;chrono&gt; 类型提供完成工作所需的所有工具(例如 I/O)时,或者例如需要与其他一些计时 API 交互时,就会出现此类紧急情况通过整数。

查看您和其他人的代码以使用 count()time_since_epoch() 并仔细检查这些函数的每次使用:是否有任何方法可以重写代码以消除它们的使用?

查看代码的第一行:

uint64_t now = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();

now 是一个time_point(来自steady_clock)。它的单位是milliseconds,但此时我不相信这些单位很重要。重要的是now 是从steady_clock 检索到的time_point

auto now = steady_clock::now();

你的第二行更复杂:

bool is_old = (120 * 1000 < (now - data_holder->getTimestamp()));

让我们从data_holder-&gt;getTimestamp() 开始:如果您可以修改getTimestamp(),您应该修改它以返回time_point 而不是uint64_t。为此,您必须知道正确的单位(您所做的 - 毫秒),并且您必须知道正确的纪元。纪元是衡量您的毫秒数的时间点。

在这种情况下,1437520382241ms 大约是 45.6 年。假设这是最近的时间戳,45.6 年前非常接近 1970-01-01。事实证明,system_clock() 的每个实现都使用 1970-01-01 作为它的纪元(尽管每个实现从这个纪元计算不同的单位)。

因此要么修改getTimestamp() 以返回time_point&lt;system_clock, milliseconds&gt;,要么将getTimestamp() 的返回值包装为time_point&lt;system_clock, milliseconds&gt;

auto dh_ts = system_clock::time_point{milliseconds{data_holder->getTimestamp()}};

现在你的第二行是:

bool is_old = (120 * 1000 < (now - dh_ts));

另一个很好的指南:

如果您在 &lt;chrono&gt; 代码中看到转换因子,则说明您做错了。 &lt;chrono&gt; 活着为你你做转换。

bool is_old = (minutes{2} < (now - dh_ts));

下一步是风格化的,但现在你的代码很简单,如果你喜欢的话,可以去掉多余的括号:

bool is_old = minutes{2} < now - dh_ts;

如果您能够修改 getTimestamp() 以返回类型安全的值,则此代码也可能如下所示:

bool is_old = minutes{2} < now - data_holder->getTimestamp();

唉,不管怎样,这仍然无法编译!错误消息应说明nowdh_ts 之间没有有效的operator-()

这是一种类型安全系统,可帮助您避免运行时错误!

问题是system_clock 中的time_points 不能从steady_clock 中的time_points 中减去(因为两者具有不同的时期)。所以你必须切换到:

auto now = system_clock::now();

把它们放在一起:

#include <chrono>
#include <cstdint>
#include <memory>

struct DataHolder
{
    std::chrono::system_clock::time_point
    getTimestamp()
    {
        using namespace std::chrono;
        return system_clock::time_point{milliseconds{1437520382241}};
    }
};

int
main()
{
    using namespace std;
    using namespace std::chrono;
    auto data_holder = std::unique_ptr<DataHolder>(new DataHolder);

    auto now = system_clock::now();
    bool is_old = minutes{2} < now - data_holder->getTimestamp();
}

在 C++14 中,最后一行可以更简洁一些:

    bool is_old = 2min < now - data_holder->getTimestamp();

总结:

  • 拒绝使用count()(I/O 除外)。
  • 拒绝使用time_since_epoch()(I/O 除外)。
  • 拒绝使用转换系数(例如 1000)。
  • 与它争论,直到它编译为止。

如果您在上述四点上取得成功,您很可能不会遇到任何运行时错误(但您会得到应有的编译时错误)。

【讨论】:

  • 感谢您的精彩解释。让我们聊天here。我有几个基本问​​题。
  • 先生,我是个小菜鸟,对于愚蠢的问题,提前抱歉,但您的陈述 Refuse to use count() (except for I/O).Refuse to use time_since_epoch() (except for I/O) 。所以假设我正在使用一些时钟,我想知道从纪元开始的持续时间,如果没有time_since_epoch()count() 的相同问题,我该怎么做?
  • @AngelusMortis:如果没有更多关于您要解决的更大问题的背景信息,这个问题很难回答。但是我的总体观点是time_since_epoch()count() 等同于演员表。它们是强类型系统的逃生口。初学者倾向于过度使用这些功能,使类型系统无能为力。尽量保持在&lt;chrono&gt; 类型系统内,编译器会在编译时检测到你的逻辑错误(而不是你的逻辑错误编译并导致运行时错误)。
【解决方案2】:

首要任务

您看到正值的原因是无符号整数环绕。试试这个看看:

std::cout << static_cast <uint64_t> (-1) << std::endl;

getTimestamp() 返回的值是预期的吗?如果不是,如果没有看到getTimestamp() 的实现,就很难看出哪里出了问题。看来时间戳不是使用相同的时钟测量的。

稳定与系统时间

稳定的时钟最适合测量时间间隔。引用cppreference.com:

类 std::chrono::steady_clock 表示单调时钟。这个时钟的时间点不能随着物理时间的推移而减少。此时钟与挂钟时间无关,最适合测量间隔。

与 system_clock 不同,它不是单调的(例如,如果用户更改主机上的时间,时间可以减少。)

【讨论】:

    【解决方案3】:
    1. steady_clock 使用系统启动时间作为它的纪元,system_clock 使用 1970-1-1 00:00 作为它的时代,所以没有办法做任何数学 他们之间,这是没有意义的。

    2. 在 2 个无符号整数之间进行任何减法之前,请 确保被减数大于被减数。

    【讨论】:

      【解决方案4】:

      第一个问题,负整数会隐式转换 uint64_t 类型的整数,变成一个巨大的正整数。

      第二个问题,system_clock是系统范围的实时时钟,如果你修改了系统时间作为system_clock的返回时间改变。 stable_clock 是物理时间,无法更改。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 2020-09-03
          • 1970-01-01
          • 1970-01-01
          • 2019-04-01
          • 2019-08-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多