Aus Linux-Magazin 04/2016

Modernes C++ in der Praxis – Folge 27

Die neue Zeitbibliothek ist ein elementarer Baustein nicht nur für die Mulithreading-Fähigkeit von C++. Mit ihrer Hilfe legt der Entwickler einen Thread bis zu einem definierten Zeitpunkt schlafen oder fordert auf gut Glück ein Lock für eine Zeitspanne an.

Neben ihrem Einsatz mit Threads und Locks bietet sich die Zeitbibliothek auch als Werkzeug für einfache Performance-Messungen an. Wer sie nutzen will, sollte sich allerdings zunächst mit der Theorie beschäftigen.

Zeitfrage

Ein so intuitives Konzept wie die Zeit offenbart erst auf den zweiten Blick seine immanente Komplexität. Die spiegelt sich in den Begriffen Zeitpunkt, Zeitdauer und Zeitgeber wider, die einer knappen Klärung bedürfen:

  • Zeitpunkt: Ein Zeitpunkt setzt sich aus einem Startpunkt, auch als Epoche bezeichnet, sowie einer darauf bezogenen Zeitdauer zusammen.
  • Zeitdauer: Die Zeitdauer ist die Differenz zwischen zwei Zeitpunkten. Sie lässt sich als Anzahl von Zeittakten messen.
  • Zeitgeber: Ein Zeitgeber besteht aus einem Startpunkt und einem Zeittakt. Über sie bestimmt ein Programmierer den aktuellen Zeitpunkt.

Zeitpunkte eignen sich für Vergleiche. Wer eine Zeitdauer zu einem Zeitpunkt addiert, erzeugt einen neuen Zeitpunkt. Der Zeittakt spiegelt die Granularität des Zeitgebers wider und dient als Messeinheit der Zeitdauer. In unserem Kulturkreis wäre etwa Christi Geburt der Startpunkt, als typischer Zeittakt gilt ein Jahr. Abbildung 1 erklärt die Konzepte am Beispiel einer fiktiven Person, die 1833 geboren wurde und 1863 ihren 30. Geburtstag feierte.

Abbildung 1: Das Konzept hinter den Begriffen Zeitpunkt, Zeitdauer und Zeittakt an einem Beispiel.

Abbildung 1: Das Konzept hinter den Begriffen Zeitpunkt, Zeitdauer und Zeittakt an einem Beispiel.

Den Startpunkt Christi Geburt sowie die Zeitdauer im Jahrestakt definieren die Zeitpunkte 1833 und 1863. Natürlich ist auch der Startpunkt ein Zeitpunkt. Wer das Jahr 1833 vom 1863 abzieht, erhält die Zeitdauer. Nach diesem allgemeinen Einstieg, folgen die Details im Bezug auf C++. Hier besitzt jeder der genannten Begriffe ein entsprechendes Pendant [1].

Zeitpunkt

Den Zeitpunkt legen ein Startpunkt (die erwähnte Epoche) und die darauf bezogene Zeitdauer fest (Listing 1). Dabei enthält ein Zeitpunkt einen Zeitgeber (Clock) und eine Zeitdauer (Duration). Letztere darf negativ oder positiv sein. Für die Zeitgeber stellt C++ die Klassen »std::chrono::system_clock« , »std::chrono::steady_clock« und »std:: chrono::high_resolution_clock« bereit. Mit ihnen berechnet der Chronologe in Listing 2 die Zeitdauer seit dem 1. Januar 1970 in mehreren Zeittakten.

Listing 1

Zeitpunkt

01 template <class Clock,class Duration= typename Clock::duration>
02 class time_point;

Listing 2

Zeitdauer-Varianten

01 #include <chrono>
02 #include <iostream>
03
04 int main(){
05
06   std::cout << std::fixed  std::endl;
07
08   auto timeNow= std::chrono::system_clock::now();
09   auto duration= timeNow.time_since_epoch();
10
11   std::cout << "time since 1.1.1970:" << std::endl;
12   std::cout << std::endl;
13
14   std::cout << duration.count() << " nanoseconds" << std::endl;
15
16   typedef std::chrono::duration<double> MySecondTick;
17   MySecondTick mySecond(duration);
18   std::cout << mySecond.count() << " seconds" << std::endl;
19
20   const int minute= 60;
21   typedef std::chrono::duration<double, std::ratio<minute>> MyMinuteTick;
22   MyMinuteTick myMinute(duration);
23   std::cout << myMinute.count() << " minutes" << std::endl;
24
25   const int hour= minute * 60;
26   typedef std::chrono::duration<double, std::ratio<hour>> MyHourTick;
27   MyHourTick myHour(duration);
28   std::cout << myHour.count() << " hours" << std::endl;
29
30   const int day= hour * 24;
31   typedef std::chrono::duration<double, std::ratio<day>> MyDayTick;
32   MyDayTick myDay(duration);
33   std::cout << myDay.count() << " days" << std::endl;
34
35   const int month= day * 30;
36   typedef std::chrono::duration<double, std::ratio<month>> MyMonthTick;
37   MyMonthTick myMonth(duration);
38   std::cout << myMonth.count() << " months" << std::endl;
39
40   const int year= month * 12;
41   typedef std::chrono::duration<double, std::ratio<year>> MyYearTick;
42   MyYearTick myYear(duration);
43   std::cout << myYear.count() << " years" << std::endl;
44
45   typedef std::chrono::duration<double, std::ratio<3600>> MyHourTick;
46   MyHourTick myOneHour(duration);
47   std::cout << myOneHour.count() << " hours" << std::endl;
48
49   typedef std::chrono::duration<double, std::ratio<2700>> MyLessonTick;
50   MyLessonTick myLesson(duration);
51   std::cout << myLesson.count() << " lessons" << std::endl;
52
53   std::cout << std::endl;
54
55 }

In Zeile 8 ermittelt der Ausdruck »timeNow.time_since_epoch()« die Zeitdauer seit dem 1.1.1970 im Falle des Zeitgebers »std::chrono::system_clock« . Das Programm rechnet den Wert in die Zeittakte Nanosekunde (Zeile 14), Sekunde (Zeile 18), Minute (Zeile 23), Stunde (Zeile 28), Tag (Zeile 33), Monat (Zeile 38) und Jahr (Zeile 43) um. Der Entwickler definiert zudem eigene Zeittakte:

typedef std::chrono::duration<double,  std::ratio<2700>> MyLessonTick

Der Datentyp »MyLessonTick« gibt Werte im Drei-Viertelstunden-Takt aus.

Der Datentyp »std::ratio« steht für eine Bruchzahl. Standardmäßig erhalten Nenner und Zähler den Wert 1. Beispielsweise steht der Ausdruck »std::ratio<2700>« für »std::ratio<2700,1>« . Dieser Wert repräsentiert 2700 Sekunden, denn die Grundeinheit sind Sekunden. 2700 Sekunden sind 45 Minuten und entsprechen damit der Länge einer klassischen Schulstunde (Zeile 49). Abbildung 2 zeigt die Ausgabe des Programms.

Abbildung 2: Die seit dem 1. Januar 1970 verstrichene Zeit, dargestellt in verschiedenen Zeittakten.

Abbildung 2: Die seit dem 1. Januar 1970 verstrichene Zeit, dargestellt in verschiedenen Zeittakten.

Zeitdauer

Bei der Zeitdauer handelt es sich um die Differenz zwischen zwei Zeitpunkten, wiedergegeben in der Zahl der Zeittakte (Listing 3). Ist »Rep« eine Fließkommazahl, unterstützt die Zeitdauer auch Bruchteile des Zeittakts. Standardmäßig liegt der Zeittakt jedoch bei einer Sekunde.

Listing 3

Zeitdauer

01 template <class Rep, class Period = ratio<1>> class duration;

Die wichtigsten Zeitdauer-Typen definiert die Bibliothek bereits, die Details zum Code lassen sich in Listing 4 nachschauen.

Listing 4

Zeitdauer-Typen

01 typedef duration<signed int, nano> nanoseconds;
02 typedef duration<signed int, micro> microseconds;
03 typedef duration<signed int, milli> milliseconds;
04 typedef duration<signed int> seconds;
05 typedef duration<signed int, ratio< 60>> minutes;
06 typedef duration<signed int, ratio<3600>> hours;

Der C++-Standard sichert zu, dass eine Zeitdauer mindestens +/-292 Jahre umfasst. Wer die Zeitdauer in natürlichen Zahlen angeben möchte, muss diese explizit über »std::chrono::duration_cast« in Fließkommazahlen konvertieren. Dabei schneidet C++ den Wert ab. Wie lange ist eine Sekunde? Diese Frage beantwortet Listing 5 in verschiedenen Variationen.

Listing 5

Sekundenvariationen

01 #include <chrono>
02 #include <iostream>
03 #include <ratio>
04
05 int main(){
06
07   std::cout << std::endl;
08
09   typedef std::chrono::duration<long long, std::ratio<1>> MySecondTick;
10
11   MySecondTick aSecond(1);
12
13   std::chrono::nanoseconds nano(aSecond);
14   std::cout << nano.count() << " nanoseconds" << std::endl;
15
16   std::chrono::microseconds micro(aSecond);
17   std::cout << micro.count() << " microseconds" << std::endl;
18
19   std::chrono::milliseconds milli(aSecond);
20   std::cout << milli.count() << " milliseconds" << std::endl;
21
22   std::chrono::seconds seconds(aSecond);
23   std::cout << seconds.count() << " seconds" << std::endl;
24
25   // std::chrono::minutes minutes(aSecond);
26   std::chrono::minutes minutes(std::chrono::duration_cast<std::chrono::minutes>(aSecond));
27   std::cout << minutes.count() << " minutes(truncated value)" << std::endl;
28
29   //std::chrono::hours hours(aSecond);
30   std::chrono::hours hours(std::chrono::duration_cast<std::chrono::hours>(aSecond));
31   std::cout << hours.count() << " hours(truncated value)" << std::endl;
32
33   std::cout << std::endl;
34
35   typedef std::chrono::duration<double, std::ratio<60>> MyMinuteTick;
36   MyMinuteTick myMinute(aSecond);
37   std::cout << myMinute.count() << " minutes" << std::endl;
38
39   typedef std::chrono::duration<double, std::ratio<3600>> MyHourTick;
40   MyHourTick myHour(aSecond);
41   std::cout << myHour.count() << " hours" << std::endl;
42
43   typedef std::chrono::duration<double, std::ratio<2700>> MyLessonTick;
44   MyLessonTick myLesson(aSecond);
45   std::cout << myLesson.count() << " lessons" << std::endl;
46
47   typedef std::chrono::duration<long long, std::ratio <1,2>>   MyHalfASecondTick;
48   MyHalfASecondTick myHalfASecond(aSecond);
49   std::cout << myHalfASecond.count() << " HalfASeconds" << std::endl;
50
51   std::cout << std::endl;
52 }

Die Ausgabe veranschaulicht außerdem, wie C++ den Wert für die Minute und für die Stunde in den Zeilen 27 und 31 abschneidet (Abbildung 3). Der zugehörige Alias

typedef std::chrono::duration<long long,  std::ratio<1,2>> MyHalfASecondTick

in Zeile 47 erklärt eine halbe Sekunde.

Abbildung 3: Eine Sekunde aufgesplittet in verschiedene Variationen.

Abbildung 3: Eine Sekunde aufgesplittet in verschiedene Variationen.

Zeitgeber

Der Zeitgeber setzt sich aus einem Startpunkt und einem Zeittakt zusammen. Den aktuellen Zeitpunkt ermittelt die Methode »now()« . C++ kennt dabei drei verschiedene Zeitgeber:

  • »std::chrono::system_clock« : Systemzeit. Lässt sich mit der externen Uhr synchronisieren.
  • »std::chrono::steady_clock« : Uhrzeit. Lässt sich nicht explizit verändern.
  • »std::chrono::high_resolution_clock« : Systemzeit mit der höchsten Auflösung.

Der Unterschied zwischen den drei: »std::chrono::system_clock« bezieht sich auf den 1. Januar 1970, die beiden anderen Zeitgeber auf den Bootzeitpunkt des Rechners. Diese unterschiedlichen Zeitabschnitte bilden die Epochen der jeweiligen Zeitgeber.

Mit den Methoden »to_time_t()« und »from_time_t()« konvertiert der Entwickler »std::chrono::system_clock« -Objekte in »std::time_t« -Objekte und umgekehrt [2]. Die Klasse »std::chrono::steady_clock« kann er als einzigen Zeitgeber nicht zurückstellen.

Bei »std::chrono::high_resolution_clock« handelt es sich um die genaueste der drei Uhren. Oder um im Bild zu bleiben: Die Klasse verfügt über den kleinsten Zeittakt. Bei genauerem Hinsehen entpuppt sich »std::chrono::high_resolution_clock« beim vom Autor eingesetzten GCC lediglich als Alias für den Zeitgeber »std:: chrono::system_clock« . Dies aber lässt sich mit dem C++11-Standard vereinen.

Wie geht’s weiter?

Auf die Theorie folgt die Praxis, dieser Logik folgt auch der kommende Artikel. In ihm soll sich alles um den sinnvollen Einsatz der Zeitbibliothek drehen. Zugleich bereitet er die Bühne für Multithreading und einfache Performance-Messungen in C++.

Der Autor

Rainer Grimm arbeitet als Software-Architekt und Gruppenleiter bei der Metrax GmbH in Rottweil. Besonders die Software der hauseigenen Defibrillatoren ist ihm eine Herzensangelegenheit. Seine Bücher “C++11 für Programmierer”, “C++” und “C++11-Standardbibliothek” sind bei O’Reilly erschienen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 3 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben