Startseite | Programmieren | C++ | BOOST | 01
RSS Githublogo Youtubelogo higgs.at kovacs.cf

Ohne Multithreading

Mit Boost Thread können multithreading programme erzeugt werden. Bei Prozessoren mit mehreren Kernen können damit Operationen parallel ausgeführt werden. Normalerweise wird ein Programm sequenziell (von oben nach unten) seriell ausgeführt. D.H. jeder Befehl wird nach dem Abschluss des vorherigen Befehls ausgeführt. Ein multicore-optimiertes Programm kann bestimmte Teile gleichzeitig ausführen. Damit wird es u.U. schneller abgearbeitet.

Ein Programm muss aber erst hinsichtlich multithreading optimiert bzw. geschrieben weerden. Dazu muss man das Programm in Teile einteilen die (z.B.) einzeln ausgeführt werden können. Jeder Teil des Programms wird dann auf einem eigenen Prozessor ausgeführt.

Nicht jedes Programm eignet sich hierfür und um ein multicore-Programm auszuführen brauchen wir natürlich eine CPU mit mehreren Kernen.

Das Programm, welches unten angeführt ist berechnet die Primzahlen zwischen 0 und 10000000. Es besteht aus einer Funktion calc_prime(), welche als Argument eine Zahl übernimmt. In der Funktion wird berechnet, ob diese Zahl eine Primzahl ist oder nicht. Wenn es eine Primzahl ist wird diese in der Konsole ausgegeben.

#include <iostream>
#include <math.h>

int calc_prime(int prime_to_test){
     bool prime_flag = true;
     float prim_divide = 0;

     for (int t1 = 2 ; t1 <= sqrt(prime_to_test) ; t1++){
          prim_divide = prime_to_test%t1;

          if (prim_divide == 0 and t1 != prime_to_test){
               prime_flag = false;
               break;
          }
     }

     if (prime_flag == true){
          std::cout << prime_to_test << std::endl;
     }
}

int main(int argc, char *argv[]){
     int max_prime_number = 10000000;

     for (int t2 = 2 ; t2 < max_prime_number ; t2++){
          calc_prime(t2);
     }

     return 0;
}

In der main-Funktion wird dann für jede Zahl ausgeführt.



Mit Multithreading

Während der Code oben (multi)threads nicht berücksichtigt tut es der weiter unten schon:
Zuerst werden die BOOST thread Bibilotheken eingebunden mit #include <boost/thread.hpp >

Die Funktion calc_prime() ist genau die selbe wie oben!

Zusätzlich werden wir eine zweite Funktion void prime_threads(int tr_num, int max_prime_n, int numCPU) benutzen. Der Funktion werden folgende Parameter übergeben: tr_num : Die Nummer des Threads
max_prime_n : Höchste Zahl bis der wir die Primzahlsuche durchführen
numCPU : Nummer der Kerne der CPU
#include <boost/thread.hpp>
#include <iostream>
#include <math.h>

void wait(int seconds)
{
     boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}

int calc_prime(int prime_to_test){
     bool prime_flag = true;
     float prim_divide = 0;

     for (int t1 = 2 ; t1 <= sqrt(prime_to_test) ; t1++){
          prim_divide = prime_to_test%t1;

          if (prim_divide == 0 and t1 != prime_to_test){
               prime_flag = false;
               break;
          }
     }

     if (prime_flag == true){
          std::cout << prime_to_test << std::endl;
     }
}

void prime_threads(int tr_num, int max_prime_n, int numCPU)
{
     for (int act_test_num = tr_num ; act_test_num <= max_prime_n ; act_test_num+=numCPU){
          if (act_test_num != 1)
               calc_prime(act_test_num);
     }
}

int main(int argc, char *argv[]){

     int numCPU = sysconf( _SC_NPROCESSORS_ONLN );
     int max_prime_n = 10000000;

     boost::thread tread1(prime_threads, 1, max_prime_n, numCPU);
     boost::thread tread2(prime_threads, 2, max_prime_n, numCPU);
     boost::thread tread3(prime_threads, 3, max_prime_n, numCPU);
     boost::thread tread4(prime_threads, 4, max_prime_n, numCPU);

     tread1.join();
     tread2.join();
     tread3.join();
     tread4.join();

     return 0;
}

In der main-Funktion werden wir zuerst bestimmen, wie viele Kerne wir zur Verfügung haben mit dem Befehl sysconf( _SC_NPROCESSORS_ONLN )

Ich hatte 4 Kerne zur Verfügung. Dann wird max_prime_n auf 10000000 gesetzt: Wir wollen alle Primzahlen zwischen 0 und 10000000 finden.

Dann kommt der wichtige (multithreading) Teil:
Wir werden vier Threads (manuell) mit dem Konstruktor boost::thread threadn starten wobei n von 1 bis 4 geht (vier Kerne, vier Threads). Ein Thread ist eine Funktion, welche dann einem Kern übergeben wird. Dieser Thread wird dann in einer CPU ausgeführt. Threads bilden somit den wichtigsten Teil eines multicore-Programms. Dem Konstruktor werden noch ein paar Dinge übergeben: Den Name der Funktion prime_threads, welche in dem Thread laufen soll, eine Nummer zwischen 1 und 4 welche gleich dem n des Threads ist, die Variable max_prime_n und die Anzahl der Kerne.

Jeder Thread bekommt also eine Zahl(1,2,3 oder 4). In der Funktion prime_threads wird beginnend von dieser übergebenen Zahl angefangen Primzahlen zu suchen. Die nächste Zahl die dann untersucht wird ist n + numCPU. Damit haben wir die Suche auf die Threads aufgeteilt. Die vier Threads laufen parallel.

Zum Schluss wird thread.join(); aufgerufen. Dieser Befehl bewirkt, dass die main-Funktion erst beendet wird, wenn alle Threads abgeschlossen sind.

Der Code ohne multicore-Unterstützung sucht die Primzahlen von 0 bis 10000000 - eine Zahl nach der anderen.

Das multicore-Programm hat vier Threads:

Die Tabelle unten zeigt, welcher Thread welche Zahl (auf eine Primzahl) untersucht.

Thread untersuchte Zahl
Thread1  1  5  9 13 17
Thread2  2  6 10 14 18
Thread3  3  7 11 15 19
Thread4  4  8 12 16 20


Ohne multithreading schaut das folgendermaßen aus:
Thread untersuchte Zahl
Thread1  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20

Hier ist es so, als hätten wir nur einen Thread.

Wenn es mehr Zeit braucht, die Threads zu initialisieren als wenn das Programm nur in einem Thread läuft, dann optimiert der Compiler das Programm auf einen Thread. Deswegen sollte die Zahl max_prime_n relativ hoch gewählt werden. Sonst kann es z.B. bei max_prime_n = 1000 passieren, dass es keinen Unterschied macht ob man das Programm mit vier Threads laufen lässt oder mit einem weil des mehr Zeit benötigen würde die Threads zu initialisieren als das Programm mit nur einem Thread laufen zu lassen!

Unter Linux kann das multicore-Programm mit g++ prime_thread.cpp -lboost_system -pthread -lboost_thread-mt kompiliert und mit ./a.out gestartet werden.

Mit dem Befehl time ./a.out kann die Zeit ausgegeben werden, welche das Programm zum laufen benötigt. Das Bild unten zeigt die Zeit (gemittelt von 5 Durchläufen) in Abhängigkeit von den benutzten Threads.

prime_thread_5r_d.png

Die maximale Zeitersparnis liegt bei drei Threads. Mehr als drei Threads beschleunigen nicht die Laufzeit.

Warum die Laufzeit bei drei Threads so signifikant abnimmt obwohl die CPU vier Kerne hat?

Weil das Betriebssystem und diverse andere Programme auch Ressourcen benötigen!

Diese Methode hier ist sicher nicht die effektivste um Primzahlen zu berechnen. Trotzdem ist es ein relativ einfaches Beispiel um multithreading zu demonstrieren.

Das Hauptaugenmerk hier ist nicht die komplette Optimierung. Es soll ein kurzer Einblick geben, wie die Bibliother BOOST thread benutzt wird um Primzahlen zu berechnen.