Библиотека времени Chrono


О библиотеке

Для работы со временем в STD, начиная со стандарта C++11, появилась библиотека времени – Chrono. До выхода C++11 в STD была только одна библиотека работающая со временем – это Си-библиотека ctime. Она доступна и сейчас. Но время не стоит на месте. Новые скорости – новое время. Разработчики chrono хотели создать библиотеку решающую ряд недостатков сишной библиотеки, прежде всего – точность интервалов времени, часы, которые бы использовали такты времени, не связанные с системным временем. Библиотеку chrono составляют три основных типа:

  • Интервалы (duration)
  • Моменты (time_point)
  • Часы (clock)

Определение интервалов. Часы

В задачах мы ограничимся использованием часов system_clock – системные часы реального времени (всего же используются три типа часов; в C++20 будет добавлено ещё 6). Поскольку эти часы связаны с системными часами, то они могут быть не монотонными, так как системное время может быть в любой момент синхронизировано. Методов всего три:

  • now() – текущий момент времени
  • to_time_t() – преобразует момент времени в тип time_t
  • from_time_t() – преобразует тип time_t в момент времени системных часов
time_t – арифметический тип, представляет собой целочисленное значение – число секунд, прошедших с 00:00, 1 января 1970 UTC (POSIX время или Время UNIX).
Первая задача, которую нужно решить – это определение временного интервала работы определенного фрагмента программы. Идея проста – получаем первый момент времени до исследуемого фрагмента кода, а второй после. Вычисляем разницу обоих моментов и получаем возможность судить о скорости работы алгоритма.
Программа 1.

#include <iostream>
#include <chrono>
using namespace std;
using namespace chrono;

int main() {
	// Получаем момент времени_1
	system_clock::time_point start = system_clock::now();
	// Выполняем некоторый код
	for (long i = 1; i < 10000000000; i += 1);
	// Получаем момент времени_2
	system_clock::time_point end = system_clock::now();
	// Определяем тип объекта интервала и вычисляем его значение
	duration<double> sec = end - start;
	// вычисляем количество тактов в интервале
	// и выводим итог
	cout << sec.count() << " сек." << endl;
	return 0;
}

Возможный вывод программы:

13.7713 сек.

Чтобы получить текущий момент времени необходимо создать объект начального момента времени (start), инициализировав его значением, которое возвращает метод now(). Для system_clock – это текущий момент системного времени. В программе 1, для лаконичности определений, мы используем пространство имен chrono:

using namespace chrono;

Чтобы сделать выражение ещё более компактным, можно воспользоваться спецификатором auto:

auto start = system_clock::now();

Далее, определяем интервал, как разность двух моментов. В шаблонном параметре типа объекта duration указывается тип значения интервала. В данном случае – это действительное число.
Чтобы вывести итоговое значение нам нужно знать количество тактов. За такое вычисление берется метод count(), который и позволяет вывести окончательный результат. Но не всегда такой вывод может нас устроить. Что, если нам нужно определить интервалы с очень большой точностью, скажем в наносекундах? Такая необходимость возникает при тестировании быстрых алгоритмов. Chrono позволяет использовать на выводе различные единицы – от часов до наносекунд (в зависимости от величины интервала). Для изменения единиц времени используется формат преобразования в стиле C++ duration_cast<>, которому, в качестве шаблонного параметра, необходимо передать один из возможных типов перечисленных ниже:

  • std::chrono::nanoseconds
  • std::chrono::microseconds
  • std::chrono::milliseconds
  • std::chrono::seconds
  • std::chrono::minutes
  • std::chrono::hours

Изменим немножко код в самом конце:

cout << duration_cast<seconds>(end - start).count() << "\n"
	 << duration_cast<milliseconds>(end - start).count() << "\n"
	 << duration_cast<microseconds>(end - start).count() << "\n"
	 << duration_cast<nanoseconds>(end - start).count() << "\n";

Возможный вывод этого варианта программы:

13
13717
13717877
13717877315

Как вы уже поняли, к интервалам применяются арифметические операции: operator+, operator-, operator*, operator/, operator%. Используя операцию "%" вы можете разделить интервал на разные единицы (как это сделать см. [1, 179]). Также для интервалов доступны все известные операции сравнения.

Chrono и генератор случайных чисел

Переходим к следующему вопросу. Как передать в качестве начального значения генератору случайных чисел момент времени полученный в chrono, который будет создавать всегда новый набор случайных чисел с каждым запуском программы? На самом деле, тип time_point – это абстрактный тип основанный на std::ratio, таким образом, к фундаментальному типу его преобразовать не получится. Вот почему в программе 1 велись такие многоступенчатые преобразования. Следовательно, нам нужно сразу получить готовое число, представляющее некоторый интервал. Такая возможность имеется, если мы возьмем за начальный момент – начало Эпохи UNIX, а конечный, собственно, текущий момент системного времени. Чтобы не заниматься этими вычислениями самостоятельно, проще взять метод класса time_pointtime_since_epoch(), который возвращает количество тактов от начала эпохи до определенного момента (в данном случае момент системного времени). А затем использовать метод count() для получения нужного нам числа. В программе ниже используется генератор случайных чисел С++ (библиотека random):
Программа 2

#include <iostream>
#include <chrono>
#include <random>

using namespace std;
using namespace chrono;

int main() {
	long seed = system_clock::now().time_since_epoch().count();
	default_random_engine rnd(seed);
	uniform_int_distribution<int> d(1, 100);
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 10; j++)
			cout << d(rnd) << " ";
		cout << "\n";
	}
  return 0;
}

Возможный вывод программы:

99 54 90 40 16 57 19 43 87 73 
43 46 94 72 90 88 94 40 14 71 

Форматированный вывод даты/времени

А вот с форматированным выводом у chrono проблемы. Разработчики не посчитали нужным добавить человеко понятный вывод. Ничего не остается, как воспользоваться возможностями старой ctime. Но и это еще не все. Чтобы вывод соответствовал региональным традициям необходимо использовать библиотеку local. Собственно, форматирование вывода производит библиотека ios, которая перехватывает передающиеся ей от ctime данные для форматирования, а дальше, как говорится, дело техники. Нам нужно от chrono две вещи: текущий момент системного времени и, упомянутый выше, метод преобразования to_time_t(), который преобразовывает момент времени в тип time_t (с ним может работать ctime). В итоге, программный код форматирования даты/времени может быть представлен в следующем виде:
Программа 3

#include <chrono>
#include <ctime>   // localtime
#include <iomanip> // put_time
#include <iostream>

using namespace std;
using namespace chrono;

int main() {
	auto now = system_clock::now();
	auto my_time = system_clock::to_time_t(now);

	cout.imbue(locale("ru_RU.utf8"));
	cout << put_time(localtime(&my_time), "%c %Z")
		 << endl;

  return 0;
}

Возможный вывод программы:

Пт 25 янв 2019 23:31:52 MSK

В грядущем стандарте С++20 функции форматирования даты/времени в chrono всё же были заявлены [5].

Ссылки, литература
  1. Джосаттис Н. М. Стандартная библиотека C++: справочное руководство, 2-е изд.-М.: Вильямс, 2014
  2. https://stackoverflow.com/questions/17223096/outputting-date-and-time-in-c-using-stdchrono
  3. https://www.boost.org/doc/libs/1_69_0/doc/html/chrono.html
  4. https://ru.cppreference.com/w/cpp/io/manip/put_time
  5. https://en.cppreference.com/w/cpp/chrono/format

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.


Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.