CPP2019SSF32

Aus Verteilte Systeme - Wiki
Zur Navigation springen Zur Suche springen

Feature: std::chrono

Das Feature <chrono> bzw.die Bibliothek lässt sich in drei Kategorien unterteilen Uhren, Zeitspannen und Zeitpunkte.

  • Eingeführt in: C++ 11
  • Deprecated seit: -
  • Nicht mehr im Standard seit: -

Motivation für die Einführung

Der technische Fortschritt führt zu der Herausforderung, dass CPUs eine immer höhere Taktfrequenz aufweisen. Eine CPU mit einer Taktfrequenz von 4GHz kann 4000 Millionen Befehle in einer Sekunde bearbeiten. Umgerechnet ist das ein Befehl in 1/4 einer Nanosekunde. Die Instruktionen pro Zyklus (als Beispiel) können zwar heute noch gut in Nanosekunden angegeben werden, jedoch wird der stetige Fortschritt zu einer Problematik führen, dass bestehende Zeiteinheiten nicht ausreichen. Chrono bietet eine flexible Lösung zu dieser Problematik, damit in Zukunft keine erneute Umgestaltung der Genauigkeit von Zeiteinheiten erbracht werden muss.


Syntax

std::chrono::durations

In <chrono> gibt es 6 Zeiteinheiten die vom Standard vorgegeben sind:

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


int main(){
nanoseconds ns1 = 5ns;            
microseconds us1 = 5us;
milliseconds ms1= 5ms;
seconds s1 = 5s;
minutes m1 = 5min;
hours h1 = 5h;
// alternativ: 

nanoseconds ns1{5ns};
milliseconds ms1{5ms};

//etc..
// Tipp: <chrono> hat eine hervorragende Unterstützung für auto 
}

Das std::chrono::duration-Template besteht aus zwei Typen: rep für Representation und period für einen Bruch bestehend aus Zaehler und Nenner, welcher einen Tick der definierten Zeiteinheit abhängig von Sekunden darstellt. Ein tausendstel einer Sekunde ist eine Millisekunde, 60/1 ist eine Minute. Die Periode wird mit <ratio<Zaehler,Nenner> angegeben.

Die Representations sind im Standard wie folgt definiert:

using nanoseconds  = duration<int_least64_t,          nano>;  //ratio auch vom Standard vorgegeben, siehe unten
using microseconds = duration<int_least55_t,         micro>;  //int_least55_t bedeutet mindestens 55 Bits
using milliseconds = duration<int_least45_t,         milli>;
using seconds      = duration<int_least35_t,      ratio<1>>;
using minutes      = duration<int_least29_t,   ratio<60,1>>;
using hours        = duration<int_least23_t, ratio<3600,1>>;

Die ratio(s)<> sind wie folgt definiert:

using nano  = ratio<1, 1 000 000 000>;
using micro = ratio<1,     1 000 000>;
using milli = ratio<1,         1 000>;

Beispiel:

using myseconds = duration<double>; // double ist hier der Repräsentant
using mymilli = duration<double,milli>; milli wäre hier die ratio


std::chrono::time_point

Der semantische Unterschied von time_point(s) zu durations ist, dass 5 Minuten in durations beliebige 5 Minuten seien können, jedoch in time_point(s) sind 5 Minuten immer eindeutig, da jede time_point einer clock zugewiesen ist. Bei der system_clock ist der Startzeitpunkt die (de-facto Standard, aber offiziell im Standard nicht definierte) Epoche 01.01.1970 00:00:00. 5 Minuten addiert auf die system_clock ergeben 01.01.1970 00:00:05.

//Syntax: std::chrono::time_point<ErwünschteClock, Einheit> tp = 
time_point<system_clock, seconds> tp{20000ms};   // Epoch + 20000ms

std::chrono::clocks

In <chrono> sind 3 Standard-Clocks definiert: std::chrono::system_clock, std::chrono::steady_clock und std::high_resolution_clock (siehe Warnhinweise). Die system_clock ist zur Verwendung von Zeitpunkten anhand eines Kalenders gedacht. Die steady_clock kann man sich am Besten als Stoppuhr vorstellen. Diese wird häufig zum Messen von Programmabschnitten (o.ä.) verwendet.

Hier ein Beispiel zum Aufbau einer clock:

struct beispiel_clock{
using duration = chrono::duration<int64_t, nanosekunden;
using rep = duration::rep;                                      // duration
using_period = duration::period;               
using time_point = chrono::time_point;                           //time-point
static constexpr bool is_steady = false;   //constexpr um zu zeigen ob Nachjustierung an clock möglich sind, Bsp:Schaltsekunden
static time_point now() noexcept;          //statische funktion um aktuellen Zeitpunkt zu ermitteln

};


Use Cases

 
sleep(10);   // 10 Sekunden, Millisekunden, Nanosekunuden,..?
sleep(10ms); // Eindeutig

Typsicherheit ist ein wichtiges Thema in <chrono>:

 

using namespace std::chrono;

void daytime(int hours, int min, int snd)
{
/*...Code...*/

}

main:
daytime(45,2,59);
//Trotz unseres Fehlers hours und min vertauscht 
// zu haben würde dieser Code compilen.
 

using namespace std::chrono;

void daytime(hours h, minutes min, seconds sec)
{

/*...Code...*/
}

main:
daytime(45,2,69);     // Error
daytime(45min,2h,69s);  //Error 
//--> Typesafety


Stellen wir uns den Anwendungsfall vor, dass wir mit begrenzten Ressourcen arbeiten. <chrono> bietet hierfür die Flexibilität, die Repräsentationen für die Zeiteinheiten anzupassen.

using seconds16 = std::chrono::duration<int16_t>; // Statt ein longlong zu verwenden benutzen wir nun ein int16
//oder
using seconds16 std::chrono::duration<uint16_t>; // Unsigned int

Ein weiterer Use-Case wäre mit Frames zu arbeiten und deswegen 1/60 einer Sekunde festzuhalten.

//Kein Problem!

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

void bsp(duration<double,micro> dur){

  cout <<"Ausgabe: " << dur.count() << "us\n";
}

int main() {
using seconds = duration<float,ratio<1,60>>;
bsp(seconds{1});
bsp(seconds{1} + 50ms);

}

//Ausgabe: 16666.7us
//         66666.7us

Was <chrono> implizit für uns übernimmt ist das Finden eines gemeinsamen Nenners, die Addition und anschließende Umwandlung in unsere Zieleinheit und Repräsentation: micro, double in compile-Zeit!.



Vorherige Lösungsansätze

Ursprünglich wurde die <time.h> der C-Bibliothek benutzt. Diese ist weiterhin im C++-Standard vorhanden um die C-Unterstützung beizubehalten. <chrono> unterstützt deshalb auch die aus C für <time.h> bekannte Syntax. Ein wichtiger Grund für eine neue Zeit-Bibliothek war, das Fehlen der Möglichkeit neue Zeiteinheiten zu definieren, welche mit dem duration-Template in <chrono> gelöst wurde.


Warnhinweise

  • Chrono ist nicht im namespace der Standard Bibliothek std definiert, sondern unter dem namespace std::chrono
  • Funktionalität zur Umwandlung in ein Datum ist nicht in <chrono> festgelegt.
  • Zum Ausgeben von duration-Objekten muss die Member-Funktion .count() benutzt werden. Diese ist eigtl. zur Unterstützung von legacy-Code gedacht
#include <chrono>
#include <iostream>
using namespace std::chrono;

void Foo(seconds dur){

std::cout << dur.count() << "s\n";   //'s' wird nicht automatisch an die Ausgabe angehängt!
}
  • Implizites/Explizites Casten <chrono>

Implizites Casten wird in <chrono> bei verlustlosen Casts verwendet. Verliert man bei einem Cast Informationen, sprich milliseconds zu seconds muss eine Member-Function verwendet werden. Für durations ist hier die member-Function duration_cast<Zieleinheit>(eingabe) (siehe unten). Für time_points die member-Function time_point_cast<Zieleinheit>(eingabe).

//Hilfsfunktion
void foo(milliseconds dur)
{
cout << dur.count() << "ms\n";
}

main:
//implizites Casten
foo(seconds{2}); // 2000ms
foo(5s)          // 5000ms

//explizites Casten mit Member-Function duration_cast<Zieleinheit>(Eingabe)
//durations
using namespace std::chrono;

seconds s = duration_cast<seconds>(6400ms);
foo(s);          // Output: 6s
//time_point
using sys_time = time_point<system_clock, D>;
sys_time<seconds> tp{5s};
sys_time<milliseconds> tp2{5600ms};
tp = time_point_cast<seconds>(tp2); // 5s, explizites Casten
  • Von der Verwendung der clock high_resolution_clock wird abgeraten, da diese typedef(s) der zwei anderen im Standard vorgegebenen clock(s) verwendet, wobei nicht deutlich ist welche


Code-Beispiel

  • Verwendung time_points und clocks
#include <iostream>
#include <chrono>
using namespace std::chrono;
using namespace std;

void bsp(duration<double,micro> dur){

  cout << dur.count() << "us\n";
}

void bsptimepoints(time_t t)
{

cout<< ctime(&t) << "\n";

}

int main() {
steady_clock::time_point begin =steady_clock::now();
using second = duration<float,ratio<1,60>>;
bsp(second{1});
bsp(second{1} + 50ms);
time_point<system_clock, nanoseconds> tp = system_clock::now();
auto t = system_clock::to_time_t(tp);
bsptimepoints(t);
steady_clock::time_point ende = std::chrono::steady_clock::now();

std::cout << "Unser Programm brauchte:" << duration_cast<microseconds>(ende-begin).count() << "us\n"; 

}
clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final)
clang++-7 -pthread -o main main.cpp
/main


16666.7us
66666.7us
Sat Aug 24 11:25:30 2019

Unser Programm brauchte:172us

Literaturnachweis