Почему std::chrono::time_point недостаточно велик для хранения struct timespec?
Я пытаюсь последние 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 ответа
Это было сделано для максимальной гибкости и компактности. Если вам нужна сверхточная точность, вам обычно не нужен очень большой диапазон. А если вам нужен очень большой диапазон, вам обычно не нужна очень высокая точность.
Например, если вы торгуете наносекундами, вам нужно регулярно думать о более чем +/- 292 года? И если вам нужно подумать о диапазоне, превышающем этот, микросекунды дают вам +/- 292 тысячи лет.
MacOS system_clock
на самом деле возвращает микросекунды, а не наносекунды. Таким образом, эти часы могут работать 292 тысячи лет с 1970 года, пока они не переполнятся.
Окна system_clock
имеет точность 100 нс единиц, и поэтому имеет диапазон +/- 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
,
std::chrono::nanoseconds
это псевдоним типа для std::chrono::duration<some_t, std::nano>
где some_t
является int со знаком с объемом хранения не менее 64 бит. Это по-прежнему учитывает диапазон не менее 292 лет с точностью до наносекунды.
Примечательно, что единственными интегральными типами с такими характеристиками, указанными в стандарте, являются int
(|_fast
|_least
)64_t
семьи.
Вы можете выбрать более широкий тип для представления вашего времени, если ваша реализация предоставляет такой. Вы также можете предоставить пространство имен с кучей typedef, которые отражают std::chrono
отношения, с вашим более широким типом в качестве представления.