Content

Benchmark Tools in bench.h

Inhalte:

Include-Guards

Damit der Header nicht mehrfach eingebunden wird, soll ein Include-Guard benutzt werden. Schematisch sieht dies so aus:

#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:

#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:

Wenn wir versuchen, dies zu übersetzen, erhalten wir folgende Fehlermeldung:

$shell> g++ -Wall -o test test.cc
In file included from dummy.h:1:0,
                 from test.cc:2:
foo.h: In function 'void foo()':
foo.h:4:1: error: redefinition of 'void foo()'
 foo()
 ^
In file included from test.cc:1:0:
foo.h:4:1: note: 'void foo()' previously defined here
 foo()
 ^

Aufgaben

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

template <typename T, typename Index>
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:

#include <cstdio>
#include <random>

int
main()
{
    std::random_device                     random;
    std::mt19937                           mt(random());
    std::uniform_real_distribution<double> uniform(-100, 100);

    std::printf("random value: %lf\n", uniform(mt));
}

Hier ist es notwendig, dass man mit -std=c++11 übersetzt:

$shell> g++ -Wall -o test test.cc
In file included from /usr/local/gcc51/include/c++/5.2.0/random:35:0,
                 from test.cc:2:
/usr/local/gcc51/include/c++/5.2.0/bits/c++0x_warning.h:32:2: error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
 #error This file requires compiler and library support for the \
  ^
test.cc: In function 'int main()':
test.cc:7:5: error: 'random_device' is not a member of 'std'
     std::random_device                     random;
     ^
test.cc:8:5: error: 'mt19937' is not a member of 'std'
     std::mt19937                           mt(random());
     ^
test.cc:9:5: error: 'uniform_real_distribution' is not a member of 'std'
     std::uniform_real_distribution uniform(-100, 100);
     ^
test.cc:9:36: error: expected primary-expression before 'double'
     std::uniform_real_distribution uniform(-100, 100);
                                    ^
test.cc:11:48: error: 'mt' was not declared in this scope
     std::printf("random value: %lf\n", uniform(mt));
                                                ^
test.cc:11:50: error: 'uniform' was not declared in this scope
     std::printf("random value: %lf\n", uniform(mt));
                                                  ^
$shell> g++ -Wall -std=c++11 -o test test.cc
$shell> ./test
random value: -90.544822

Aufgabe

Implementiert die Funktion initGeMatrix. Zum Testen könnt Ihr Euch an folgendem Beispiel orientieren:

#include <cstdio>
#include "bench.h"

void
printValue(double x)
{
    printf("%7.2lf ", x);
}

template <typename T, typename Index>
void
printGeMatrix(Index m, Index n, const T *A, Index incRowA, Index incColA)
{
    for (Index i=0; i<m; ++i) {
        for (Index j=0; j<n; ++j) {
            printValue(A[i*incRowA+j*incColA]);
        }
        printf("\n");
    }
    printf("\n");
}

int
main()
{
    double *A = new double[3*7];

    bench::initGeMatrix(3, 7, A, 1, 3);
    printGeMatrix(3, 7, A, 1, 3);

    delete [] A;
}
$shell> g++ -Wall -std=c++11 -o test_init test_init.cc
$shell> ./test_init
 -80.74    7.80   85.05   10.20  -32.14   51.66  -17.35 
 -35.73   28.64   90.03   37.05  -37.57  -56.88   18.30 
  96.99  -54.88   12.99   34.09  -76.47   -0.26   99.29 

Funktion initGeMatrix für Matrizen mit komplexen Zahlen

Für komplexe Zahlen gibt es in C++ die Klasse std::complex, die bezüglich dem Typen für den Real- und Imaginärteil parameterisiert ist:

#include <complex>
#include <cstdio>

int
main()
{
    std::complex<double> z(2,3);

    printf("z = (%lf, %lf)\n", z.real(), z.imag());

    z = std::complex<double>(4,-3);
    printf("z = (%lf, %lf)\n", z.real(), z.imag());
}
$shell> g++ -Wall -std=c++11 -o test2 test2.cc
$shell> test2
z = (2.000000, 3.000000)
z = (4.000000, -3.000000)

Zu beachten ist, dass der obige Zufallszahlengenerator nicht direkt für komplexe Zahlen verwendet werden kann:

#include <complex>
#include <cstdio>
#include <random>

int
main()
{
    std::random_device                                    random;
    std::mt19937                                          mt(random());
    std::uniform_real_distribution<std::complex<double>>  uniform(-100, 100);

    // ....

}
$shell> g++ -Wall -std=c++11 -o test3 test3.cc
In file included from /usr/local/gcc51/include/c++/5.2.0/random:49:0,
                 from test3.cc:3:
/usr/local/gcc51/include/c++/5.2.0/bits/random.h: In instantiation of 'class std::uniform_real_distribution >':
test3.cc:10:66:   required from here
/usr/local/gcc51/include/c++/5.2.0/bits/random.h:1868:7: error: static assertion failed: template argument not a floating point type
       static_assert(std::is_floating_point<_RealType>::value,
       ^

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:

Es ist also notwendig, den Real- und Imaginärteil getrennt mit einer Zufallszahl zu initialisieren:

#include <complex>
#include <cstdio>
#include <random>

int
main()
{
    std::random_device                      random;
    std::uniform_real_distribution<double>  uniform(-100, 100);

    std::complex<double> z(uniform(random), uniform(random));

    printf("z = (%lf, %lf)\n", z.real(), z.imag());

    // or

    z = std::complex<double>(uniform(random), uniform(random));

    printf("z = (%lf, %lf)\n", z.real(), z.imag());
}
$shell> g++ -Wall -std=c++11 -o test4 test4.cc
$shell> ./test4
z = (20.857154, -16.647095)
z = (-69.442747, -58.213283)

Aufgabe

Für komplexe Matrizen müssen wir die Funktion initGeMatrix deshalb spezialisieren:

template <typename T, typename Index>
void
initGeMatrix(Index m, Index n, std::complex<T> *A, Index incRowA, Index incColA)
{
    /* your code here */
}

Zum Testen könnt Ihr Euch an folgendem Beispiel orientieren:

#include <cstdio>
#include <complex>
#include "bench.h"

void
printValue(double x)
{
    printf("%7.2lf ", x);
}

// Frage: Wieso wird hier eine Const-Referenz uebergeben?
void
printValue(const std::complex<double> &x)
{
    printf("(%7.2lf,%7.2lf) ", x.real(), x.imag());
}

template <typename T, typename Index>
void
printGeMatrix(Index m, Index n, const T *A, Index incRowA, Index incColA)
{
    for (Index i=0; i<m; ++i) {
        for (Index j=0; j<n; ++j) {
            printValue(A[i*incRowA+j*incColA]);
        }
        printf("\n");
    }
    printf("\n");
}

int
main()
{
    std::complex<double> *A = new std::complex<double>[3*7];

    bench::initGeMatrix(3, 7, A, 1, 3);
    printGeMatrix(3, 7, A, 1, 3);

    delete [] A;
}
$shell> g++ -Wall -std=c++11 -o test_init_complex test_init_complex.cc
$shell> ./test_init_complex
(  26.86,  10.10) ( -89.96,  95.81) ( -47.83, -94.97) (   7.47, -41.81) ( -41.37, -51.56) (  80.04,  60.55) (  57.40, -68.67) 
(  85.93,  73.91) ( -34.45, -82.04) ( -47.24,  74.20) (  -7.89, -13.01) (  42.73,  35.72) ( -41.67, -21.91) (  15.06,  29.19) 
(  18.11, -82.31) (   6.79,  -2.42) (  89.15, -60.56) ( -60.01, -55.32) ( -80.89, -22.25) (  24.27,  77.73) (  75.68,  70.84) 

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

template <typename T, typename Index>
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:

#include <complex>
#include <cmath>
#include <cstdio>

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<double>(-1.3, 2.3)));
}
$shell> g++ -Wall -std=c++11 -o test_abs test_abs.cc
$shell> ./test_abs
abs(-1.4) = 1.400000
abs(-1.4f) = 1.400000
abs(std::complex(-1.3, 2.3) = 2.641969

Aufgabe

Implementiert die Funktion. Zum Testen könnt Ihr Euch an folgendem Beispiel orientieren:

#include <cstdio>
#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> g++ -Wall -std=c++11 -o test_asumdiff test_asumdiff.cc
$shell> ./test_asumdiff
diff = 0.000000

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:

#include <chrono>
#include <cstdio>
#include <random>

void
doStuff()
{
    std::random_device                     random;
    std::uniform_real_distribution<double> 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<double,std::chrono::seconds::period>(elapsed).count();

    printf("Time elapesd in seconds: %5.2lf\n", elapsedSeconds);
}
$shell> g++ -Wall -std=c++11 -o test5 test5.cc
$shell> ./test5
Time elapesd in seconds:  1.17

Wer mehr über die C++11-Klassen wissen möchte, kann hier weiterlesen oder natürlich Google fragen.

Das Messen der benötigten Zeit soll aber einfacher und hübscher möglich sein. Und zwar so:

#include <cstdio>
#include "bench.h"

void
doStuff()
{
    std::random_device                     random;
    std::uniform_real_distribution<double> uniform(-100, 100);

    volatile double foo = 0;
    for (std::size_t i=0; i<1000*1000; ++i) {
        foo += uniform(random);
    }
}

int
main()
{
    bench::WallTime<double> wallTime;

    //
    // Stoppuhr starten
    //
    wallTime.tic();

    doStuff();

    //
    // Stoppuhr stoppen
    //
    double elapsedSeconds = wallTime.toc();

    printf("Time elapesd in seconds: %5.2lf\n", elapsedSeconds);
}
$shell> g++ -Wall -std=c++11 -o test6 test6.cc
$shell> ./test6
Time elapesd in seconds:  1.18

Dazu geben wir folgende Vorlage:

template <typename T>
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.