============================ Benchmark Tools in `bench.h` [TOC:2] ============================ Inhalte: - Aufbau von Header-Files - Template-Funktionen und Spezialisierungen - Erster Konatkt mit folgenden C++ Features: - Zufallszahlen (C++11) - C++ Klasse für komplexe Zahlen - Timer-Funktionen (C++11) Include-Guards ============== Damit der Header nicht mehrfach eingebunden wird, soll ein _Include-Guard_ benutzt werden. Schematisch sieht dies so aus: ---- CODE (type=cc) ------------------------------------------------------------ #ifndef BENCH_H #define BENCH_H 1 /* ... your code in bench.h ... */ #endif // BENCH_H -------------------------------------------------------------------------------- Damit keine Konflikte mit anderen Bibliotheken (wie der STL) entstehen, sollte man zusätzlich noch den Projektnamen verwenden. Zum Beispiel: ---- CODE (type=cc) ------------------------------------------------------------ #ifndef HPC_BENCH_H #define HPC_BENCH_H 1 /* ... your code in bench.h ... */ #endif // HPC_BENCH_H -------------------------------------------------------------------------------- Wieso ist dies notwendig? ------------------------- Betrachten wir ein Beispiel Projekt, das aus drei Dateien besteht: - In `foo.h` wird eine Funktion `foo` definiert, die der Einfachheit halber nur ein kurze Nachricht ausgibt: ---- CODE (file=session10/example1/foo.h) ------------------------------------ #include void foo() { std::printf("foo!\n"); } ------------------------------------------------------------------------------ - `dummy.h` enthält eine Funktion, die nichts tut außer `foo()` aufzurufen und deshalb `foo.h` einbindet: ---- CODE (file=session10/example1/dummy.h) ---------------------------------- #include "foo.h" void dummy() { foo(); } ------------------------------------------------------------------------------ - In `test.cc` werden sowohl `foo()` als auch `dummy()` aufgerufen. Deshalb werden auch beide Header eingebunden: ---- CODE (file=session10/example1/test.cc) ---------------------------------- #include "foo.h" #include "dummy.h" int main() { foo(); dummy(); } ------------------------------------------------------------------------------ Wenn wir versuchen, dies zu übersetzen, erhalten wir folgende Fehlermeldung: ---- SHELL (path=session10/example1) ------------------------------------------- g++ -Wall -o test test.cc -------------------------------------------------------------------------------- Aufgaben -------- - Fügt nach obigem Muster Include-Guards ein. - Übsetzt man das obige Programm `test.cc` und möchte das ausführbare Programm wie folgt ausführen, so passiert scheinbar nichts. Wieso? ---- SHELL (path=session10/example2) ----------------------------------------- g++ -Wall -o test test.cc test ------------------------------------------------------------------------------ Zumindest bei der Shell-Box von DocTool passiert doch scheinbar etwas. Denn diese ist rot eingefärbt was darauf hinweist, dass der zuletzt ausgeführte Befehl einen Exit-Code ungleich Null erzeugt hat. Wie so oft kommt man der Sache durch _rtfm_ auf die Spur: `man test`. Funktion `initGeMatrix` ======================= Die Funktion `initGeMatrix` soll eine $m \times n$ Matrix mit Zufallszahlen initialisieren. Wir legen fest, dass diese zwischen $-100$ und $100$ gleichverteilt sein sollen. Die Signatur geben wir mit ---- CODE (type=cc) ------------------------------------------------------------ template void initGeMatrix(Index m, Index n, T *A, Index incRowA, Index incColA) { /* your code here */ } -------------------------------------------------------------------------------- vor. Erzeugen von Zufallszahlen in C++11 ----------------------------------- Zum Erzeugen von Zufallszahlen stellt C++11 einige Hilfmittel bereit, die man mit Hilfe des Internets genauer erkunden kann. Wir geben hier mit einem kleinen Beispiel vor, wie man Zufallszahlen mit der gewünschten Verteilung erzeugen kann: ---- CODE (file=session10/example3/test.cc) ------------------------------------ #include #include int main() { std::random_device random; std::mt19937 mt(random()); std::uniform_real_distribution uniform(-100, 100); std::printf("random value: %lf\n", uniform(mt)); } -------------------------------------------------------------------------------- Hier ist es notwendig, dass man mit `-std=c++11` übersetzt: ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -o test test.cc g++ -Wall -std=c++11 -o test test.cc ./test -------------------------------------------------------------------------------- Aufgabe ------- Implementiert die Funktion `initGeMatrix`. Zum Testen könnt Ihr Euch an folgendem Beispiel orientieren: ---- CODE (file=session10/example3/test_init.cc) ------------------------------- #include #include "bench.h" void printValue(double x) { printf("%7.2lf ", x); } template void printGeMatrix(Index m, Index n, const T *A, Index incRowA, Index incColA) { for (Index i=0; i #include int main() { std::complex z(2,3); printf("z = (%lf, %lf)\n", z.real(), z.imag()); z = std::complex(4,-3); printf("z = (%lf, %lf)\n", z.real(), z.imag()); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test2 test2.cc test2 -------------------------------------------------------------------------------- Zu beachten ist, dass der obige Zufallszahlengenerator nicht direkt für komplexe Zahlen verwendet werden kann: ---- CODE (file=session10/example3/test3.cc) ----------------------------------- #include #include #include int main() { std::random_device random; std::mt19937 mt(random()); std::uniform_real_distribution> uniform(-100, 100); // .... } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test3 test3.cc -------------------------------------------------------------------------------- Wie man aus der Fehlermeldung erfährt, können nur Fließkommazahlen als Template-Parameter (`float`, `double`, `long double`) für die Verteilung verwendet werden. Mehr Informationen: - __Link1__ - __Link2__ :links: Link1 -> http://en.cppreference.com/w/cpp/numeric/random/uniform_real_distribution Link2 -> http://www.cplusplus.com/reference/random/uniform_real_distribution/ Es ist also notwendig, den Real- und Imaginärteil getrennt mit einer Zufallszahl zu initialisieren: ---- CODE (file=session10/example3/test4.cc) ----------------------------------- #include #include #include int main() { std::random_device random; std::uniform_real_distribution uniform(-100, 100); std::complex z(uniform(random), uniform(random)); printf("z = (%lf, %lf)\n", z.real(), z.imag()); // or z = std::complex(uniform(random), uniform(random)); printf("z = (%lf, %lf)\n", z.real(), z.imag()); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test4 test4.cc ./test4 -------------------------------------------------------------------------------- Aufgabe ------- Für komplexe Matrizen müssen wir die Funktion `initGeMatrix` deshalb spezialisieren: ---- CODE (type=cc) ------------------------------------------------------------ template void initGeMatrix(Index m, Index n, std::complex *A, Index incRowA, Index incColA) { /* your code here */ } -------------------------------------------------------------------------------- Zum Testen könnt Ihr Euch an folgendem Beispiel orientieren: ---- CODE (file=session10/example3/test_init_complex.cc) ----------------------- #include #include #include "bench.h" void printValue(double x) { printf("%7.2lf ", x); } // Frage: Wieso wird hier eine Const-Referenz uebergeben? void printValue(const std::complex &x) { printf("(%7.2lf,%7.2lf) ", x.real(), x.imag()); } template void printGeMatrix(Index m, Index n, const T *A, Index incRowA, Index incColA) { for (Index i=0; i *A = new std::complex[3*7]; bench::initGeMatrix(3, 7, A, 1, 3); printGeMatrix(3, 7, A, 1, 3); delete [] A; } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test_init_complex test_init_complex.cc ./test_init_complex -------------------------------------------------------------------------------- Funktion `asumDiffGeMatrix` =========================== Die Funktion `asumDiffGeMatrix` summiert die Beträge (_absolut values_) der Differenz von zwei $m \times n$ Matrizen im Full-Storage-Format. Dies soll nun bezüglich dem Element- und Index-Typ mittels Templates parameterisiert werden. Die Signatur lautet ---- CODE (type=cc) ------------------------------------------------------------ template double asumDiffGeMatrix(Index m, Index n, const T *A, Index incRowA, Index incColA, T *B, Index incRowB, Index incColB) { /* your code here */ } -------------------------------------------------------------------------------- Für den Betrag soll statt der C-Funktion `fabs` die bezüglich dem Typ überladene Funktion `abs` aus `cmath` verwendet werden: ---- CODE (file=session10/example3/test_abs.cc) -------------------------------- #include #include #include int main() { printf("abs(-1.4) = %lf\n", abs(1.4)); printf("abs(-1.4f) = %f\n", abs(1.4f)); printf("abs(std::complex(-1.3, 2.3) = %lf\n", abs(std::complex(-1.3, 2.3))); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test_abs test_abs.cc ./test_abs -------------------------------------------------------------------------------- Aufgabe ------- Implementiert die Funktion. Zum Testen könnt Ihr Euch an folgendem Beispiel orientieren: ---- CODE (file=session10/example3/test_asumdiff.cc) --------------------------- #include #include "bench.h" int main() { double *A = new double[3*7]; bench::initGeMatrix(3, 7, A, 1, 3); printf("diff = %lf\n", bench::asumDiffGeMatrix(3, 7, A, 1, 3, A, 1, 3)); delete [] A; } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test_asumdiff test_asumdiff.cc ./test_asumdiff -------------------------------------------------------------------------------- Eine C++-Klasse für eine Stoppuhr ================================= Seit C++11 gibt es endlich plattform-unabhängige Schnittstellen für Timer. Für unsere Zwecke kann mit der `std::chrono::high_resolution_clock`-Klasse die Wall-Time folgendermaßen gemessen werden: ---- CODE (file=session10/example3/test5.cc) ----------------------------------- #include #include #include void doStuff() { std::random_device random; std::uniform_real_distribution uniform(-100, 100); volatile double foo = 0; for (std::size_t i=0; i<1000*1000; ++i) { foo += uniform(random); } } int main() { std::chrono::high_resolution_clock::time_point t0; std::chrono::high_resolution_clock::duration elapsed; // // bench doStuff() // t0 = std::chrono::high_resolution_clock::now(); doStuff(); elapsed = std::chrono::high_resolution_clock::now() - t0; // // Convert duration into seconds and store it in a double // double elapsedSeconds = std::chrono::duration(elapsed).count(); printf("Time elapesd in seconds: %5.2lf\n", elapsedSeconds); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test5 test5.cc ./test5 -------------------------------------------------------------------------------- Wer mehr über die C++11-Klassen wissen möchte, kann __hier__ weiterlesen oder natürlich Google fragen. :links: hier -> http://www.cplusplus.com/reference/chrono/ Das Messen der benötigten Zeit soll aber einfacher und hübscher möglich sein. Und zwar so: ---- CODE (file=session10/example3/test6.cc) ----------------------------------- #include #include "bench.h" void doStuff() { std::random_device random; std::uniform_real_distribution uniform(-100, 100); volatile double foo = 0; for (std::size_t i=0; i<1000*1000; ++i) { foo += uniform(random); } } int main() { bench::WallTime wallTime; // // Stoppuhr starten // wallTime.tic(); doStuff(); // // Stoppuhr stoppen // double elapsedSeconds = wallTime.toc(); printf("Time elapesd in seconds: %5.2lf\n", elapsedSeconds); } -------------------------------------------------------------------------------- ---- SHELL (path=session10/example3) ------------------------------------------- g++ -Wall -std=c++11 -o test6 test6.cc ./test6 -------------------------------------------------------------------------------- Dazu geben wir folgende Vorlage: ---- CODE (type=cc) ------------------------------------------------------------ template struct WallTime { void tic() { /* ... set t0 to current time ... */ } T toc() { /* ... set duration ... */ } /* ... t0, duration attributes ... */ }; -------------------------------------------------------------------------------- Mit angemessenem Einsatz von `using` kann man es auch schaffen, dass Zeilen nicht mehr als 80 Zeichen benötigen. :navigate: up -> doc:index back -> doc:session10/page01 next -> doc:session10/page03