为什么 std::chrono::time_point 不够大,无法存储 struct timespec?
Why std::chrono::time_point is not large enough to store struct timespec?
我正在尝试最近的 std::chrono
api,我发现在 64 位 Linux 架构和 gcc 编译器上 time_point
和 duration
类 无法以最高分辨率(纳秒)处理操作系统的最大时间范围。事实上,与 timespec
和 timeval
相比,这些 类 的存储似乎是一种 64 位整数类型,它们在内部使用两个 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 改进的计划?
这样做是为了让您获得最大的灵活性和紧凑的尺寸。如果您需要超精细的精度,通常不需要非常大的范围。而如果你需要非常大的范围,你通常不需要非常高的精度。
例如,如果您以纳秒为单位进行交易,您是否经常需要考虑超过 +/- 292 年?如果你需要考虑一个更大的范围,那么微秒给你 +/- 292 千 年。
macOS system_clock
实际上 returns 微秒,而不是纳秒。所以那个时钟可以运行从1970年开始29.2万年直到它溢出。
Windows system_clock
的精度为 100-ns 单位,因此范围为 +/- 29200 年。
几十万年还不够,试试毫秒。现在你的寿命范围为 +/- 292 百万 年。
最后,如果您只是 将纳秒级精度保持在几百年以上,<chrono>
也允许您自定义存储:
using dnano = duration<double, nano>;
这给你纳秒存储为 double
。如果您的平台支持 128 位整数类型,您也可以使用它:
using big_nano = duration<__int128_t, nano>;
哎呀,如果你为 timespec
编写重载运算符,你甚至可以使用 that 进行存储(虽然我不推荐它)。
您也可以达到比纳秒更精细的精度,但这样做会牺牲范围。例如:
using picoseconds = duration<int64_t, pico>;
这个范围只有 +/- .292 年(几个月)。所以你 do 必须小心。如果您有一个可以提供亚纳秒级精度的源时钟,那么非常适合计时。
查看 this video 了解有关 <chrono>
的更多信息。
为了创建、操作和存储范围大于当前公历有效期的日期,我创建了这个 open-source date library,它使用日历服务扩展了 <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;
}
更新
在下面的评论中,问题是如何将 "normalize" duration<int32_t, nano>
转换为秒和纳秒(然后将秒添加到 time_point)。
首先,我会谨慎地将纳秒填充到 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 中引入的。如果你早点想要它,你可以从 here or here.
中获取它
我偏爱上面的减法运算,因为我觉得它更具可读性。但这也有效(如果 n
不是负数):
auto s = duration_cast<seconds>(n);
n %= 1s;
C++14 引入了1s
。在 C++11 中,您将不得不使用 seconds{1}
代替。
一旦你有秒数 (s
),你可以将其添加到你的 time_point
。
std::chrono::nanoseconds
是 std::chrono::duration<some_t, std::nano>
的类型别名,其中 some_t
是一个至少有 64 位存储空间的有符号整数。这仍然允许至少 292 年的范围和纳秒精度。
值得注意的是,标准提到的唯一具有此类特征的整数类型是 int
(|_fast
|_least
)64_t
系列。
如果您的实现提供了一种类型,您可以自由选择更广泛的类型来表示您的时间。您可以进一步自由地提供一个名称空间,其中包含一堆反映 std::chrono
比率的 typedef,以您的更广泛的类型作为表示。
我正在尝试最近的 std::chrono
api,我发现在 64 位 Linux 架构和 gcc 编译器上 time_point
和 duration
类 无法以最高分辨率(纳秒)处理操作系统的最大时间范围。事实上,与 timespec
和 timeval
相比,这些 类 的存储似乎是一种 64 位整数类型,它们在内部使用两个 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 改进的计划?
这样做是为了让您获得最大的灵活性和紧凑的尺寸。如果您需要超精细的精度,通常不需要非常大的范围。而如果你需要非常大的范围,你通常不需要非常高的精度。
例如,如果您以纳秒为单位进行交易,您是否经常需要考虑超过 +/- 292 年?如果你需要考虑一个更大的范围,那么微秒给你 +/- 292 千 年。
macOS system_clock
实际上 returns 微秒,而不是纳秒。所以那个时钟可以运行从1970年开始29.2万年直到它溢出。
Windows system_clock
的精度为 100-ns 单位,因此范围为 +/- 29200 年。
几十万年还不够,试试毫秒。现在你的寿命范围为 +/- 292 百万 年。
最后,如果您只是 将纳秒级精度保持在几百年以上,<chrono>
也允许您自定义存储:
using dnano = duration<double, nano>;
这给你纳秒存储为 double
。如果您的平台支持 128 位整数类型,您也可以使用它:
using big_nano = duration<__int128_t, nano>;
哎呀,如果你为 timespec
编写重载运算符,你甚至可以使用 that 进行存储(虽然我不推荐它)。
您也可以达到比纳秒更精细的精度,但这样做会牺牲范围。例如:
using picoseconds = duration<int64_t, pico>;
这个范围只有 +/- .292 年(几个月)。所以你 do 必须小心。如果您有一个可以提供亚纳秒级精度的源时钟,那么非常适合计时。
查看 this video 了解有关 <chrono>
的更多信息。
为了创建、操作和存储范围大于当前公历有效期的日期,我创建了这个 open-source date library,它使用日历服务扩展了 <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;
}
更新
在下面的评论中,问题是如何将 "normalize" duration<int32_t, nano>
转换为秒和纳秒(然后将秒添加到 time_point)。
首先,我会谨慎地将纳秒填充到 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 中引入的。如果你早点想要它,你可以从 here or here.
我偏爱上面的减法运算,因为我觉得它更具可读性。但这也有效(如果 n
不是负数):
auto s = duration_cast<seconds>(n);
n %= 1s;
C++14 引入了1s
。在 C++11 中,您将不得不使用 seconds{1}
代替。
一旦你有秒数 (s
),你可以将其添加到你的 time_point
。
std::chrono::nanoseconds
是 std::chrono::duration<some_t, std::nano>
的类型别名,其中 some_t
是一个至少有 64 位存储空间的有符号整数。这仍然允许至少 292 年的范围和纳秒精度。
值得注意的是,标准提到的唯一具有此类特征的整数类型是 int
(|_fast
|_least
)64_t
系列。
如果您的实现提供了一种类型,您可以自由选择更广泛的类型来表示您的时间。您可以进一步自由地提供一个名称空间,其中包含一堆反映 std::chrono
比率的 typedef,以您的更广泛的类型作为表示。