================================================= Parallelisierung der Matrix-Matrix-Multiplikation ================================================= Eine Parallelisierung erscheint insbesondere innerhalb des Makro-Kernels sinnvoll, also in der Funktion `mgemm` und dort bei der äußeren `for`-Schleife. Aufgaben ======== * Parallelisieren Sie `mgemm` mit Hilfe von OpenMP. Es ist dabei allerdings überlegenswert, ob dabei eine Anpassung der von `gemm` bestimmten Blockgrößen sinnvoll sein könnte, die die Zahl der Threads berücksichtigt. Wenn Sie die Größe des bei OpenMP zur Verfügung stehenden Thread-Pools bestimmen möchten, geht dies mit folgendem Trick: ---- CODE (type=cpp) ------------------------------------------------------- #if defined(_OPENMP) #include #endif /* ... */ int nof_threads = 1; #if defined(_OPENMP) #pragma omp parallel { if (omp_get_thread_num() == 0) { nof_threads = omp_get_num_threads(); } } #endif ---------------------------------------------------------------------------- Wenn `omp_get_num_threads()` außerhalb eines parallelisierten Blocks aufgerufen wird, gibt es nur 1 zurück, weil nur ein Thread aktiv ist. Dieser Trick ruft nun `omp_get_thread_num()` innerhalb eines parallelisierten Blocks auf, so dass wir die Zahl der zur Verfügung stehenden Threads während einer Parallelisierung erfahren. Bei `#pragma omp parallel` wird keine `for`-Schleife parallelisiert, sondern der folgende Block bzw. die folgende Anweisung parallelisiert aufgerufen. Damit die einzelnen Threads die Aufgabe aufteilen können, steht mit `omp_get_thread_num()` die eigene Thread-Nummer (von 0 bis zur Zahl der Threads minus 1) zur Verfügung und mit `omp_get_num_threads()` kann die Zahl der laufenden Threads ermittelt werden. * Parallelisieren Sie `mgemm` mit Hilfe des Thread-Pools aus der Vorlesungsbibliothek. Bislang wurde der Thread-Pool als Parameter weitergereicht. Um hier bei nur einer `gemm`-Funktion bleiben zu können, erscheint es praktikabler, mit einem global deklarierten Thread-Pool zu arbeiten und diesen in Abhängigkeit eines Präprozessor-Symbols `GLOBAL_THREAD_POOL` zu verwenden. Dies klappt, indem Sie in `bench_gemm.cpp` einen globalen Thread-Pool deklarieren: ---- CODE (type=cpp) ------------------------------------------------------- hpc::mt::ThreadPool global_tpool(4); ---------------------------------------------------------------------------- In der Vorlage ist das bereits geschehen. Beim Übersetzen können Sie dann statt `-fopenmp` die Option `-DGLOBAL_THREAD_POOL=global_tpool` hinzufügen. Der Programmtext der Bibliothek kann dann auf diesen globalen Thread-Pool zurückgreifen, wenn das entsprechende Symbol definiert ist. Hier demonstriert am einfachen Beispiel der Bestimmung der zur Verfügung stehenden Threads: ---- CODE (type=cpp) ------------------------------------------------------- #if defined(_OPENMP) #include #endif #ifdef GLOBAL_THREAD_POOL #include #endif #ifdef GLOBAL_THREAD_POOL extern hpc::mt::ThreadPool GLOBAL_THREAD_POOL; #endif /* ... */ int nof_threads = 1; #if defined(GLOBAL_THREAD_POOL) nof_threads = ::GLOBAL_THREAD_POOL.get_num_threads(); #elif defined(_OPENMP) #pragma omp parallel { if (omp_get_thread_num() == 0) { nof_threads = omp_get_num_threads(); } } #endif ---------------------------------------------------------------------------- Außerhalb der Funktionen wird zu Beginn hier `GLOBAL_THREAD_POOL` als externe Variable deklariert. Dies muss außerhalb irgendwelcher `namespace`-Deklarationen erfolgen. Die beiden Doppelpunkte vor `GLOBAL_THREAD_POOL` sind hier notwendig, da der Thread-Pool im globalen Namensraum deklariert ist, während sich dieser Programmtext bereits innerhalb des Namensraums `hpc::ulmblas` befindet. Damit wird eine Kollision mit gleichlautenden lokalen Namen verhindert. Analog kann dann in `mgemm.h` vorgegangen werden. Vorlage ======= Um `gemm.h` und `mgemm.h` anzupassen, sollten Sie die _hpc_-Hierarchie in Ihr eigenes Verzeichnis kopieren. Das geht beispielsweise mit folgendem Kommando: ---- CODE (type=sh) ----------------------------------------------------------- cp -r /home/numerik/pub/hpc/session18/hpc . ------------------------------------------------------------------------------- So sehen dann die Übersetzungskommandos aus, je nachdem, ob Sie parallelisieren und ob Sie sich ggf. für OpenMP oder den Thread-Pool entscheiden: ---- CODE (type=sh) ----------------------------------------------------------- g++ -DAVX -DD_BLOCKSIZE_MR=4 -DD_BLOCKSIZE_NR=8 -O3 -g -I. -std=c++11 -o bench_gemm bench_gemm.cpp g++ -DAVX -DD_BLOCKSIZE_MR=4 -DD_BLOCKSIZE_NR=8 -O3 -g -I. -std=c++11 -fopenmp -o bench_gemm_omp bench_gemm.cpp g++ -DAVX -DD_BLOCKSIZE_MR=4 -DD_BLOCKSIZE_NR=8 -O3 -g -I. -std=c++11 -DGLOBAL_THREAD_POOL=global_tpool -o bench_gemm_tp bench_gemm.cpp ------------------------------------------------------------------------------- Am Ende können Sie auch gerne Ihre Lösung wieder zu einem tar-Archiv zusammenpacken und mit `submit` einreichen: ---- CODE (type=sh) ----------------------------------------------------------- submit hpc session18 session18.tar ------------------------------------------------------------------------------- :import:session18/bench_gemm.cpp :navigate: up -> doc:index back -> doc:session18/page04