为什么std::chrono::time_point不足以存储struct timespec?

9
我正在尝试使用最新的std :: chrono API,并发现在64位Linux架构和gcc编译器上,time_point和duration类不能处理操作系统的最大时间范围以及最大分辨率(纳秒)。实际上,这些类的存储是一个64位整数类型,而timespec和timeval内部使用两个64位整数,一个用于秒,一个用于纳秒。
#include <iostream>
#include <chrono>
#include <typeinfo>
#include <time.h>

using namespace std;
using namespace std::chrono;

int main()
{
    cout << sizeof(time_point<nanoseconds>) << endl;                       // 8
    cout << sizeof(time_point<nanoseconds>::duration) << endl;             // 8
    cout << sizeof(time_point<nanoseconds>::duration::rep) << endl;        // 8
    cout << typeid(time_point<nanoseconds>::duration::rep).name() << endl; // l
    cout << sizeof(struct timespec) << endl;                               // 16
    cout << sizeof(struct timeval) << endl;                                // 16
    return 0;
}

在64位Windows(MSVC2017)上,情况非常相似:存储类型也是一个64位整数。当处理稳定(又名单调)时钟时,这不是问题,但存储限制使得不同的API实现不适合存储更大的日期和更广泛的时间跨度,从而为类似Y2K的错误创造了基础。这个问题被认可了吗?是否有改进实现或API改进的计划?
2个回答

15

这样做是为了让您获得最大的灵活性和紧凑的大小。如果您需要超精确度,通常不需要很大的范围。如果您需要非常大的范围,通常不需要非常高的精度。

例如,如果您要处理纳秒级别的数据,您是否经常需要考虑超过+/- 292年的时间范围?如果您需要考虑比那还要大的范围,那么微秒级别就可以给您提供+/- 292 年的时间范围。

macOS的system_clock实际上返回微秒而不是纳秒。因此,该时钟可以从1970年运行292千年直到溢出。

Windows的system_clock具有100 ns单位的精度,因此其范围为+/- 29.2千年。

如果几十万年仍然不够,请尝试使用毫秒级别。现在您的时间范围可达+/- 292 百万年。

最后,如果您真的需要在几百年内拥有纳秒级别的精度,<chrono>允许您自定义存储:

using dnano = duration<double, nano>;

这会给你以纳秒为单位存储的double类型。 如果您的平台支持128位整数类型,您也可以使用它:

using big_nano = duration<__int128_t, nano>;

如果你为 timespec 编写了重载的运算符,你甚至可以使用 来存储(但我不建议这样做)。

你也可以实现比纳秒更精细的精度,但这样做会牺牲范围。例如:

using picoseconds = duration<int64_t, pico>;

这个范围仅为+/- .292年(几个月)。所以,如果您有一个提供亚纳秒精度的源时钟,那么您必须小心使用它。但是如果你需要计时的话,非常适合。

查看此视频了解有关<chrono>更多信息。

创建、操作和存储日期的范围大于当前公历日历的有效性时,我创建了这个开源日期库,该库通过日历服务扩展了<chrono>库。该库将年份存储在带符号的16位整数中,因此具有+/- 32K年的范围。可以像这样使用它:

#include "date.h"

int
main()
{
    using namespace std::chrono;
    using namespace date;
    system_clock::time_point now = sys_days{may/30/2017} + 19h + 40min + 10s;
}

更新

在下面的评论中,有人问如何将duration<int32_t,nano>“规范化”为秒和纳秒(然后将秒添加到时间点)。

首先,我会谨慎地将纳秒装入32位。该范围仅略大于+/- 2秒。但以下是我分离单位的方法:

    using ns = duration<int32_t, nano>;
    auto n = ns::max();
    auto s = duration_cast<seconds>(n);
    n -= s;

请注意,这仅适用于n为正数。要正确处理负数n,最好的方法是:

    auto n = ns::max();
    auto s = floor<seconds>(n);
    n -= s;

std::floor是C++17中引入的。如果您希望更早地使用它,可以从这里这里获取。

我偏爱上面的减法操作,因为我认为它更易读。但是这也可以工作(如果n不是负数):

    auto s = duration_cast<seconds>(n);
    n %= 1s;

1s 是C++14中引入的。在C++11中,您将需要使用seconds {1}

一旦您有了秒(s),就可以将其添加到您的time_point中。


你的回答很有道理,谢谢。API中是否有一些通用的工具可以规范化time_point<days>duration<int32_t, nano>这样一起使用的情况?例如,确保在duration中不存在大于等于1秒的时间? - ceztko
@ceztko:抱歉,我不完全理解这个问题。如果以下回答不能解决问题,您能否重新表述一下?time_point<days> + duration<int32_t,nano>将给你一个time_point<int32_t,nano>(在2秒钟内会溢出)。 没有内置的溢出保护。 但是,如果你使用“safeint”库获取表示以获得溢出保护。 - Howard Hinnant
抱歉,示例实际上是错误的。我实际上是指标准化time_point<seconds>duration<int32_t,nano>:在这种情况下,我可能会将duration<int32_t,nano>转换为秒(向下取整),并加到time_point<seconds>中,并将持续时间重写为模一秒。只是想知道使用API执行此操作的最流畅方式是什么。 - ceztko
1
我会将这个加入到答案中。 - Howard Hinnant

0

std::chrono::nanoseconds是一个类型别名,用于表示std::chrono::duration<some_t, std::nano>,其中的some_t是带有至少64位存储的signed int。这仍然允许在纳秒精度下拥有至少292年的范围。

值得注意的是,标准中提到具有这种特征的唯一整数类型是int(|_fast|_least)64_t家族。

如果你的实现提供了更宽的类型来表示时间,你可以自由选择它。你还可以提供一个命名空间,其中包含与std::chrono比率相对应的一堆typedef,并将更宽的类型作为表示。


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