MPI Pessage Passing Interface su Raspberry Pi

MPI  è uno standard di fatto per il calcolo parallelo, che definisce un insieme di interfacce per la creazione di processi paralleli, la comunicazione fra i processi e operazioni tipiche del calcolo parallelo, come meccanismi di sincronizzazione.

MPI, nelle sue implementazioni, fornisce uno strumento efficace per il calcolo parallelo su sistemi cluster o, nei casi più semplici, in sistemi multiprocessore/multicore.

MPI definisce propri tipi dati. Questo, ed altro, favorisce la portabilità su varie piattaforme, uno degli scopi di MPI.

MPI è rilasciato con licenza The 3-Clause BSD License

MPI non definisce librerie o codice. L’implementazione è libera, pertanto chiunque (capace), nel rispetto delle specifiche, può implementare una libreria MPI per il linguaggio che preferisce.

MPI è pensato per programmazione ad alte prestazioni, per questo le principali implementazioni sono per C/C++ e Fortran.

Le due implementazioni più note sono Open MPI e MPICH.

I programmi paralleli sono scomposti in sotto programmi (task, thread) che possono essere eseguiti in concomitanza. Alla difficoltà di suddividere un algoritmo in parti opportune per il parallelismo, si aggiungono altre problematiche, fra queste,  la necessità di comunicare con i vari processi per coordinali, suddividere i dati da elaborare sui vari task e riunirli in un unico risultato.
Questa comunicazione non è semplice e pone diverse tematiche come l’accesso concorrente (in lettura o in scrittura) a dati.
I modi di comunicare sono principalmente due:

  • Condivisione di memoria (shared memory),  aree di memoria comuni a tutti i processi/processori consentono lo scambio dei dati.
  • Comunicazione attraverso meccanismi di rete, quando ogni elaboratore/cpu/processo è il il responsabile  della propria memoria  (distribuited memory). MPI usa questo meccanismo.

Pur essendo destinato a cluster ‘impegnativi’ (si parla di HPC – High Performance Computing https://it.wikipedia.org/wiki/High_Performance_Computing ), MPI si può implementare su un Raspberry Pi, se non altro per provare programmi poco ‘impegnativi’,  imparare e comunque implementare programmi paralleli. Pi 3 ha 4 core che possono lavorare in parallelo.

Inoltre, nulla vieta di mettere insieme alcuni Pi per realizzare un cluster a basso costo.

Volevo fare qualcosa di simile.

Su internet esistono diversi progetti di cluster con Raspberry Pi  ma la maggior parte usano mpi4py, per python, la mia idea era di implementare MPI per C/C++.

Ho iniziato con Open MPI. In rete ho trovato qualcosa che non mi ha soddisfatto. L’installazione dell’eseguibile sembrava mancare di parti. Ho optato per ricompilare Open MPI su Pi: errori/orrori. Alcuni, credo, si riferissero ad istruzioni del processore di Pi per la gestione delle operazioni atomiche, non ho indagato troppo. Insomma non sono riuscito a farlo funzionare, magari qualcuno può darmi spunti. Col tempo, forse, ci riprovo.

Mi sono, poi, orientato a MPICH e ricompilato tutto su Pi. Funziona!

Di seguito le semplici, ma un po’ lunghe, operazioni da seguire.

Sul sito di MPICH, a questo indirizzo, è disponibile il download del sorgente.

Come root, nella cartella /root/bin ho scaricato il programma per linux:

  1. wget http://www.mpich.org/static/downloads/3.2.1/mpich-3.2.1.tar.gz

Si espande il tutto:

  1. tar -xzf mpich-3.2.1.tar.gz
  2. cd mpich-3.2.1.tar.gz

ora la cartella mpich-3.2.1 contiene tutto il codice sorgente.  Preparare e compilare è un po’ lungo, ma funziona.

Si lancia:
./configure  Il comando con l’opzione --help fornisce una miriade di opzioni, delle quali non mi sono curato molto. Io ho usato solo l’opzione --disable-fortran per evitare la  compilazioni di librerie fortran. Sarà opportuno rivedere con più attenzione i vari parametri.

Quindi:

./configure --disable-fortran

Seguito da:

  1. make
  2. make install

Attendere con pazienza…

Alla fine la prima prova può essere:

  1. root@raspberrypi:~/bin/prg# mpiexec --version
  2.  
  3. HYDRA build details:
  4. Version: 3.3b2
  5. Release Date: Mon Apr 9 17:58:42 CDT 2018
  6. CC: gcc
  7. CXX: g++
  8. F77:
  9. F90:

Realizziamo  il solito programma ‘Hello World’ (per me hw.c):
 

  1. #include <stdio.h>
  2. #include "mpi.h"
  3.  
  4. #define MASTER 0
  5.  
  6. int main(int argc, char **argv)
  7.  
  8. {
  9. int numtask, taskid, len;
  10. char hostname[MPI_MAX_PROCESSOR_NAME];
  11.  
  12. // inizializza l’ambiente MPI
  13. MPI_Init(&argc, &argv);
  14.  
  15. // numtask è il numero dei task
  16. MPI_Comm_size(MPI_COMM_WORLD, &numtask);
  17.  
  18. // tasked è l’identificativo di uno specific task
  19.  
  20. MPI_Comm_rank(MPI_COMM_WORLD, &taskid);
  21.  
  22. // Identifica il nodo
  23. MPI_Get_processor_name(hostname, &len);
  24.  
  25. // Stampo il messaggio. Questa istruzione, come quelle all’interno di MPI_Init
  26. // è eseguita da ogni singolo task il cui identificativo è taskid
  27. // quindi avrò la stampa di tante righe quanto i task
  28. printf("Hello world, %d, %s\n", taskid, hostname);
  29.  
  30. // Per il task 0, definite come MASTER, stampo qualcosa di diverso.
  31. if (taskid == MASTER)
  32. printf("Sono MASTER, %d, %s\n", taskid, hostname);
  33.  
  34. MPI_Finalize();
  35. }

Non resta che compilare:

mpicc -o hw hw.c

mpicc è una shell script che richiama gcc con le corrette opzioni e librerie. mpicc –help per avere più informazioni.

Lanciamo il programma:
 

  1. mpirun -np 5 ./hw
  2. Hello world, 0, raspberrypi
  3. Sono MASTER, 0, raspberrypi
  4. Hello world, 1, raspberrypi
  5. Hello world, 3, raspberrypi
  6. Hello world, 4, raspberrypi
  7. Hello world, 2, raspberrypi

-np 5 crea a runtime 5 processi. E’ immaginabile un programma su una CPU con 4 core veda 4 come numero ragionevole di processi. Non è escluso che 5 o 6 processi forniscano migliori prestazioni (in termini di tempo di esecuzione), di certo un numero di processi molto più elevato delle CPU/core disponibili non porta vantaggi. Hello World non è certamente un programma adatto a fare test di prestazioni.

Come si nota, l’ordine di stampa è casuale: il processo per primo esegue printf, stampa il messaggio.

Se questo disposizione non va bene occorre coordinare i task per dare un ordine di esecuzione. Attenzione però: siamo in programmazione parallela. Se l’algoritmo parallelo è ben realizzato, i task sono il più possibile indipendenti, senza troppi ordini di esecuzione e vincoli di dipendenza..

 

Categoria: 

Tags: 

Mi piace: 

0
No votes yet