Differences

This shows you the differences between two versions of the page.

Link to this comparison view

app:laboratoare:05 [2022/10/26 21:20]
florin.mihalache
app:laboratoare:05 [2024/11/05 03:19] (current)
alexandru.bala [MPI_Recv]
Line 1: Line 1:
-====== Laboratorul 5 - pthreads ​====== +====== Laboratorul 5 - MPI ====== 
-===== Despre ​pthreads ​===== +===== Despre ​MPI ===== 
-pthreads ​reprezintă ​o bibliotecă din C/C++nativă Linux, prin care se pot implementa programe multithreaded.+MPI (Message Passing Interface) ​reprezintă ​un standard pentru comunicarea prin mesaje în cadrul programării distribuiteelaborat de MPI Forumși are la bază modelul proceselor comunicante ​prin mesaje.
  
-Spre deosebire de OpenMP, pthreads este low-level ​și oferă ​o mai mare flexibilitate ​în ceea ce privește sincronizarea ​thread-urilor ​și distribuirea task-urilor către thread-uri.+Un proces reprezintă un program aflat în execuție ​și se poate defini ca unitate de bază care poate executa una sau mai multe sarcini ​în cadrul unui sistem de operare. Spre deosebire de thread-uri, un proces are propriul său spațiu de adrese (propria zonă de memorie) ​și acesta poate avea, în cadrul său, mai multe thread-uri ​în execuție, care partajează resursele procesului.
  
-===== Implementarea unui program paralel în pthreads ===== +===== Compilare ​și rulare ===== 
-==== Includere ​și compilare ​==== +În cadrul lucrului în C/C++, MPI reprezintă o bibliotecă,​ care are funcționalitățile implementate într-un header numit **mpi.h**. ​Pentru ​compilarela MPI există un compilator specific: 
-Pentru ​a putea folosi pthreadseste necesar să includem ​în program ​biblioteca ​''​pthread.h''​. De asemenea la compilare este necesar să includem flag-ul ​''​-lpthread''​+  * ''​mpicc'',​ pentru lucrul ​în 
-<code bash> +  * ''​mpic++'',​ pentru lucrul în C++ 
-gcc -o program program.c -lpthread +În ambele limbaje, pentru rularea unui program ​MPI folosim comanda ​''​mpirun''​, împreună cu parametrul ​''​-np''​, unde precizăm numărul de procese care rulează în cadrul programului distribuit.
-./program +
-</​code>​+
  
-==== Crearea și terminarea thread-urilor ==== +Exemplu: 
-În pthreads, avem un thread principal, ​pe care rulează funcția mainDin thread-ul principal se pot crea thread-uri noi, care vor executa task-uri în paralel.+  * compilare:​ 
 +    * C: ''​mpicc hello.c ​-o hello''​ 
 +    * C++: ''​mpic++ hello.cpp -o hello''​ 
 +  * rulare: ''​mpirun -np 4 hello''​ - rulare cu 4 procese 
 +Dacă încercați să rulați comanda mpirun cu un număr de procese mai mare decât numărul de core-uri fizice disponibile ​pe procesorul vostru, este posibil să primiți o eroare cum ca nu aveți destule sloturi liberePuteți elimina acea eroare adăugând parametrul ''​--oversubscribe''​ atunci când rulați ''​mpirun''​.
  
-Pentru a crea thread-uri în pthreadsfolosim funcția ''​pthread_create'':​ +===== Instalare MPI ===== 
-<code c> +Pentru a lucra cu MPItrebuie să instalați biblioteca pentru MPI pe Linuxfolosind următoarea comandă: ''​sudo apt install openmpi-bin openmpi-common openmpi-doc libopenmpi-dev''​
-int pthread_create(pthread_t *threadconst pthread_attr_t *attr, void *(*thread_function) (void *), void *arg); +
-</​code>​ +
-unde: +
-  * ''​thread''​ - thread-ul pe care vrem să-l pornim +
-  * ''​attr'' ​atributele unui thread (''​NULL''​ - atribute default) +
-  * ''​thread_function''​ - funcția pe care să o execute thread-ul +
-  * ''​arg''​ - parametrul trimis la funcția executată de thread (dacă vrem să trimitem mai mulți parametri, îi împachetăm într-un struct+
  
-Exemplu de funcție pe care o execută un thread:+===== Implementarea unui program distribuit în MPI ===== 
 +Exemplu de program MPI - Hello World:
 <code c> <code c>
-void *f(void *arg) { +#​include ​"mpi.h"
-    // do stuff +
-    // aici putem să întoarcem un rezultat, dacă este cazul +
-    pthread_exit(NULL);​ // termină un thread - mereu apelat la finalul unei funcții executate de thread, dacă nu întoarcem un rezultat în funcție +
-+
-</​code>​ +
-Pentru terminarea thread-urilor,​ care vor fi "​lipite înapoi"​ în thread-ul principal, folosim funcția ''​pthread_join'',​ care așteaptă terminarea thread-urilor:​ +
-<code c> +
-int pthread_join(pthread_t thread, void **retval);​ +
-</​code>​ +
-unde: +
-  * ''​thread''​ - thread-ul pe care îl așteptăm să termine +
-  * ''​retval''​ - valoarea de retur a funcției executate de thread (poate fi ''​NULL''​) +
- +
-Exemplu de program scris folosind pthreads: +
-<code c> +
-#​include ​<pthread.h>+
 #include <​stdio.h>​ #include <​stdio.h>​
 #include <​stdlib.h>​ #include <​stdlib.h>​
    
-#​define ​NUM_THREADS 2+#​define ​ ​MASTER 0
    
-void *f(void *arg) +int main (int argc, char *argv[]) { 
-+    ​int numtasks, ranklen
-    ​long id = *(long*) arg; +    ​char hostname[MPI_MAX_PROCESSOR_NAME];
-    printf("​Hello World din thread-ul %ld!\n"​id)+
-    ​return NULL; +
-}+
    
-int main(int argc, char *argv[]) +    MPI_Init(&argc, &argv); 
-{ +    ​MPI_Comm_size(MPI_COMM_WORLD,​ &​numtasks)
-    ​pthread_t threads[NUM_THREADS]+    ​MPI_Comm_rank(MPI_COMM_WORLD,&​rank)
-    ​int r+    ​MPI_Get_processor_name(hostname,​ &len)
-    ​long id+    ​if (rank == MASTER) 
-    ​void *status+        printf("​MASTER:​ Number of MPI tasks is: %d\n",​numtasks)
-    ​long arguments[NUM_THREADS];+    ​else 
 +        printf("​WORKER:​ Rank: %d\n",​rank);
    
-    ​for (id = 0; id < NUM_THREADS;​ id+++    ​MPI_Finalize();
-        arguments[id] = id; +
-        r = pthread_create(&​threads[id],​ NULL, f, (void *) &​arguments[id]);​ +
-  +
-        if (r) { +
-            printf("​Eroare la crearea thread-ului %ld\n",​ id); +
-            exit(-1); +
-        } +
-    } +
-  +
-    for (id = 0; id < NUM_THREADS;​ id++) { +
-        r = pthread_join(threads[id],​ &​status);​ +
-  +
-        if (r) { +
-            printf("​Eroare la asteptarea thread-ului %ld\n",​ id); +
-            exit(-1); +
-        } +
-    } +
-  +
-    return 0;+
 } }
 </​code>​ </​code>​
  
-În caz că dorim să trimitem mai mulți parametri funcției executate de threads, folosim ​un ''​struct''​, în care incapsulăm dateleși îl trimitem ca parametru al funcției executate de threads.+Un comunicator (''​MPI_Comm''​) reprezintă un grup de procese care comunică între ele. ''​MPI_COMM_WORLD'' ​reprezintă comunicatorul defaultdin care fac parte toate procesele.
  
-Exemplu+Funcții
-<spoiler Click pentru exemplu>​ +  * ''​MPI_Init''​ - se inițializează programul MPI, mai precis se creează contextul în cadrul ​căruia rulează procesele. Argumentele din linie de comandă sunt pasate către contextul de rulare a proceselor
-<​code ​c+  * ''​MPI_Comm_size''​ - funcție care determină numărul de procese (numtasks) care rulează în cadrul comunicatorului (de regulă MPI_COMM_WORLD) 
-#include <pthread.h> +  * ''​MPI_Comm_rank''​ - funcție care determină identificatorul (rangul) procesului curent în cadrul comunicatorului
-#include <​stdio.h>​ +  * ''​MPI_Get_processor_name''​ - determină numele procesorului 
-#include <stdlib.h> +  * ''​MPI_Finalize''​ - declanșează terminarea programului MPI
-  +
-#define NUM_THREADS 8+
  
-struct pair { +În cadrul schimbului de date între proceseeste necesar mereu să precizăm tipul acestoraÎn MPIse folosește enum-ul ''​MPI_Datatype''​care se mapează cu tipurile de date din C/C++, după cum puteți vedea în tabelul de mai jos:
-    int firstsecond; +
-}; +
-  +
-void *f(void *arg) +
-+
-    struct pair info = *(struct pair*) arg; +
-    printf("​First = %d; second = %d\n", info.firstinfo.second);​ +
-    pthread_exit(NULL);​ +
-+
-  +
-int main(int argc, char *argv[]) +
-+
-    pthread_t threads[NUM_THREADS];​ +
-    int r; +
-    long id; +
-    void *status; +
-    struct pair arguments[NUM_THREADS];​ +
-  +
-    for (id = 0; id < NUM_THREADS;​ id++) { +
-        arguments[id].first = id; +
-        arguments[id].second = id * 2; +
-        r = pthread_create(&​threads[id],​ NULL, f, (void *) &​arguments[id]);​ +
-  +
-        if (r) { +
-            printf("​Eroare la crearea thread-ului %ld\n"id); +
-            exit(-1); +
-        } +
-    } +
-  +
-    for (id = 0; id < NUM_THREADS;​ id++) { +
-        r = pthread_join(threads[id]&​status);​ +
-  +
-        if (r) { +
-            printf("​Eroare la asteptarea thread-ului %ld\n",​ id); +
-            exit(-1); +
-        } +
-    } +
-  +
-    pthread_exit(NULL);​ +
-+
-</​code>​ +
-</​spoiler>​+
  
-Când dorim să paralelizăm operații efectuate pe arrays, fiecărui thread îi va reveni o bucată ​din array, pe acea bucată executând funcția atribuită lui (thread-ului).+^   ''​MPI_Datatype '' ​ ^  Echivalentul ​din C/C++  ^ 
 +|   ''​MPI_INT'' ​   |     ''​int'' ​   | 
 +|   ''​MPI_LONG'' ​  ​| ​    ''​long'' ​  | 
 +|   ''​MPI_CHAR'' ​  ​| ​   ''​char'' ​   | 
 +|   ''​MPI_FLOAT'' ​ |    ''​float'' ​  | 
 +|  ''​MPI_DOUBLE'' ​ |    ''​double'' ​ |
  
-Formula de împărțire:​ +===== Funcții ​de transmisie a datelor în MPI =====
-<code c> +
-start_index ​id * (double) n / p +
-end_index ​min(n, (id + 1) * (double) n / p)), unde id id-ul thread-ului,​ n dimensiunea array-ului, p numărul ​de threads +
-</​code>​+
  
-===== Elemente de sincronizare ===== +<​note>​ 
-==== Mutex ==== +**Convenție**:​ vom marca cu ↓ parametrii ​de tip input ai unei funcții și cu ↑ parametrii ​de tip output ​în cadrul prezentării funcțiilor de mai jos. 
-Un mutex (mutual exclusion) este folosit pentru a delimita și pentru a proteja o zonă critică, unde au loc, de regulă, operații de citire ​și de scriere. Un singur thread intră ​în zona critică (o rezervă pentru el - lock), unde se execută instrucțiuni, iar celelalte thread-uri așteaptă ca thread-ul curent să termine ​de executat instrucțiunile din zona critică. După ce thread-ul curent termină de executat instrucțiuni în zona critică, aceasta o eliberează (unlock) și următorul thread urmează aceiași pași.+</​note>​
  
-Pentru zonele critice în pthreads folosim ''​pthread_mutex_t'',​ care reprezintă un mutex, care asigură faptul ​că un singur thread accesează zona critică la un moment dat, thread-ul deținând lock-ul pe zona critică în momentul respectiv, și că celelalte thread-uri care nu au intrat încă în zona critică așteaptă eliberarea lock-ului de către thread-ul aflat în zona critică în acel moment.+==== MPI_Send ==== 
 +MPI_Send ​reprezintă ​funcția prin care un proces trimite date tre un alt proces. Semnătura funcției este următoarea:
  
-Funcții pentru mutex: +''​int ​MPI_Send(voiddataint count, MPI_Datatype datatype, int destination,​ int tag, MPI_Comm communicator)''​, unde: 
-  * crearea unui mutex: ​''​int ​pthread_mutex_init(pthread_mutex_t ​*mutexconst pthread_mutexattr_t *attr);''​ +  * ''​data'' ​(- reprezintă datele trimise de la procesul sursă către procesul destinație 
-  * lock pe mutex: ​''​int pthread_mutex_lock(pthread_mutex_t *mutex);''​ +  * ''​count''​ (↓) - dimensiunea datelor transmise 
-  * unlock pe mutex: ​''​int pthread_mutex_unlock(pthread_mutex_t *mutex);''​ +  * ''​datatype'' ​(- tipul datelor transmise 
-  * distrugerea unui mutex: ​''​int pthread_mutex_destroy(pthread_mutex_t *mutex);''​+  * ''​destination''​ (↓) - rangul / identificatorului procesului destinație,​ către care se trimit datele 
 +  * ''​tag'' ​(- identificator al mesajului 
 +  * ''​communicator''​ (↓) - comunicatorul în cadrul căruia se face trimiterea datelor între cele două procese 
 +MPI_Send este o funcție blocantă. Mai precis, programul se blochează până când bufferul dat ca prim parametru poate fi refolosit, chiar dacă nu se execută acțiunea de primire a mesajului transmis de procesul curent (MPI_Recv). Dacă apare cazul în care procesul P1 trimite date (MPI_Send) la procesul P2, iar P2 nu are suficient loc în buffer-ul de recepție (buffer-ul nu are suficient loc liber sau este plin) atunci P1 se va bloca.
  
-Exemplu ​de folosire: +==== MPI_Recv ==== 
-<spoiler Click pentru exemplu>​ +MPI_Recv reprezintă funcția prin care un proces primește date de la un alt proces. Semnătura funcției este următoarea:
-<code c> +
-#include <​stdio.h>​ +
-#include <​stdlib.h>​ +
-#include <​pthread.h>​+
  
-#define NUM_THREADS 2+''​int MPI_Recv(void* data, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm communicator,​ MPI_Status* status)'',​ unde: 
 +  * ''​data''​ (↑) - reprezintă datele primite de la procesul sursă de către procesul destinație 
 +  * ''​count''​ (↓) - dimensiunea datelor primite 
 +  * ''​datatype''​ (↓) - tipul datelor primite 
 +  * ''​source''​ (↓) - rangul / identificatorului procesului sursă, care trimite datele 
 +  * ''​tag''​ (↓) - identificator al mesajului 
 +  * ''​communicator''​ (↓) - comunicatorul în cadrul căruia se face trimiterea datelor între cele două procese 
 +  * ''​status''​ - conține date despre mesajul primit, ''​MPI_Status''​ fiind o structură ce conține informații despre mesajul primit (sursa, tag-ul mesajului, dimensiunea mesajului). Dacă nu dorim să ne folosim de datele despre mesajul primit, punem ''​MPI_STATUS_IGNORE'',​ prin care se ignoră status-ul mesajului. 
 +În situația în care procesul P apelează funcția de MPI_Recv(), el se va bloca până va primi toate datele asteptate, astfel că dacă nu va primi nimic sau ceea ce primește este insuficient,​ P va rămâne blocat. Adică MPI_Recv() se termină doar în momentul în care buffer-ul a fost umplut cu datele așteptate.
  
-int a = 0; +Structura ''​MPI_Status''​ include următoarele câmpuri: 
-pthread_mutex_t mutex;+  * ''​int count''​ - dimensiunea datelor primite 
 +  * ''​int MPI_SOURCE''​ - identificatorul procesului sursă, care trimis datele 
 +  * ''​int MPI_TAG''​ - tag-ul mesajului primit
  
-void *f(void *arg) 
-{ 
-    // facem lock pe mutex 
- pthread_mutex_lock(&​mutex);​ 
-    // zona critica 
- a += 2; 
-    // facem unlock pe mutex 
- pthread_mutex_unlock(&​mutex);​ 
  
- pthread_exit(NULL);​ +MPI_Recv este o funcție blocantă, mai precis programul se poate bloca până când se execută acțiunea de trimitere a mesajului de către procesul sursă.
-}+
  
-int main(int argc, char *argv[]) { +Un exemplu ​de program ​în care un proces trimite un mesaj către un alt proces:
- int i; +
- void *status; +
- pthread_t threads[NUM_THREADS];​ +
- int arguments[NUM_THREADS];​ +
- +
-    // cream mutexul +
- pthread_mutex_init(&​mutex,​ NULL); +
- +
- for (i = 0; i < NUM_THREADS;​ i++) { +
- arguments[i] = i; +
- pthread_create(&​threads[i],​ NULL, f, &​arguments[i]);​ +
-+
- +
- for (i = 0; i < NUM_THREADS;​ i++) { +
- pthread_join(threads[i],​ &​status);​ +
-+
- +
-        // distrugem mutex-ul +
- pthread_mutex_destroy(&​mutex);​ +
- +
- printf("​a = %d\n", a); +
- +
- return 0; +
-+
-</​code>​ +
-</​spoiler>​ +
- +
-==== Barieră ==== +
-Bariera este folosită atunci când dorim să sincronizăm thread-urile încât să ajungă (să se sincronizeze) în același punct. Mai concret, ea asigură faptul că niciun thread, gestionat ​de barieră, nu trece mai departe de zona în care aceasta este amplasată decât atunci când toate thread-urile gestionate de barieră ajung în aceeași zonă. În pthreads folosim structura ''​pthread_barrier_t''​ pentru barieră. +
- +
-Funcții: +
-  * crearea unei bariere: ''​int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned count);''​ +
-  * așteptarea thread-urilor la barieră: ''​int pthread_barrier_wait(pthread_barrier_t *barrier);''​ +
-  * distrugerea unei bariere: ''​int pthread_barrier_destroy(pthread_barrier_t *barrier);''​ +
- +
-Exemplu de folosire: +
-<spoiler Click pentru exemplu>+
 <code c> <code c>
 +#include "​mpi.h"​
 #include <​stdio.h>​ #include <​stdio.h>​
 #include <​stdlib.h>​ #include <​stdlib.h>​
-#include <​pthread.h>​ 
  
-#define NUM_THREADS 8+int main (int argc, char *argv[]) 
 +
 +    int  numtasks, rank, len; 
 +    char hostname[MPI_MAX_PROCESSOR_NAME];​
  
-pthread_barrier_t barrier;+    MPI_Init(&​argc,​ &​argv);​ 
 +    MPI_Comm_size(MPI_COMM_WORLD,​ &​numtasks);​ // Total number of processes. 
 +    MPI_Comm_rank(MPI_COMM_WORLD,&​rank);​ // The current process ID / Rank. 
 +    MPI_Get_processor_name(hostname,​ &len);
  
-void *f(void *arg) +    srand(42); 
-{ +    int random_num ​rand(); 
-    int index *(int *arg; +    printf("​Before ​send: process with rank %d has the number ​%d.\n", ​rank, 
-  +            random_num);
-    printf("​Before ​barrier - thread ​%d\n", index); +
-    pthread_barrier_wait(&​barrier);​ +
-    printf("​After barrier - thread ​%d\n", ​index);+
  
- pthread_exit(NULL); +    if (rank == 0) { 
-}+        MPI_Send(&​random_num,​ 1, MPI_INT, 1, 0, MPI_COMM_WORLD);​ 
 +    } else { 
 +        MPI_Status status; 
 +        MPI_Recv(&​random_num,​ 1, MPI_INT, 0, 0, MPI_COMM_WORLD,​ &​status);​ 
 +        printf("​Process with rank %d, received %d with tag %d.\n",​ 
 +                rank, random_num, status.MPI_TAG); 
 +    }
  
-int main(int argcchar *argv[]+    printf("After send: process with rank %d has the number %d.\n"rank, 
- int i; +            random_num);
- void *status; +
- pthread_t threads[NUM_THREADS];​ +
- int arguments[NUM_THREADS];+
  
- pthread_barrier_init(&​barrier,​ NULL, NUM_THREADS);+    MPI_Finalize();
  
- for (i = 0; i < NUM_THREADS;​ i++) { 
- arguments[i] = i; 
- pthread_create(&​threads[i],​ NULL, f, &​arguments[i]);​ 
- } 
- 
- for (i = 0; i < NUM_THREADS;​ i++) { 
- pthread_join(threads[i],​ &​status);​ 
- } 
- pthread_barrier_destroy(&​barrier);​ 
- return 0; 
 } }
 </​code>​ </​code>​
-</​spoiler>​ 
  
-==== Semafor ==== +<note important>​ 
-Un semafor este un element de sincronizare,​ care reprezintă o generalizare a mutex-ului. Semaforul are un contor care este incrementat la intrarea unui thread în zona de cod critică și care e decrementat când thread-ul respectiv iese din zona critică (contorul nu poate fi negativ în pthreads).+Când un proces X trimite un mesaj către un proces Y, tag-ul T al mesajului din MPI_Send, executat ​de procesul X, trebuie să fie același cu tag-ul mesajului ​din MPI_Recv, executat de procesul Y, deoarece procesul Y așteaptă un mesaj care are tag-ul T, altfel, dacă sunt tag-uri diferite, programul se va bloca. 
 +</​note>​
  
-Pentru ​folosi semafoare în pthreads ne folosim de structura ''​sem_t'',​ pentru care trebuie să includem biblioteca ''​semaphore.h''​.+O ilustrație ​modului cum funcționează împreună funcțiile MPI_Send și MPI_Recv:
  
-Semafoarele POSIX sunt de două tipuri: +{{ :app:​laboratoare:​send_recv.png?​700 |}}
-  * cu nume - sincronizare între procese diferite +
-  * fără nume - sincronizare între thread-urile din cadrul aceluiași proces+
  
-Funcții: +Mai jos aveți un exemplu în care un proces trimite un întreg array de 100 de elemente către un alt proces:
-  * ''​int sem_init(sem_t *sem, int pshared, unsigned int value);''​ - inițiere semafor +
-  * ''​int sem_destroy(sem_t *sem);''​ - distrugere semafor +
-  * ''​int sem_post(sem_t *sem);''​ - acquire +
-  * ''​int sem_wait(sem_t *sem);''​ - release +
- +
-Exemplu folosire - producer - consumer:+
 <spoiler Click pentru exemplu> <spoiler Click pentru exemplu>
 <code c> <code c>
-#define _REENTRANT ​   1 +#include "​mpi.h"​
 #include <​stdio.h>​ #include <​stdio.h>​
 #include <​stdlib.h>​ #include <​stdlib.h>​
  
-#include <​pthread.h>​ +int main (int argc, char *argv[]) 
-#include <​semaphore.h>​+
 +    int numtasks, rank, len; 
 +    int size = 100; 
 +    char hostname[MPI_MAX_PROCESSOR_NAME];​ 
 +    int arr[size];
  
-#define NUM_THREADS ​   50 +    MPI_Init(&​argc,​ &​argv);​ 
-#define CONSUMER ​       0 +    MPI_Comm_size(MPI_COMM_WORLD,​ &​numtasks);​ 
-#define PRODUCER ​       1+    ​MPI_Comm_rank(MPI_COMM_WORLD,&​rank);​ 
 +    ​MPI_Get_processor_name(hostname,​ &len);
  
-#define BUF_LEN ​        3+    srand(42);​ 
 +    if (rank == 0) { 
 +        for (int i = 0; i < size; i++) { 
 +            arr[i] = i; 
 +        }
  
-pthread_mutex_t mutex+        printf("​Process with rank [%d] has the following array:​\n",​ rank)
-sem_t full_sem    // semafor contor al elementelor pline +        for (int i = 0i < size; i++) { 
-sem_t empty_sem   // semafor contor al elementelor goale+            printf("​%d ", arr[i]); 
 +        } 
 +        ​printf("​\n"​);
  
-char buffer[BUF_LEN]; +        MPI_Send(arr,​ size, MPI_INT, 1, 1, MPI_COMM_WORLD);​ 
-int buf_cnt = 0;+        printf("​Process with rank [%dsent the array.\n",​ rank)
 +    } else { 
 +        MPI_Status status; 
 +        MPI_Recv(arr,​ size, MPI_INT, ​0, 1, MPI_COMM_WORLD,​ &​status);​ 
 +        printf("​Process with rank [%d], received array with tag %d.\n",​ 
 +                rank, status.MPI_TAG);
  
-void my_pthread_sleep ​(int millis) { +        printf("​Process with rank [%d] has the following array:​\n",​ rank); 
-    ​struct timeval timeout;+        for (int i = 0; i < size; i++) { 
 +            ​printf("​%d ", arr[i]); 
 +        } 
 +        printf("​\n"​);​ 
 +    }
  
-    ​timeout.tv_sec = millis / 1000; +    ​MPI_Finalize();
-    timeout.tv_usec = (millis % 1000* 1000;+
  
-    select (0, NULL, NULL, NULL, &​timeout);​ 
 } }
 +</​code>​
 +</​spoiler>​
  
-void *producer_func (void *arg) { +==== MPI_Bcast ==== 
-    ​sem_wait ​(&​empty_sem);+MPI_Bcast reprezintă o funcție prin care un proces trimite un mesaj către toate procesele din comunicator ​(message broadcast), inclusiv lui însuși. 
 +<note important>​În cadrul implementării MPI_Bcast sunt executate acțiunile de trimitere și de recepționare de mesaje, așadar nu trebuie să apelați MPI_Recv. 
 +</​note>​Semnătura funcției este următoarea:​
  
-    pthread_mutex_lock ​(&mutex);+''​int MPI_Bcast(void* data, int count, MPI_Datatype datatype, int root, MPI_Comm communicator)'',​ unde: 
 +  * ''​data''​ (↓ + ↑) - reprezintă datele care sunt transmise către toate procesele. Acest parametru este de tip input pentru procesul cu identificatorul ''​root''​ și este de tip output pentru restul proceselor. 
 +  * ''​count''​ (↓) - dimensiunea datelor trimise 
 +  * ''​datatype''​ (↓)- tipul datelor trimise 
 +  * ''​root''​ (↓) - rangul / identificatorului procesului sursă, care trimite datele către toate procesele din comunicator,​ inclusiv lui însuși 
 +  * ''​tag''​ (↓) - identificator al mesajului 
 +  * ''​communicator''​ (↓) - comunicatorul în cadrul căruia se face trimiterea datelor către toate procesele din cadrul acestuia
  
-    buffer[buf_cnt] = '​a';​ +O ilustrație care arată cum funcționează MPI_Bcast aveți mai jos:
-    buf_cnt++;​ +
-    printf ("​Produs un element.\n"​);​+
  
- pthread_mutex_unlock (&​mutex);​+{{ :​app:​laboratoare:​bcast.png?​500 |}}
  
-    my_pthread_sleep (rand () % 1000);+==== MPI_Scatter ==== 
 +MPI_Scatter este o funcție prin care un proces împarte un array pe bucăți egale ca dimensiuni, unde fiecare bucată revine, în ordine, fiecărui proces, și le trimite tuturor proceselor din comunicator,​ inclusiv lui însuși.
  
-    sem_post ​(&​full_sem);+Semnătura funcției este următoarea:​ 
 +''​int MPI_Scatter(void* send_data, int send_count, MPI_Datatype send_datatype,​ void* recv_data, int recv_count, MPI_Datatype recv_datatype,​ int root, MPI_Comm communicator)'',​ unde: 
 +  * ''​send_data''​ (↓) - reprezintă datele care sunt împărțite și trimise către procesele din comunicator 
 +  * ''​send_count''​ (↓) - reprezintă dimensiunea bucății care revine fiecărui proces (de regulă se pune ca fiind dimensiunea_totală / număr_de_procese). 
 +  * ''​send_datatype''​ (↓) - tipul datelor trimise către procese 
 +  * ''​recv_data''​ (↑) - reprezintă datele care sunt primite și stocate de către procese 
 +  * ''​recv_count''​ (↓) - dimensiunea datelor primite (de regulă dimensiunea_totală / număr_de_procese) 
 +  * ''​recv_datatype''​ (↓) - tipul datelor primite de către procese (de regulă este același cu send_datatype) 
 +  * ''​root''​ (↓) - identificatorul procesului care împarte datele și care le trimite către procesele din comunicator,​ inclusiv lui însuși 
 +  * ''​communicator''​ (↓) - comunicatorul din care fac parte procesele (de regulă ''​MPI_COMM_WORLD''​)
  
-    return NULL; +O ilustrație a modului cum funcționează MPI_Scatter:​
-}+
  
-void *consumer_func (void *arg) +{{ :​app:​laboratoare:​scatter.png?​500 |}} 
-    ​sem_wait (&​full_sem);​+==== MPI_Gather ==== 
 +MPI_Gather este o funcție care reprezintă inversul lui MPI_Scatter,​ în sensul că un proces primește elemente de la fiecare proces din comunicator,​ inclusiv de la el însuși, și le unifică într-o singură colecție.
  
-    pthread_mutex_lock ​(&mutex);+Semnătura funcției este următoarea:​ 
 +''​int MPI_Gather(void* send_data, int send_count, MPI_Datatype send_datatype,​ void* recv_data, int recv_count, MPI_Datatype recv_datatype,​ int root, MPI_Comm communicator)'',​ unde: 
 +  * ''​send_data''​ (↓) - reprezintă datele care trimise de fiecare proces către procesul cu id-ul root 
 +  * ''​send_count''​ (↓) - reprezintă dimensiunea bucății trimisă de fiecare proces (de regulă se pune ca fiind dimensiunea_totală / număr_de_procese). 
 +  * ''​send_datatype''​ (↓) - tipul datelor trimise de către procese 
 +  * ''​recv_data''​ (↑) - reprezintă datele care sunt primite și stocate de către procesul root 
 +  * ''​recv_count''​ (↓) - dimensiunea datelor primite (de regulă dimensiunea_totală / număr_de_procese) 
 +  * ''​recv_datatype''​ (↓) - tipul datelor primite de către procesul root (de regulă este același cu send_datatype) 
 +  * ''​root''​ (↓) - identificatorul procesului care primește datele (inclusiv de la el însuși) 
 +  * ''​communicator''​ (↓) - comunicatorul din care fac parte procesele (de regulă ''​MPI_COMM_WORLD''​)
  
-    buf_cnt--;​ +O ilustrare a modului cum funcționează MPI_Gather:
-    char elem = buffer[buf_cnt];​ +
-    printf ("​Consumat un element%c\n", elem);+
  
-    pthread_mutex_unlock (&​mutex);​+{{ :​app:​laboratoare:​gather.png?​500 |}}
  
-    my_pthread_sleep (rand () % 1000); +Mai jos aveți un exemplu ​de MPI_Scatter folosit împreună cu MPI_Gather:
- +
-    sem_post (&​empty_sem);​ +
- +
-    return NULL; +
-+
- +
-int main () { +
-    int i; +
-    int type; +
-    pthread_t tid_v[NUM_THREADS];​ +
- +
-    pthread_mutex_init (&​mutex,​ NULL); +
- +
-    sem_init (&​full_sem,​ 0, 0); +
-    sem_init (&​empty_sem,​ 0, 3); +
- +
-    srand (time (NULL)); +
-    for (i = 0; i < NUM_THREADS;​ i++) { +
-        type = rand () % 2; +
-        if (type == CONSUMER) { +
-        pthread_create (&​tid_v[i],​ NULL, consumer_func,​ NULL); +
- } else { +
-            pthread_create (&​tid_v[i],​ NULL, producer_func,​ NULL); +
-        } +
-+
- +
-    for (i = 0; i < NUM_THREADS;​ i++) { +
-        pthread_join (tid_v[i], NULL); +
-+
- +
- pthread_mutex_destroy(&​mutex);​ +
- +
-    return 0; +
-+
-</​code>​ +
-</​spoiler>​ +
- +
-==== Variabile condiție ==== +
-Variabilele condiție reprezintă o structură de sincronizare,​ care au asociat un mutex, șele au un sistem ​de notificare a thread-urilor,​ astfel încât un thread să fie blocat până când apare o notificare de la alt thread. Pentru a putea folosi variabile condiție în pthreads ne folosim de structura ''​pthread_cond_t''​. +
- +
-Variabilele condiție sunt folosite pentru a bloca thread-ul curent (mutexul și semaforul blochează celelalte thread-uri). Acestea permit unui thread să se blocheze până când o condiție devine adevărată,​ moment când condiția este semnalată de thread că a devenit adevărată și thread-ul / thread-urile blocate de condiție își reiau activitatea o variabilă condiție va avea mereu un mutex pentru a avea race condition, care apare când un thread 0 se pregătește să aștepte la variabila condiție și un thread 1 semnalează condiția înainte ca thread-ul 0 să se blocheze +
- +
-Funcții: +
-  * ''​int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);''​ - inițializare variabilă condiție +
-  * ''​int pthread_cond_destroy(pthread_cond_t *cond);''​ - distrugere variabilă condiție +
-  * ''​pthread_cond_t cond = PTHREAD_COND_INITIALIZER;''​ - inițializare statică a unei variabile condiție (atribute default, nu e nevoie de distrugere / eliberare) +
-  * ''​int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);''​ - blocarea unui thread care așteaptă după o variabilă condiție +
-  * ''​int pthread_cond_signal(pthread_cond_t *cond);''​ - deblocarea unui thread +
-  * ''​int pthread_cond_broadcast(pthread_cond_t *cond);''​ - deblocarea tuturor thread-urilor blocate +
-  *  +
-Exemplu folosire - producer - consumer:+
 <spoiler Click pentru exemplu> <spoiler Click pentru exemplu>
 <code c> <code c>
-#define _REENTRANT ​   1 
- 
 #include <​stdio.h>​ #include <​stdio.h>​
 #include <​stdlib.h>​ #include <​stdlib.h>​
 +#include <​mpi.h>​
  
-#include <​pthread.h>​+#define ROOT 0 
 +#define CHUNK_SIZE 5 // numarul de elemente per proces
  
-#define NUM_THREADS ​   50 +int main (int argc, char **argv) { 
-#define CONSUMER ​       0 +    int rank, proc, a;
-#define PRODUCER ​       1+
  
-#define BUF_LEN ​        3+    int* arr; 
 +    int* process_arr;​ 
 +    int* result_arr;​ 
 +     
 +    MPI_Init(&​argc,​ &​argv);​ 
 +     
 +    MPI_Comm_rank(MPI_COMM_WORLD,​ &​rank);​ 
 +    MPI_Comm_size(MPI_COMM_WORLD,​ &proc);
  
-pthread_mutex_t mutex// folosit pentru incrementarea si decrementarea marimii buffer-ului +    if (rank == ROOT) { 
-pthread_cond_t full_cond// cand buffer-ul este gol +        arr = malloc (CHUNK_SIZE * proc * sizeof(int))
-pthread_cond_t empty_cond// cand buffer-ul este plin+        for (int i = 0i < proc * CHUNK_SIZE; ++i) { 
 +            ​arr[i] = 0; 
 +        } 
 +    }
  
-char buffer[BUF_LEN]+    process_arr = malloc (CHUNK_SIZE * sizeof(int))
-int buf_cnt = 0;+    ​MPI_Scatter(arr,​ CHUNK_SIZE, MPI_INT, process_arr,​ CHUNK_SIZE, MPI_INT, ROOT, MPI_COMM_WORLD);
  
-void my_pthread_sleep(int millis) { +    for (int i = 0; i < CHUNK_SIZE; i++) { 
- struct timeval timeout;+        ​printf("​Before:​ rank [%d] - value = %d\n", rank, process_arr[i]); 
 +        process_arr[i] = i; 
 +        printf("​After:​ rank [%d] - value = %d\n", rank, process_arr[i]);​ 
 +    }
  
- timeout.tv_sec ​millis / 1000; +    if (rank == ROOT) { 
- timeout.tv_usec ​= (millis % 1000) 1000;+        ​result_arr ​malloc ​(CHUNK_SIZE ​proc * sizeof(int)); 
 +    }
  
- select ​(0NULLNULLNULL&​timeout); +    MPI_Gather(process_arrCHUNK_SIZEMPI_INTresult_arrCHUNK_SIZE, MPI_INT, ROOT, MPI_COMM_WORLD);
-}+
  
-void *producer_func ​(void *arg) { +    if (rank == ROOT) { 
- pthread_mutex_lock ​(&​mutex);​ +        ​for ​(int i 0; i < CHUNK_SIZE * proc; i++) { 
-  +            ​printf("%d "result_arr[i]);​ 
- // cat timp buffer-ul este plin, producatorul asteapta +        } 
- while (buf_cnt ​== BUF_LEN) { +        printf("​\n"​); 
- pthread_cond_wait ​(&​full_cond&mutex); +    }
- }+
  
- buffer[buf_cnt] ​'​a';​ +    if (rank == ROOT) { 
- buf_cnt+++        ​free(arr)
- printf ​("​Produs un element.\n"​);+        free(result_arr); 
 +    }
  
- pthread_cond_signal ​(&​empty_cond);​ +    free(process_arr);
- my_pthread_sleep (rand () % 1000);+
  
- pthread_mutex_unlock ​(&mutex); +    MPI_Finalize(); 
- +    return ​0;
- return ​NULL;+
 } }
  
-void *consumer_func (void *arg) { 
- pthread_mutex_lock (&​mutex);​ 
- 
- // cat timp buffer-ul este gol, consumatorul asteapta 
- while (buf_cnt == 0) { 
- pthread_cond_wait (&​empty_cond,​ &​mutex);​ 
- } 
- 
- buf_cnt--; 
- char elem = buffer[buf_cnt];​ 
- printf ("​Consumat un element: %c\n", elem); 
- 
- pthread_cond_signal (&​full_cond);​ 
- my_pthread_sleep (rand () % 1000); 
- 
- pthread_mutex_unlock (&​mutex);​ 
- 
- return NULL; 
-} 
- 
-int main() { 
- int i; 
- int type; 
- pthread_t tid_v[NUM_THREADS];​ 
- 
- pthread_mutex_init (&​mutex,​ NULL); 
- pthread_cond_init (&​full_cond,​ NULL); 
- pthread_cond_init (&​empty_cond,​ NULL); 
- 
- srand (time (NULL)); 
- for (i = 0; i < NUM_THREADS;​ i++) { 
- type = rand () % 2; 
- if (type == CONSUMER) { 
- pthread_create (&​tid_v[i],​ NULL, consumer_func,​ NULL); 
- } else { 
- pthread_create (&​tid_v[i],​ NULL, producer_func,​ NULL); 
- } 
- } 
- 
- for (i = 0; i < NUM_THREADS;​ i++) { 
- pthread_join (tid_v[i], NULL); 
- } 
- 
- pthread_mutex_destroy(&​mutex);​ 
- 
- return 0; 
-} 
 </​code>​ </​code>​
 </​spoiler>​ </​spoiler>​
  
-===== Modele de programare ​===== +===== Alte funcții ​===== 
-==== Boss worker ==== +  * Funcții nonblocante:​ ''​MPI_Irecv'',​ ''​MPI_Isend'',​ ''​MPI_Ibcast'',​ ''​MPI_Igather'',​ ''​MPI_Iscatter''​ etc. 
-Modelul boss-worker este o versiune generalizată a modelului producer-consumer,​ unde avem un thread ​cu rolul de boss / master, care produce task-uri, și restul thread-urilor au rol de worker, ele având rolul de a executa task-urile produse de thread-ul boss.+  * Funcții sincrone: ''​MPI_Ssend'',​ ''​MPI_Issend''​ 
 +  * ''​MPI_Bsend'' ​send cu buffer 
 +  * ''​MPI_Barrier'' ​barieră 
 +  * ''​MPI_Reduce'' ​operație distribuită ​de reduce pe arrays
  
-Task-urile sunt puse într-o coadă de către thread-ul boss șpreluate de către thread-urile worker, care le execută.+Detalii despre aceste funcții putețvedea [[https://​ocw.cs.pub.ro/​courses/​apd/​laboratoare/​11 | aici]].
  
-În general avem un singur thread boss, dar putem avea mai multe thread-uri de tip boss.+===== Exerciții ===== 
 +  * Rulați exemplele de cod din [[https://​github.com/​cs-pub-ro/​app-labs/​tree/​master/​lab4/​demo | folder-ul de demo]] din cadrul laboratorului.
  
-Probleme ce pot apărea: +  ​Scrieți un program ce adună un vector de elemente folosind MPI (pentru ​ușurință considerați că numărul ​de elemente e divizibil cu numărul de procese), ​folosind ​doar MPI_Send și MPI_Recv. Fiecare proces va calcula o suma intermediară,​ procesul master fiind cel care va calcula suma finală.
-  ​performanța când sunt multe task-uri executate prin refolosirea thread-urilor +
-  * resursele limitate ​pentru ​execuția thread-urilor +
-  * probleme ​de sincronizare +
-Boss worker se poate implementa ​folosind:+
  
-  * două variabile condiționale +  * Extindeți programul ​de calcul al sumei unui vector prin adăugarea unui coeficient la suma finală, fiecare proces va calcula suma parțială * coeficient''​sum = sum coeficient''​ 
-    * prima folosită de thread-urile worker să aștepte când coada este goală. Dacă nu e goală, workers semnalează thread-urilor boss șiau din coadă +<​note>​Folosiți MPI_Bcast ​pentru ​a propaga valoarea coeficientului introdus ​de la tastatură.</​note>​
-    * a doua folosită ​de thread-urile boss să aștepte când coada este plină. Dacă nu e plină, boss semnalează thread-urilor worker și pune în coadă. +
-  * un mutex - protejează coada, variabilele condiționale și contoarele +
-  * o coadă - sincronizare când se pun / preiau date de către thread-uri +
-    ​dimensiunea cozii poate să fie: +
-      ​statică - sunt necesare două variabile condiție (una pentru boss, alta pentru workers) +
-      * dinamică - o necesară o variabilă condiție pentru ​workers (când coada e goală), dar are un dezavantaj pentru boss, mai precis acesta poate să fie supraîncărcat cu task-uri +
-  * trei contoare: +
-    * numărul ​de task-uri în coadă +
-    * numărul de thread-uri worker în așteptare +
-    * numărul de thread-uri boss în așteptare+
  
-Thread-urile acționează într-o buclă continuă în ceea ce privește crearea și execuția task-urilor șele nu își termină execuția când coada este goală, astfel trebuie să avem o modalitate de terminare a execuției thread-urilor boss și workers.+  * Modificați programul de calcul al sumei unui vector astfel încât să folosiți MPI_Scatter ​și MPI_Gather pentru transferul informației (vector parțial și suma parțială).
  
-Putem face terminarea thread-urilor în două moduri: +===== Resurse și referințe ===== 
-  * un task special de tip ”exit” pentru thread-urile worker, care se vor opri când primesc acest task (ordinea task-urilor să fie FIFO) +  * [[https://​ocw.cs.pub.ro/​courses/​apd/​laboratoare/​08 | Laboratorul 8 APD]] 
-  * coada să aibă un flag de exit (ea trebuie să fie goală), setat de către thread-ul boss. Thread-urile worker se opresc când văd că coada este goală și flag-ul de exit setat pe true, iar apoi thread-ul boss se oprește +  * [[https://​ocw.cs.pub.ro/​courses/​apd/​laboratoare/​11 | Laboratorul 11 APD]] 
- +  * [[https://​www.open-mpi.org | Open MPI]] 
-==== Work crew ==== +  * [[https://​hpc-tutorials.llnl.gov/​mpi/​ | LLNL MPI Tutorial]] ​
-Work crew reprezintă un model de programare paralelă, unde avem un thread principal și N - 1 thread-uri, care sunt controlate de către thread-ul principal, care le creeazăCele N - 1 thread-uri sunt thread-uri de tip worker, care execută task-uri distribuite de către thread-ul principal, pe care, de asemenea, le execută, după ce thread-urile worker au terminat execuția lor+
- +
-===== Probleme de sincronizare ​opțional ===== +
- +
-===== Exerciții =====+
app/laboratoare/05.1666808411.txt.gz · Last modified: 2022/10/26 21:20 by florin.mihalache
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0