Differences

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

Link to this comparison view

app:laboratoare:05 [2022/10/26 21:10]
florin.mihalache [Elemente de sincronizare]
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 *thread, const pthread_attr_t *attr, void *(*thread_function) (void *), void *arg); +
-</​code>​ +
-unde: +
-  * ''​thread''​ - thread-ul pe care vrem -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: +
-<code c> +
-void *f(void *arg) { +
-    // do stuff +
-    // aici putem să întoarcem un rezultatdacă 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:+===== Implementarea unui program distribuit în MPI ===== 
 +Exemplu de program ​MPI - Hello World:
 <code c> <code c>
-#​include ​<pthread.h>+#​include ​"mpi.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, rank, len
-    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[]) +
-{ +
-    pthread_t threads[NUM_THREADS];​ +
-    int r+
-    ​long id; +
-    void *status; +
-    long arguments[NUM_THREADS]; +
-  +
-    for (id = 0; id < NUM_THREADS;​ id++) { +
-        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 = 0id < NUM_THREADSid++{ +    ​MPI_Init(&argc, &argv); 
-        r = pthread_join(threads[id], &status);+    MPI_Comm_size(MPI_COMM_WORLD,​ &​numtasks); 
 +    MPI_Comm_rank(MPI_COMM_WORLD,&​rank); 
 +    ​MPI_Get_processor_name(hostname, &len); 
 +    if (rank == MASTER) 
 +        printf("​MASTER:​ Number of MPI tasks is: %d\n",​numtasks);​ 
 +    else 
 +        printf("​WORKER:​ Rank: %d\n",​rank);
    
-        if (r+    MPI_Finalize();
-            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. 
 +  * ''​MPI_Comm_size''​ - funcție care determină numărul de procese (numtasks) care rulează în cadrul comunicatorului (de regulă MPI_COMM_WORLD) 
 +  * ''​MPI_Comm_rank''​ - funcție care determină identificatorul (rangul) procesului curent în cadrul comunicatorului. 
 +  * ''​MPI_Get_processor_name''​ - determină numele procesorului 
 +  * ''​MPI_Finalize''​ - declanșează terminarea programului MPI 
 + 
 +În cadrul schimbului de date între procese, este necesar mereu să precizăm tipul acestora. În MPI, se 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: 
 + 
 +^   ''​MPI_Datatype '' ​ ^  Echivalentul din C/C++  ^ 
 +|   ''​MPI_INT'' ​   |     ''​int'' ​   | 
 +|   ''​MPI_LONG'' ​  ​| ​    ''​long'' ​  | 
 +|   ''​MPI_CHAR'' ​  ​| ​   ''​char'' ​   | 
 +|   ''​MPI_FLOAT'' ​ |    ''​float'' ​  | 
 +|  ''​MPI_DOUBLE'' ​ |    ''​double'' ​ | 
 + 
 +===== Funcții de transmisie a datelor în MPI ===== 
 + 
 +<note> 
 +**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. 
 +</​note>​ 
 + 
 +==== MPI_Send ==== 
 +MPI_Send reprezintă funcția prin care un proces trimite date către un alt proces. Semnătura funcției este următoarea:​ 
 + 
 +''​int MPI_Send(void* data, int count, MPI_Datatype datatype, int destination,​ int tag, MPI_Comm communicator)'',​ unde: 
 +  * ''​data''​ (↓) - reprezintă datele trimise de la procesul sursă către procesul destinație 
 +  * ''​count''​ (↓) - dimensiunea datelor transmise 
 +  * ''​datatype''​ (↓) - tipul datelor transmise 
 +  * ''​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. 
 + 
 +==== MPI_Recv ==== 
 +MPI_Recv reprezintă funcția prin care un proces primește date de la un alt proces. Semnătura funcției este următoarea:​ 
 + 
 +''​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. 
 + 
 +Structura ''​MPI_Status''​ include următoarele câmpuri: 
 +  * ''​int count''​ - dimensiunea datelor primite 
 +  * ''​int MPI_SOURCE''​ - identificatorul procesului sursă, care a trimis datele 
 +  * ''​int MPI_TAG''​ - tag-ul mesajului primit 
 + 
 + 
 +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ă. 
 + 
 +Un exemplu de program în care un proces trimite un mesaj către un alt proces:
 <code c> <code c>
-#​include ​<pthread.h>+#​include ​"mpi.h"
 #include <​stdio.h>​ #include <​stdio.h>​
 #include <​stdlib.h>​ #include <​stdlib.h>​
-  
-#define NUM_THREADS 8 
  
-struct pair { +int main (int argc, char *argv[])
-    ​int first, second; +
-}; +
-  +
-void *f(void *arg)+
 { {
-    ​struct pair info = *(struct pair*arg+    ​int  numtasks, rank, len; 
-    ​printf("First = %d; second = %d\n"info.first, info.second); +    char hostname[MPI_MAX_PROCESSOR_NAME];​ 
-    ​pthread_exit(NULL); + 
-+    MPI_Init(&argc, &argv); 
-  +    ​MPI_Comm_size(MPI_COMM_WORLD&​numtasks); // Total number of processes. 
-int main(int argcchar *argv[]+    ​MPI_Comm_rank(MPI_COMM_WORLD,&​rank); // The current process ID / Rank. 
-{ +    ​MPI_Get_processor_name(hostname&len); 
-    ​pthread_t threads[NUM_THREADS]+ 
-    int r+    ​srand(42)
-    ​long id; +    int random_num = rand()
-    void *status; +    ​printf("​Before send: process with rank %d has the number %d.\n",​ rank, 
-    struct pair arguments[NUM_THREADS]+            ​random_num)
-  + 
-    ​for (id = 0; id < NUM_THREADS;​ id++) { +    ​if (rank == 0) { 
-        ​arguments[id].first = id+        ​MPI_Send(&​random_num,​ 1, MPI_INT, 1, 0, MPI_COMM_WORLD); 
-        ​arguments[id].second = id * 2+    } else { 
-        ​r = pthread_create(&threads[id]NULLf(void *) &arguments[id]); +        ​MPI_Status status
-  +        ​MPI_Recv(&random_num1MPI_INT, 0, 0, MPI_COMM_WORLD, &status); 
-        ​if (r) { +        printf("​Process with rank %d, received %d with tag %d.\n", 
-            ​printf("​Eroare la crearea thread-ului ​%ld\n", ​id); +                rank, random_num, status.MPI_TAG);
-            ​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 arraysfiecărui thread îi va reveni o bucată din arraype acea bucată executând funcția atribuită lui (thread-ului).+    printf("​After send: process with rank %d has the number %d.\n"rank, 
 +            random_num);
  
-Formula de împărțire:​ +    MPI_Finalize(); 
-<code c> + 
-start_index = id * (doublen / 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>​ </​code>​
  
-===== Elemente de sincronizare ===== +<note important>​ 
-==== Mutex ==== +Când un proces X trimite un mesaj către un proces Ytag-ul T al mesajului din MPI_Sendexecutat ​de procesul Xtrebuie să fie același cu tag-ul mesajului din MPI_Recvexecutat de procesul Ydeoarece procesul Y așteaptă ​un mesaj care are tag-ul T, altfel, dacă sunt tag-uri diferiteprogramul se va bloca. 
-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țiuniiar 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.+O ilustrație a modului cum funcționează împreună funcțiile MPI_Send ​și MPI_Recv:
  
-Funcții pentru mutex: +{{ :app:laboratoare:send_recv.png?​700 |}}
-  * crearea unui mutex''​int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);''​ +
-  * lock pe mutex''​int pthread_mutex_lock(pthread_mutex_t *mutex);''​ +
-  * unlock pe mutex: ''​int pthread_mutex_unlock(pthread_mutex_t *mutex);''​ +
-  * distrugerea unui mutex: ''​int pthread_mutex_destroy(pthread_mutex_t *mutex);''​+
  
-Exemplu ​de folosire:+Mai jos aveți un exemplu în care un proces trimite un întreg array de 100 de elemente către un alt proces:
 <spoiler Click pentru exemplu> <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 2 +int main (int argc, char *argv[])
- +
-int a = 0; +
-pthread_mutex_t mutex; +
- +
-void *f(void *arg)+
 { {
-    ​// facem lock pe mutex +    ​int numtasks, rank, len
- pthread_mutex_lock(&​mutex)+    ​int size 100
-    ​// zona critica +    ​char hostname[MPI_MAX_PROCESSOR_NAME];​ 
- a +2+    int arr[size];
-    ​// facem unlock pe mutex +
- pthread_mutex_unlock(&​mutex);+
  
- pthread_exit(NULL); +    MPI_Init(&argc, &argv); 
-}+    ​MPI_Comm_size(MPI_COMM_WORLD,​ &​numtasks);​ 
 +    MPI_Comm_rank(MPI_COMM_WORLD,&​rank);​ 
 +    MPI_Get_processor_name(hostname,​ &len);
  
-int main(int argc, char *argv[]) { +    srand(42); 
- int i; +    if (rank == 0) { 
- void *status+        for (int i = 0i < sizei++) { 
- pthread_t threads[NUM_THREADS]; +            arr[i= i
- int arguments[NUM_THREADS];​+        }
  
-    // cream mutexul +        printf("​Process with rank [%d] has the following array:​\n",​ rank); 
- pthread_mutex_init(&mutexNULL);+        ​for ​(int i = 0; i < size; i++) { 
 +            printf("​%d "arr[i]); 
 +        } 
 +        printf("​\n"​);
  
- for (i = 0; i < NUM_THREADS;​ i++{ +        MPI_Send(arr, size, MPI_INT, 1, 1, MPI_COMM_WORLD); 
- arguments[i= i+        ​printf("​Process with rank [%dsent the array.\n",​ rank)
- pthread_create(&​threads[i]NULLf, &arguments[i]); +    } else { 
- }+        MPI_Status status; 
 +        MPI_Recv(arrsizeMPI_INT, 0, 1, MPI_COMM_WORLD, &status); 
 +        printf("​Process with rank [%d], received array with tag %d.\n",​ 
 +                rank, status.MPI_TAG);
  
- for (i = 0; i < NUM_THREADS; i++) { +        printf("​Process with rank [%d] has the following array:​\n",​ rank); 
- pthread_join(threads[i], &status); +        ​for (int i = 0; i < size; i++) { 
- }+            ​printf("%d ", arr[i]); 
 +        } 
 +        printf("​\n"​);​ 
 +    ​}
  
-        // distrugem mutex-ul +    MPI_Finalize();
- pthread_mutex_destroy(&mutex);+
  
- printf("​a = %d\n", a); 
- 
- return 0; 
 } }
 </​code>​ </​code>​
 </​spoiler>​ </​spoiler>​
  
-Barieră +==== MPI_Bcast ==== 
-Bariera este folosită atunci când dorim să sincronizăm thread-urile încât să ajungă (să se sincronizezeîn același punctMai 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șzonăÎn pthreads folosim structura ''​pthread_barrier_t''​ pentru barieră.+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 mesajeașadar ​nu trebuie să apelațMPI_Recv. 
 +</​note>​Semnătura funcției este următoarea:​
  
-Funcții+''​int MPI_Bcast(void* data, int count, MPI_Datatype datatype, int root, MPI_Comm communicator)'',​ unde
-  * crearea unei bariere: ''​int ​pthread_barrier_init(pthread_barrier_t ​*barrierconst pthread_barrierattr_t ​*attrunsigned count);''​ +  * ''​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. 
-  * așteptarea thread-urilor ​la barieră: ''​int ​pthread_barrier_wait(pthread_barrier_t ​*barrier);''​ +  * ''​count''​ (↓) - dimensiunea datelor trimise 
-  * distrugerea unei bariere: ​''​int pthread_barrier_destroy(pthread_barrier_t ​*barrier);''​+  * ''​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 
 + 
 +O ilustrație care arată cum funcționează MPI_Bcast aveți mai jos: 
 + 
 +{{ :​app:​laboratoare:​bcast.png?​500 |}} 
 + 
 +==== 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. 
 + 
 +Semnătura funcției este următoarea:​ 
 +''​int ​MPI_Scatter(voidsend_dataint send_count, MPI_Datatype send_datatype,​ voidrecv_dataint 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''​) 
 + 
 +O ilustrație a modului cum funcționează MPI_Scatter:​ 
 + 
 +{{ :​app:​laboratoare:​scatter.png?​500 |}} 
 +==== 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. 
 + 
 +Semnătura funcției este următoarea: 
 +''​int ​MPI_Gather(voidsend_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''​) 
 + 
 +O ilustrare a modului cum funcționează MPI_Gather:​ 
 + 
 +{{ :​app:​laboratoare:​gather.png?​500 |}}
  
-Exemplu ​de folosire:+Mai jos aveți un exemplu ​de MPI_Scatter folosit împreună cu MPI_Gather:
 <spoiler Click pentru exemplu> <spoiler Click pentru exemplu>
 <code c> <code c>
 #include <​stdio.h>​ #include <​stdio.h>​
 #include <​stdlib.h>​ #include <​stdlib.h>​
-#include <pthread.h>+#include <mpi.h>
  
-#​define ​NUM_THREADS 8+#​define ​ROOT 0 
 +#define CHUNK_SIZE 5 // numarul de elemente per proces
  
-pthread_barrier_t barrier;+int main (int argc, char **argv) { 
 +    int rank, proc, a;
  
-void *f(void *arg) +    intarr; 
-{ +    int* process_arr;​ 
-    int index = *(int *) arg+    ​int* result_arr
-  +     
-    ​printf("​Before barrier - thread %d\n"index); +    ​MPI_Init(&argc&argv); 
-    ​pthread_barrier_wait(&barrier); +    ​ 
-    ​printf("After barrier - thread %d\n"index);+    MPI_Comm_rank(MPI_COMM_WORLD, ​&rank); 
 +    ​MPI_Comm_size(MPI_COMM_WORLD&proc);
  
- pthread_exit(NULL); +    if (rank == ROOT) { 
-}+        arr = malloc (CHUNK_SIZE * proc * sizeof(int)); 
 +        for (int i = 0; i < proc * CHUNK_SIZE; ++i) { 
 +            arr[i] = 0; 
 +        } 
 +    ​}
  
-int main(int argc, char *argv[]) { +    process_arr = malloc ​(CHUNK_SIZE ​sizeof(int))
- int i+    ​MPI_Scatter(arr,​ CHUNK_SIZE, MPI_INT, process_arr,​ CHUNK_SIZE, MPI_INT, ROOT, MPI_COMM_WORLD);
- void *status; +
- pthread_t threads[NUM_THREADS];​ +
- int arguments[NUM_THREADS];+
  
- pthread_barrier_init(&​barrierNULLNUM_THREADS);+    for (int i = 0; i < CHUNK_SIZE; i++) { 
 +        printf("​Before:​ rank [%d] - value = %d\n", rank, process_arr[i]);​ 
 +        process_arr[i] = i; 
 +        printf("​After:​ rank [%d] - value = %d\n"rankprocess_arr[i]); 
 +    }
  
- for (0; i < NUM_THREADS;​ i++) { +    if (rank == ROOT) { 
- arguments[i] ​i; +        ​result_arr ​malloc ​(CHUNK_SIZE * proc * sizeof(int)); 
- pthread_create(&​threads[i],​ NULL, f, &​arguments[i]); +    }
- }+
  
- for (i = 0; i < NUM_THREADS; i++) { +    MPI_Gather(process_arr,​ CHUNK_SIZE, MPI_INT, result_arr, CHUNK_SIZE, MPI_INT, ROOT, MPI_COMM_WORLD);​ 
- pthread_join(threads[i], &status); + 
-+    if (rank == ROOT) { 
- pthread_barrier_destroy(&​barrier); +        ​for (int i = 0; i < CHUNK_SIZE * proc; i++) { 
- return 0;+            ​printf("%d ", result_arr[i]); 
 +        
 +        ​printf("​\n"​);​ 
 +    } 
 + 
 +    if (rank == ROOT) { 
 +        free(arr);​ 
 +        free(result_arr);​ 
 +    } 
 + 
 +    free(process_arr);​ 
 + 
 +    MPI_Finalize(); 
 +    return 0;
 } }
 +
 </​code>​ </​code>​
 </​spoiler>​ </​spoiler>​
 +
 +===== Alte funcții =====
 +  * Funcții nonblocante:​ ''​MPI_Irecv'',​ ''​MPI_Isend'',​ ''​MPI_Ibcast'',​ ''​MPI_Igather'',​ ''​MPI_Iscatter''​ etc.
 +  * Funcții sincrone: ''​MPI_Ssend'',​ ''​MPI_Issend''​
 +  * ''​MPI_Bsend''​ - send cu buffer
 +  * ''​MPI_Barrier''​ - barieră
 +  * ''​MPI_Reduce''​ - operație distribuită de reduce pe arrays
 +
 +Detalii despre aceste funcții puteți vedea [[https://​ocw.cs.pub.ro/​courses/​apd/​laboratoare/​11 | aici]].
 +
 +===== 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.
 +
 +  * 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ă.
 +
 +  * 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''​
 +<​note>​Folosiți MPI_Bcast pentru a propaga valoarea coeficientului introdus de la tastatură.</​note>​
 +
 +  * 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ă).
 +
 +===== Resurse și referințe =====
 +  * [[https://​ocw.cs.pub.ro/​courses/​apd/​laboratoare/​08 | Laboratorul 8 APD]]
 +  * [[https://​ocw.cs.pub.ro/​courses/​apd/​laboratoare/​11 | Laboratorul 11 APD]]
 +  * [[https://​www.open-mpi.org | Open MPI]]
 +  * [[https://​hpc-tutorials.llnl.gov/​mpi/​ | LLNL MPI Tutorial]] ​
app/laboratoare/05.1666807857.txt.gz · Last modified: 2022/10/26 21:10 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