Cluster beowulf

27
Costruzione di un cluster Beowulf A.S. 2007/08 ITIS G. Vallauri Specializzazione Informatica, progettazione software Realizzazione di un cluster per HPC con l'uso di calcolatori commerciali (cluster Beowulf) Progetto realizzato da Fanti Marco Diana Lorenzo Classe V B Informatica 1

description

Costruzione di un cluster BeowulfA.S. 2007/08 ITIS G. Vallauri Specializzazione Informatica, progettazione softwareRealizzazione di un cluster per HPC con l'uso di calcolatori commerciali (cluster Beowulf)Progetto realizzato da Fanti Marco Diana LorenzoClasse V B Informatica1Costruzione di un cluster BeowulfIndice1 Introduzione all'High Performance Computing 1.1 Algoritmi parallelizzabili 1.2 Architettura di un cluster HPC 2 Introduzione al boot da rete 2.1 PXE 3 Costruzione del c

Transcript of Cluster beowulf

Page 1: Cluster beowulf

Costruzione di un cluster Beowulf

A.S. 2007/08

ITIS G. Vallauri

Specializzazione Informatica, progettazione software

Realizzazione di un cluster per HPC con l'uso di calcolatori commerciali (cluster Beowulf)

Progetto realizzato da

Fanti Marco Diana Lorenzo

Classe V B Informatica

1

Page 2: Cluster beowulf

Costruzione di un cluster Beowulf

Indice 1 Introduzione all'High Performance Computing 4

1.1 Algoritmi parallelizzabili 4

1.2 Architettura di un cluster HPC 4

2 Introduzione al boot da rete 5

2.1 PXE 5

3 Costruzione del cluster 6

3.1 Premesse 6

3.2 Installazione e configurazione del server 6

3.2.1 Installazione 6

3.2.2 Partizionamento dei dischi 6

3.2.3 Configurare la rete del server 7

3.2.4 NFS – Network File System 7

3.2.5 Configurare il server DHCP 7

3.2.6 Configurare il server TFTP 8

3.2.7 Configurare PXE 9

3.3 Installare e configurare i nodi client 10

3.3.1 Instsallare il sistema operativo 10

3.3.2 Configurare le schede di rete 10

3.3.3 Terminare l'installazione 10

4 MPI – Message Passing Interface 11

4.1 Introduzione a Lam-MPI 11

4.2 Composizione della System Service Interface 12

4.2.1 Modulo Boot 12

4.2.2 Modulo Coll 12

4.2.3 Modulo RPI 12

4.3 Installare Lam-MPI 13

4.3.1 Compilare Lam-MPI 13

4.3.2 Configurare SSH 13

4.4 Uso di Lam-MPI 14

4.4.1 Avvio del cluster 14

4.4.2 Compilazione dei programmi 14

2

Page 3: Cluster beowulf

Costruzione di un cluster Beowulf

4.4.3 Esecuzione dei programmi com mpirun 15

4.4.4 Esecuzione dei programmi con mpiexec 15

4.4.5 Altri comandi utili 16

5 Programmare con le librerie MPI 17

5.1 Struttura di un programma parallelizzabile 17

5.2 Tipi speciali di variabili 17

5.2.1 L'oggetto Communicator 17

5.2.2 La struttura MPI_Status 18

5.2.3 Il tipo MPI_Operation 18

5.2.4 Il tipo MPI_Datatype 18

5.3 Funzioni principali 19

5.3.1 Rank & Size 19

5.3.1.1 Come ottenere il rank 19

5.3.1.2 come ottenere il size 19

5.3.2 Comunicazioni punto-punto 19

5.3.2.1 MPI_Send 19

5.3.2.2 MPI_Recv 20

5.3.2.3 Comunicazioni punto-punto asincrone 20

5.3.2.4 Esempio di comunicazione punto-punto 21

5.3.3 Comunicazioni collettive 22

5.3.3.1 Sincronizzare i processi 22

5.3.3.2 Messaggi di broadcast 22

5.3.3.3 Operazione reduce 23

5.3.3.4 Operazioni Scatter e Gather 23

5.3.3.5 Scatter 24

5.3.3.6 Gather 24

5.3.4 Conclusioni 24

5.4 Programma di esempio – calcolo dell'integrale 25

3

Page 4: Cluster beowulf

Costruzione di un cluster Beowulf

1. Introduzione all'High Performance ComputingLa nostra area di progetto ha avuto come scopo la creazione di un cluster High Performance Computing (HPC) tramite l'uso di calcolatori commerciali.

I cluster HPC sono molto usati in svariati campi come la ricerca scientifica o il cinema, o più in generale in tutto ciò che richiede un'elevata potenza di calcolo. Essi sono solitamente dei supercomputer realizzati appositamente da aziende del calibro di Sun, IBM o HP, dove ogni calcolatore può avere anche alcune centinaia di processori.Esistono poi i cluster Beowulf, realizzati con semplici PC collegati tramite una rete convenzionale. Questo tipo di cluster è considerato economico, e per questo viene realizzato e utilizzato da molte università o semplici gruppi di ricercatori, i quali non si possono permettere l'acquisto di un supercomputer.

1.1. Algoritmi parallelizzabili

Se il principale scopo di un cluster HPC è svolgere nel minor tempo possibile un lavoro, l'algoritmo alla base di tale lavoro deve essere parallelizzabile. Per parallelismo di un algoritmo si intende la possibilità di poterlo applicare contemporaneamente a dati diversi: ovviamente, nel caso di un cluster, la mole dei dati da elaborare sarà enorme.

1.2. Architettura di un cluster HPC

I calcolatori multi-core sono chiamati a memoria condivisa, in quanto le varie CPU condividono le stesse risorse e, in particolare, la stessa memoria cache e la stessa RAM.

Un cluster invece è un sistema a memoria distribuita, ciò significa che non esiste una memoria centrale, ma che ogni nodo ha a disposizione una memoria cache e una RAM proprie.

È difficile distinguere tra pregi e difetti di questi due sistemi, perchè a livello teorico entrambi i sistemi necessitano di lavorare su algoritmi parallelizzabili per poter sfruttare tutte le CPU. Bisogna notare però che non si è ancora arrivati a montare più di 8 unità di elaborazione all'interno della stessa macchina, mentre teoricamente un sistema a memoria distribuita può avere infiniti nodi. Nella pratica nulla vieta di utilizzare entrambi i sistemi (memoria condivisa e distribuita) per la realizzazione di un cluster, anzi, utilizzare macchine multi-core ottimizza il consumo di energia a parità di prestazioni.

Come si può vedere nello schema i vari nodi, in un sistema a memoria distribuita, sono collegati tra loro tramite una rete; per ottenere migliori performance questa rete deve essere molto veloce perchè con una rete lenta aggiungere nodi non migliorerebbe le prestazioni. La soluzione ottimale dal punto di vista economico-prestazionale è utilizzare una LAN gigabit.

4

Page 5: Cluster beowulf

Costruzione di un cluster Beowulf

2. Introduzione al boot da reteVista l'architettura dei cluster, è preferibile che tutti i PC escluso il nodo principale o Master siano senza hard disk. Questo permette di risparmiare elettricità, essendoci solo una macchina provvista di disco, e riduce gli sprechi: visto infatti che i vari nodi del cluster hanno installato un sistema minimale che occupa al massimo 500 Mbyte, è inutile inserire un disco capiente per poi non utilizzarlo.

I nodi del cluster effettuano il boot tramite la rete utilizzando il protocollo PXE, standard di fatto in questo campo e ormai integrato in tutte le schede madri.

2.1 PXE

PXE è un programmino residente nel bios incaricato di effettuare il boot tramite la rete. All'avvio PXE ottiene dinamicamente un indirizzo IP tramite dhcp, utilizzando solitamente la scheda ethernet integrata nella scheda madre. Tramite il servizio dhcp il client viene a conoscenza, oltre che del suo indirizzo IP, subnet mask e gateway, anche di due parametri che spesso vengono ignorati, ma che qui sono fondamentali. Questi due parametri sono il next_server e il boot_file, utilizzati appunto da PXE

per iniziare il boot. Next_server infatti è l'indirizzo IP da cui scaricare, tramite protocollo TFTP, il file situato in posizione boot_file. Una volta scaricato questo file, PXE lo esegue (è un file in linguaggio macchina). Nel caso che il sistema operativo del client sia GNU/Linux, la procedura di boot da qui in poi è la seguente:

1. Si scarica dal server, tramite protocollo TFTP, un file contenente la posizione del kernel e le opzioni con cui esso va eseguito. Il nome di questo file può variare e sarà trattato più nel dettaglio nella sezione 3.2.8 – Configurare PXELinux

2. Si scarica dal server, sempre tramite TFTP, il kernel da avviare e l'eventuale ramdisk assocato

3. Si avvia il kernel a cui, nei parametri d'esecuzione, è stata passata la posizione del proprio file system

4. Infine viene montato il file system tramite NFS e il boot viene completato

5

Illustrazione 1: GNU/Linux mentre si avvia dalla rete grazie a PXE

Page 6: Cluster beowulf

Costruzione di un cluster Beowulf

3. Costruzione del clusterPer spiegare come abbiamo costruito il cluster bisogna spiegare prima come abbiamo configurato il computer principale, che chiameremo server, per poi rivolgerci sulla configurazione dei client.

3.1. Premesse

Abbiamo deciso di utilizzare come client dei PC diskless, quindi senza disco rigido, che effettueranno il boot tramite la rete. Sia il server che i client andranno configurati in modo opportuno.

3.2. Installazione e configurazione del Server

Come sistema operativo per il PC server abbiamo scelto Debian GNU/Linux nella sua versione Stable attuale, cioè Etch. Questo sistema operativo è famoso per la sua leggerezza e stabilità in ambito server, per questo lo abbiamo preferito rispetto ad altre distribuzioni GNU/Linux come Ubuntu, che ci avrebbero permesso di raggiungere gli stessi risultati. Abbiamo scartato l'opzione di utilizzare Microsoft Windows perché non sarebbe stato facile fare il boot tramite la rete per i PC client, oltre al fatto che usare Linux porta a prestazioni maggiori, costa di meno ed è più didattico.

3.2.1. Installazione

Dopo che il server è stato assemblato a dovere abbiamo scaricato dal sito www.debian.org l'ultima release stable di GNU/Linux Debian e l'abbiamo masterizzata su un CD. L'abbiamo installata seguendo la normale procedura tra cui il partizionamento dei dischi; quest'ultimo passo necessita di particolare attenzione.

3.2.2. Partizionamento dei dischi

Sul computer server abbiamo montato 3 hard-disk di recupero da 20 GB ognuno. Abbiamo configurato i 3 dischi come un unico volume logico da poco meno di 60 GB con file system ext3. I 3 dischi sono quindi visti dal sistema operativo come un'unica partizione. Abbiamo scelto il file system ext3 perchè supporta il journaling, il che ci garantisce una frammentazione pressochè nulla, oltre alla sicurezza che in caso di improvviso spegnimento non ci sia perdita di dati.

Abbiamo riservato 1 GB dell'ultimo hard disk per una partizione di swap, ossia una partizione utilizzata come memoria virtuale.

6

Page 7: Cluster beowulf

Costruzione di un cluster Beowulf

3.2.3. Configurare la rete del server

Il nostro server è dotato di 3 interfacce di rete, il cui scopo è rispettivamente

1. Permette la comunicazione con l'esterno del cluster (eth1)

2. Condivide il file system con i nodi diskless (eth3)

3. Viene utilizzata dall'interfaccia MPI del cluster per la comunicazione tra i processi che si trovano su nodi diversi. Questa scheda di rete supporta una velocità di 1 Gbps. (eth0)

La prima scheda di rete è stata configurata durante l'installazione per acquisire l'indirizzo dinamicamente tramite DHCP, alla seconda e alla terza abbiamo invece impostato un indirizzo IP fisso. Più precisamente la scheda di rete che condivide il file system ha indirizzo IP 192.168.2.1/24, mentre l'altra ha 192.168.3.1/24.

Vedremo in seguito la procedura per condividere il file system in modo da permettere il boot da rete.

3.2.4. NFS – Network File System

Per condividere in rete il file system del server abbiamo utilizzato il protocollo NFS, molto usato con sistemi operativi Unix.Per installare il servizio NFS su Debian Etch abbiamo installato il pacchetto nfs-kernel-server, con il comando # apt-get install nfs-kernel-server

Fatto questo siamo andati a modificare il file di configurazione di nfs, situato in /etc/exports, in questo modo:

/nodes/nfs/nodo2 192.168.2.0/24(rw,no_root_squash,sync,no_subtree_check)/nodes 192.168.2.0/24(rw,no_root_squash,sync,no_subtree_check)/home 192.168.2.0/24(rw,no_root_squash,sync,no_subtree_check)

La prima parte di ogni riga indica la cartella da esportare, la seconda indica le reti che vi potranno accedere (nel nostro caso la lan 10/100 192.168.2.0/24), mentre nella terza parte ci sono le opzioni con cui la cartella viene esportata.Rw indica che la cartella sarà asportata con i permessi di lettura e scrittura, sync indica che ogni modifica comandata da remoto dovrà avvenire immediatamente sull'hard disk del server. Senza l'opzione no_root_squash il nodo diskless sarebbe visto dal server come l'utente nobody, e non potrebbe lavorare correttamente sul file system condiviso..La cartella /nodes/nfs/nodo2 conterrà, dopo l'installazione dei sistemi client, il file system utilizzato dal secondo nodo, e dovrà esserci una linea simile, in cui cambia solo il numero del nodo, per ogni nodo del cluster escluso il server. Le altre due entry del file non sono strettamente necessarie, ma potrebbero tornare utili nella manutenzione del cluster.

3.2.5. Configurare il server DHCP

Il server dhcp è configurato in modo da assegnare sempre gli stessi indirizzi agli stessi PC, per fare questo nel suo file di configurazione bisogna specificare gli indirizzi mac delle schede dei client.Come è già stato anticipato nel capitolo riguardante l'avvio da rete, il server dhcp passa ai client anche alcune informazioni utili a PXE per eseguire il boot. Una volta installato il pacchetto dhcp3-server bisogna configurare il server editando il file /etc/dhcp3/dhcpd.conf in questo modo:

ddns-updates off;

7

Page 8: Cluster beowulf

Costruzione di un cluster Beowulf

option T150 code 150 = string;deny client-updates;next-server 192.168.2.1; #TFTP server. riga necessaria

#solo se il server TFTP è #diverso dal gateway

ddns-update-style none;

# per ogni nodo serve una sezione come la seguentehost nodo2 { #opzioni specifiche per nodo2 hardware ethernet 00:30:05:31:67:d6; fixed-address 192.168.2.2; #indirizzo riservato a nodo2 option root-path "/nodes/nfs/nodo2"; #posizione dove si trova la root} #di nodo2 sul server

#opzioni comuni a tutti i nodisubnet 192.168.2.0 netmask 255.255.255.0 { interface eth3; #interfaccia di rete da usare range 192.168.2.150 192.168.2.200; default-lease-time 6000; #imposta il lease time max-lease-time 7200; option domain-name "bellowulf.edu"; option subnet-mask 255.255.255.0; #imposta subnet-mask option broadcast-address 192.168.2.255; #indirizzo di broadcast option routers 192.168.2.1; #IP del gateway (il server) #IP dei DNS, in questo caso utilizziamo gli openDNS (vedi openDNS.com) option domain-name-servers 208.67.222.222, 208.67.220.220; option time-offset -3600; filename "/tftpboot/pxelinux.0"; #posizione del file che PXE deve } #eseguire per cominciare il boot

3.2.6 Configurare il server TFTP

TFTP è il protocollo che i nodi in fase di boot utilizzano per scaricare dal server il file pxelinux.0 e successivamente il kernel con i parametri da passargli all'avvio. Su Debian etch abbiamo scelto di utilizzare il pacchetto atftpd, un server tftp molto leggero che si appoggia xinetd per avviarsi. Per installare questo servizio abbiamo dato il comando

# apt-get install atftpd xinetd

Bisogna poi configurare xinetd in modo da fargli avviare atftpd creando il file /etc/xinetd.d/atftpd e scrivendoci dentro:

service tftp

{disable = nosocket_type = dgramprotocol = udpwait = yesuser = nobodyserver = /usr/sbin/in.tftpdserver_args = --tftpd-timeout 300 --retry-timeout 5

--mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot

}

Successivamente creiamo la cartella /tftpboot e le diamo tutti i permessi con i comandi

8

Page 9: Cluster beowulf

Costruzione di un cluster Beowulf

# mkdir /tftpboot# chmod 777 /tftpboot

3.2.7. Configurare PXE

Per iniziare bisogna scaricare PXELinux dal sito http://syslinux.zytor.com/pxe.php. All'interno della tarball l'unico file che ci interessa è pxelinux.0 che va copiato nella cartella /tftpboot.

Dopo aver fatto ciò bisogna installare il sistema operativo sui client (vedi unità 3.3) prima di continuare a configurare PXE. Una volta eseguita l'installazione dei client come spiegata nell'unità 3.4, sul server si ottengono tante cartelle chiamate nodoX (dove x è il numero del nodo) poste all'interno della cartella /nodes/nfs. Il file system del primo client quindi si trova nella cartella /nodes/nfs/nodo2, che non a caso è una delle cartelle condivise da NFS.

Per terminare la configurazione di PXE dobbiamo per prima cosa eseguire un chroot su ogni cartella dei client:

# chroot /nodes/nfs/nodo2 /bin/bash

Una volta entrati nel chroot bisogna dire al nodo che dovrà avviarsi dalla rete: si fa modificando il file /etc/initramfs-tools/initramfs.conf, andando a scrivere BOOT=nfs al posto di BOOT=local. Per poi lanciare il comando

# update-initramfs -u

Fatto questo si può uscire dal chroot con un semplice comando exit.Per spiegare cosa si è realmente fatto con queste ultime operazioni dobbiamo prima spiegare cos'è in Linux l'initramfs: si tratta di un piccolo file che all'avvio viene montato dal kernel come disco virtuale in memoria ram, e contiene i driver per permettere la prima parte del boot di Linux. In questo caso abbiamo modificato le impostazioni dell'initramfs in modo che il kernel all'avvio sia in grado di montare i file system in rete invece che in locale.Come penultimo passo dobbiamo linkare il kernel e l'initramfs del client nella cartella /tftpboot del server, da dove poi verranno scaricati dai client in fase di avvio. Per fare ciò bastano i comandi

# cd /tftpboot# ln -s /nodes/nfs/nodo2/boot/initrd.img-VERSIONE_KERNEL# ln -s /nodes/nfs/nodo2/boot/vmlinuz-VERSIONE_KERNEL

dove VERSIONE_KERNEL è la versione del kernel che volete copiare.

Nulla vieta di copiare il kernel invece che linkarlo, il risultato è lo stesso.Noi abbiamo lo stesso kernel per tutti i nodi client, se non si vuole fare così basta copiare nella cartella /tftpboot tanti kernel quanti ne servono, basta che si ricordino i nomi per la fase successiva.

Per terminare la configurazione di PXELinux bisogna creare la cartella /tftpboot/pxelinux.cfg

# mkdir /tftpboot/pxelinux.cfg

all'interno della quale creeremo un file per ogni client, dove il file avrà un nome del tipo 01-xx-xx-xx-xx-xx-xx, con l'indirizzo MAC della scheda di rete del client al posto delle x. Per esempio un nome possibile per questo file è 01-00-30-4f-5e-a1-60 in caso di MAC uguale a 00:30:4f:5e:a1:60.

Il contenuto di tale file dovrà essere:

default linuxlabel linuxkernel vmlinuz-VERSIONE_KERNELappend initrd=initrd.img-VERSIONE_KERNEL nfsroot=192.168.2.1:/nodes/nfs/nodoX

Possiamo notare che nelle opzioni di avvio del kernel, specificate dopo la dicitura append, vengono

9

Page 10: Cluster beowulf

Costruzione di un cluster Beowulf

specificati l'initramfs da usare (in questo caso quello che è stato creato prima e linkato nella cartella /tftpboot) e la posizione del file system root da montare tramite nfs. Se si volesse far partire un nodo con un kernel differente, basterebbe specificare in questo file un altro kernel e un altro initramfs.

3.3. Installare e configurare i nodi client

Per installare il sistema operativo sul client, lo dotiamo temporaneamente di un hard disk, che verrà rimosso al termine.

Non è necessario utilizzare lo stesso sistema operativo sui vari nodi del cluster, ma noi per semplicità abbiamo scelto Debian Etch anche per i computer client. Questi però verranno dotati solo di un sistema minimale, in quanto il loro unico scopo sarà fornire potenza di calcolo.

3.3.1. Installare il sistema operativo

Bisogna installare il sistema operativo come se se si stesse installando su un normale PC, vediamo di seguito gli unici passi che richiedono una particolare attenzione.

3.3.2. Configurare le schede di rete

Ogni client ha due schede di rete. La prima, nel nostro caso eth0, serve per fare il boot da rete e per accedere al file system condiviso sul server. La seconda (eth1) invece serve all'interfaccia MPI per far comunicare i vari nodi durante l'esecuzione dei processi in parallelo. Naturalmente questa seconda scheda di rete supporta una velocità di un gigabit, visto che l'interfaccia di MPI richiede possibilmente una rete molto veloce.

Bisogna configurare eth0 in modo che gli venga assegnato dinamicamente l'indirizzo IP, mentre eth1 necessita di un indirizzo statico del tipo 192.168.3.X, dove X è il numero del nodo (maggiore o uguale a 2).

3.3.3. Terminare l'installazione

Al termine dell'installazione verrà chiesto se installare o no un boot loader. Bisogna rispondere di NO facendo terminare così l'installazione.Per finire bisogna staccare fisicamente il disco rigido dal computer e in qualche modo copiarne tutto il contenuto nella cartella sul server riservata al nodo, cioè /nodes/nfs/nodoX. Noi per comodità abbiamo utilizzato un adattatore EIDE-USB per collegare l'hard disk del client al server come se fosse un semplice disco esterno.Fatto questo basta terminare la configurazione del server (vedi 3.2.8), e i vari nodo del cluster sono pronti per avviarsi da rete

10

Page 11: Cluster beowulf

Costruzione di un cluster Beowulf

4. MPI – Message Passing InterfacePer MPI si intende un protocollo utilizzato in un sistema a memoria distribuita per la comunicazione tra i vari processi in esecuzione sui diversi nodi. Questo protocollo è chiuso, ma essendo pubbliche le specifiche negli ultimi anni sono nate molte implementazioni open source di MPI.

Le implementazioni open source di MPI più interessanti sono openMPI e Lam-MPI: tra queste due abbiamo scelto Lam-MPI (per brevità chiamato Lam) in quanto c'è molta più documentazione disponibile, anche se pare non essere la migliore soluzione in termini di prestazioni. Bisogna dire che openMPI è un progetto in forte crescita, che oltretutto è finanziato da importati centri accademici e industriali, e nel corso dell'ultimo anno il team di Lam è confluito quasi completamente in quello di openMPI. Ci aspettiamo quindi a breve che il problema della carente documentazione di openMPI venga risolto.

4.1. Introduzione a Lam-MPI

Di fatto Lam-MPI è composto da due parti:

● La prima consiste in una serie di comandi finalizzati alla gestione del cluster, come l'avvio dello stesso, l'esclusione dal cluster di alcuni nodi, il controllo del carico di lavoro, il lancio di processi paralleli e molto altro ancora.

● La seconda invece comprende alcune librerie aggiuntive per i vari linguaggi supportati, oltre ad appositi wrapper per i compilatori di questi linguaggi. Per wrapper si intende un “contenitore” che estende un oggetto già esistente; nel caso di MPI questi wrapper semplicemente aggiungono delle opzioni alla linea di comando dei compilatori originali, ad esempio -lmpi.

Come si può facilmente intuire, le librerie per i linguaggi di programmazione e le estensioni per i relativi compilatori si possono installare anche su un computer solo, solitamente il server, dove i programmi verranno compilati per poi dare il file eseguibile a tutti i nodi del cluster. La parte di Lam relativa alla gestione dell'esecuzione in parallelo dei processi invece andrò installata su tutti i nodi del cluster.

Il cuore di Lam è chiamato System Service Interface (SSI), ed è composto da vari moduli ognuno dei quali ha precisi compiti:

● boot: modulo che permette di far partire su tutti i nodi del cluster un demone Lam, il boot può avvenire in diversi modi;

11

Page 12: Cluster beowulf

Costruzione di un cluster Beowulf

● coll: modulo che si occupa delle comunicazioni collettive tra i vari processi;

● rpi: modulo che si occupa delle comunicazioni punto-punto tra i processi;

● cr: fornisce la possibilità di creare dei checkpoint da cui far ripartire il cluster in caso di blocchi.

4.2. Composizione della System Service Interface

Come già detto in precedenza, il nucleo di Lam si chiama SSI, e si occupa della gestione del cluster. Per fare questo SSI è diviso in quattro moduli che gestiscono altrettanti aspetti del cluster.

4.2.1. Modulo Boot

Permette, dal computer che funge da server, di avviare Lam su tutti i nodi client senza che l'utente debba accedere fisicamente a tutte le macchine. Per fare questo il modulo di boot utilizza una shell remota, che lancia i comandi necessari su tutti i nodi mentre l'utente rimane sul server.

Il modulo di boot può essere configurato in diversi modi, durante la compilazione di Lam oppure ad installazione già avvenuta. Noi abbiamo scelto di configurarlo durante la compilazione. Questa configurazione consiste nel dire a Lam qual'è la shell remota da utilizzare: noi abbiamo scelto la shell ssh, che, anche se non è tra quelle ufficialmente supportate, si può impostare scegliendo come shell rsh e specificando che il comando per utilizzare rsh è “ssh -X”.Per vedere la configurazione vera e propria del modulo di boot andare al capitolo riguardante l'installazione di Lam-MPI.

4.2.2. Modulo Coll

Il modulo Coll gestisce le comunicazioni multiple tra processi, come ad esempio i messaggi di broadcast, messaggi che vengono spediti da un processo a tutti gli altri. Esistono vari modi per gestire queste comunicazioni, e se questo modulo è lasciato non configurato è Lam stesso a decidere di volta in volta il modo migliore a seconda del tipo di comunicazione collettiva da fare.Noi abbiamo lasciato il modulo Coll non configurato, anche se nella documentazione di Lam è presente un vasto capitolo che la tratta (specificando che non dovrebbe essere necessaria).

4.2.3. Modulo Rpi

Questo è il modulo che gestisce le comunicazioni punto-punto tra i processi, è composto a sua volta da vari moduli i quali descrivono una modalità di comunicazione:

● tcp, i vari processi comunicano utilizzando socket TCP

● crtcp, come il TCP ma supporta funzionalità di checkpoint/restart;

● sysv, utilizza socket TCP per comunicare con i processi in esecuzione su altri nodi, mentre utilizza semafori in memoria condivisa per comunicare con processi in esecuzione sulla stessa macchina;

● usysv, simile al precedente, ma la gestione della memoria condivisa è diversa;

● lamd (lam daemon) le comunicazioni sono gestite dal demone Lam, è il metodo più lento, ma permette l'uso di messaggi asincroni;

● ib (infiniband) fatto apposta per le reti infiniband;

Si può specificare il modulo che si vuole utilizzare al momento di lanciare i programmi, vedremo

12

Page 13: Cluster beowulf

Costruzione di un cluster Beowulf

come nel capitolo riguardante l'uso di Lam.

4.3. Installare Lam-MPI

Su Debian Etch Lam-MPI va per forza compilato, visto che l'ultima versione non è disponibile per questa distribuzione, quindi bisogna procedere su tutti i nodi a preparare il sistema per poter installare da sorgenti. E' necessario quindi installare il pacchetto build-essential, e, visto che dopo sarà necessario, installiamo anche un server ssh (una shell remota) ed eventualmente il compilatore fortran77, che noi non abbiamo installato in quanto in seguito useremo il linguaggio C.

# apt-get install build-essential openssh-server

Per permettere ai client l'accesso a internet per l'installazione dei pacchetti bisogna eseguire sul server due semplici comandi

# iptables -t nat -A POSTROUTING -o eth -j MASQUERADE# sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"

dove per “eth” s'intende l'interfaccia di rete del server collegata al mondo esterno.

4.3.1. Compilare Lam-MPI

Per iniziare si deve scaricare dal sito www.lam-mpi.org la tarball contenente l'ultima versione, noi consigliamo di scaricare anche la documentazione, che anche se è in inglese è di facile comprensione.

Una volta scaricato il file .tar.gz va scompattato

$ tar xzvf lam-7.1.4.tar.gz

Si entra nella nuova cartella

$ cd lam-7.1.4

e si compila:

$ ./configure --without-fc --with-boot=rsh --with-rsh="ssh -x" (molti output)

$ make(molti output)

# make install(molti output)

Nel configure abbiamo istruito Lam a usare ssh come shell remota. La shell remota viene usata dal modulo boot di SSI (visto in precedenza) per avviare un demone Lam sui nodi client il cui scopo sarà rimanere in attesa di programmi da eseguire parallelamente.Il flag --without-fc specifica di non compilare la parte di Lam relativa al compilatore fortran.

4.3.2. Configurare SSH

Per eseguire i programmi parallelamente dobbiamo poter accedere via ssh alle altre macchine senza doverci autenticare. Per fare ciò abbiamo bisogno di configurare ssh per permettere il login senza password. Una volta terminata questa configurazione saremmo quindi in grado di eseguire comandi su una qualsiasi macchina del cluster da remoto senza inserire alcuna password, quindi Lam potrà svolgere la sua fase di boot correttamente.

Ecco qui di seguito i passi per configurare ssh dal server, le cartelle qui nominate che non esistono vanno create dall'utente:

13

Page 14: Cluster beowulf

Costruzione di un cluster Beowulf

$ cd ~/.ssh$ ssh-keygen -t rsa

ssh impiegherà un po' di tempo per generare le chiavi. Dopo questo ssh chiederà il nome da dare alle chiavi, lasciamo il nome proposto di default (id_rsa). Dopo questo, ssh chiede l'inserimento di una password, bisogna lasciarla vuota, dando semplicemente invio. A questo punto bisogna aggiungere la chiave pubblica appena creata (id_rsa.pub) alla lista delle chiavi il cui accesso è autorizzato sui vari client.

$ cat id_dsa.pub >> /nodes/nfs/nodoX/home/nomeutente/.ssh/authorized_keys

Per terminare la configurazione di ssh a questo punto basta connettersi via remoto dal server ai vari nodi con il comando

$ ssh [email protected]

e rispondere affermativamente alla domanda che compare a video. Per essere sicuri che Lam sarà in grado di utilizzare ssh, bisogna essere sicuri che lo script di avvio della shell non vadano a scrivere niente sullo standard error (su Debian Etch è già così). A seconda della distribuzione questi script si chiamano .login, .profile, .cshrc o .bashrc e si trovano solitamente nella cartella home dell'utente. Se contengono comandi che stampano qualcosa sullo standard error, essi vanno cancellati.

4.4. Uso di Lam-MPI

La gestione del cluster non deve essere fatta da root, quindi l'utente che lancia il boot di Lam e poi i programmi deve essere un normale utente.

Per comodità si suppone che il nome utente sia lo stesso su tutti i nodi del cluster, in caso contrario bisogna configurare Lam appositamente, ma è più comodo creare lo stesso utente su tutti i nodi.

4.4.1. Avvio del cluster

Bisogna creare un file contenete la lista di tutti i PC facenti parte del cluster, specificando eventualmente il numero di cpu per i nodi che ne hanno più di una. Questo file è fatto all'incirca in questo modo:

192.168.3.1 #server da cui viene lanciato il comando di boot192.168.3.2 #nodo con solo una CPU192.168.3.3 cpu=2 #nodo con un processore dual-core o con due CPU

Qui sono specificati tre computer di cui uno con due processori, nel nostro cluster gli indirizzi ip sono quelli della rete gigabit.

Il comando da lanciare per l'avvio del cluster è

$ lamboot -v hostfile

Il flag -v è facoltativo, serve solo per far visualizzare a video gli indirizzi dei nodi.In questo modo, grazie alla configurazione che abbiamo dato in fase di compilazione, il demone Lam viene lanciato tramite ssh su tutti i nodi del cluster; infatti dopo l'esecuzione di lamboot è visibile su tutti i nodi un processo chiamato lamd.

Naturalmente il comando lamboot ha molti parametri che permettono di configurare il boot in modi diversi da come specificato durante la compilazione di Lam-MPI.

4.4.2. Compilazione dei programmi

Lam-MPI mette a disposizione dei wrapper per i più comuni compilatori, essi si limitano a

14

Page 15: Cluster beowulf

Costruzione di un cluster Beowulf

richiamare i compilatori originali aggiungendo il supporto alle librerie MPI. Questi compilatori sono:

● mpicc, compilatore C;

● mpiCC compilatore C++;

● mpif77 compilatore fortran77.

Quindi per compilare un file sorgente scritto in C bisogna dare il comando:

$ mpicc -o nomeprogramma file_sorgente.c

Come risultato della compilazione si ha un programma pronto ad essere eseguito sul cluster.

4.4.3. Esecuzione dei programmi con mpirun

Per poter eseguire il programma è prima di tutto necessario che tutti i nodi del cluster abbiano a disposizione l'eseguibile, che deve essere nella stessa cartella su tutti i computer. Fatto questo siamo pronti a eseguire il programma, con il comando mpirun:

$ mpirun C -ssi rpi sysv ./nomeprogramma

Il flag C indica di eseguire una copia del programma per ogni CPU presente nel cluster, in alterativa a C si possono usare o il flag N, che lancia un programma per ogni nodo, oppure il flag -np=numero, dove si specifica il numero esatto di istanze del programma da eseguire. Con quest'ultima opzione si possono eseguire più processi su una stessa CPU, c'è da sottolineare però che la loro esecuzione sarà solo virtualmente parallela.

Come si può notare nel comando di esempio abbiamo specificato il modulo da usare nelle comunicazione punto-punto tra i processi. In questo caso è specificato il modulo sysv, ma nulla impedisce di usare lamd, tcp, crtcp o usysv: noi riteniamo però che il metodo usato da sysv per far comunicare i processi tra loro sia il migliore. Se avessimo voluto usare il metodo lamd la riga di comando sarebbe stata:

$ mpirun C -ssi rpi lamd ./nomeprogramma

Controllare la documentazione per ulteriori esempi e spiegazioni.

4.4.4. Esecuzione dei programmi con mpiexec

Mpiexec è un altro comando utilizzato per eseguire processi sul cluster, ma può gestire casi più complessi, come l'esecuzione di due processi contemporaneamente oppure l'esecuzione di un programma su un cluster composto da architetture miste. Con questo si intende che, visto che Lam si può installare su qualsiasi sistema operativo Unix, il cluster potrebbe contenere macchine con diverse architetture, che quindi non possono eseguire lo stesso codice macchina. Mpiexec permette di lanciare un determinato eseguibile per ogni architettura presente nel cluster, con ad esempio il comando

$ mpiexec -arch solaris programma.solaris : -arch linux programma.linux

l'esempio riportato sopra esegue sui nodi con architettura solaris il programma compilato per solaris, e si comporta allo stesso modo per i nodi con architettura linux.

Anche in questo caso si consiglia di controllare la documentazione di mpiexec per ulteriori dettagli.

15

Page 16: Cluster beowulf

Costruzione di un cluster Beowulf

4.4.5. Altri comandi utili● $ lamwipe -v hostfile

spegne il demone lam sui nodi del cluster specificati nel file hostfile

● $ lamhalt

termina il demone lam solo sul nodo dove viene eseguito

● $ mpitask

visualizza i processi in esecuzione sul cluster

● $ mpimsg

monitora i buffer usati da MPI per la trasmissione dei messaggi

● $ lamgrow

aggiunge un nodo al cluster

● $ lamnodes

visualizza l'elenco dei nodi

● $ laminfo

visualizza le informazioni sulla configurazione di Lam-MPI

Questi sono i principali comandi utilizzati per la gestione di un cluster per HPC che usa Lam come interfaccia MPI. Per ulteriori informazioni si consiglia la letture della documentazione di Lam-MPI presente sul sito www.lam-mpi.org.

16

Page 17: Cluster beowulf

Costruzione di un cluster Beowulf

5. Programmare con le librerie MPILe librerie MPI consistono in un set di istruzioni che permettono la comunicazione tra i vari processi. Per creare un programma parallelo bisogna quindi includere nel programma una o più librerie speciali, tra le quali c'è sempre la libreria MPI, che in C si chiama mpi.h.

5.1. Struttura di un programma parallelizzabile

In linguaggio C un programma in grado di essere eseguito sul cluster è sempre composto in questo modo:

/* simple send and receive */#include <stdio.h>#include <mpi.h>

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

/* dichiarazioni variabili (comuni a tutti i processi) */

/* inizializzazione dell'interfaccia MPI */ MPI_Init(&argc, &argv);

/* Istruzioni da eseguire parallelamente */ /* indica all'interfaccia MPI che il programma parallelo è terminato */ MPI_Finalize(); return(0);}

Come si può è stata inclusa la libreria mpi.h, la quale fornisce le funzioni necessarie al programma per gestire l'esecuzione parallela del processo sui vari nodi. Il programma parallelo inizia sempre con l'istruzione MPI_Init, a cui vengono passati per referenza gli argomenti passati al main, e termina sempre con l'istruzione MPI_Finalize. Vedremo in seguito che anche tutte le altre funzioni contenute in mpi.h avranno una nomenclatura del tipo MPI_Nomefunzione( ).

5.2. Tipi speciali di variabili

La libreria mpi.h aggiunge dei tipi speciali di variabili necessarie alle varie funzioni, di seguito sono elencati questi principali tipi ed eventuali costanti che ne facilitano l'uso.

L'oggetto communicator

Esiste il tipo MPI_Comm, detto communicator, che indica un gruppo di processi. Ad esempio si potrebbero raggruppare in un oggetto communicator i processi in esecuzione sui nodi pari, mentre in un altro quelli in esecuzione sui nodi dispari.

Quasi sempre è necessario passare alle funzioni MPI un oggetto communicator, in quanto le funzioni devono sapere su quali processi devono lavorare.

Costanti:

● MPI_COMM_WORLD è una costante di tipo MPI_Comm che indica il gruppo di tutti i processi.

17

Page 18: Cluster beowulf

Costruzione di un cluster Beowulf

● MPI_COMM_NULL, indica un gruppo vuoto, cioè di cui non fa parte nessun processo.

La struttura MPI_Status

Questo tipo di dato, chiamato MPI_Status, è una struttura che può contenere informazioni sullo stato di una comunicazione. Ad esempio può contenere informazioni su quale processo ci ha spedito un certo messaggio, ma questo verrà spiegato più dettagliatamente al momento di spiegare la funzione MPI_Recv.

Il tipo MPI_Operation

Questo tipo di dato serve per indicare ad alcune funzioni MPI quale operazione va svolta sui dati che devono elaborare. Il tipo di operazione solitamente è espresso sotto forma di una costante, che appunto è una costante di tipo MPI_Operation.

Ogni costante di tipo MPI_Operation elencata qui di seguito indica una diversa operazione che si può indicare di svolgere a particolari funzioni di MPI che lavorano su gruppi di dati:

● MPI_SUM esegue la somma;

● MPI_PROD esegue il prodotto;

● MPI_MAX cerca il valore massimo;

● MPI_MIN cerca il valore minimo;

● MPI_LAND logical AND;

● MPI_BAND esegue la AND bit a bit;

● MPI_LOR logical OR;

● MPI_BOR esegue la OR bit a bit;

● MPI_LXOR logical XOR ;

● MPI_BXOR esegue la XOR bit a bit;

● MPI_MAXLOC esegue la ricerca del valore massimo, in più ritorna anche un indice che indica il processo su cui di trova il valore trovato;

● MPI_MINLOC esegue la ricerca del valore minimo, in più ritorna anche un indice che indica il processo su cui di trova il valore trovato;

● MPI_OP_NULL indica un'operazione nulla;

Il tipo MPI_Datatype

Questo ultimo tipo di dato speciale serve per indicare alle funzioni MPI qual'è il tipo di dato che stanno elaborando. Anche qui si usano delle costanti per comodità, in particolare le costanti di tipo MPI_Datatype sono:

● MPI_BYTE = byte;

● MPI_CHAR = char;

● MPI_SHORT = short;

● MPI_INT = int;

18

Page 19: Cluster beowulf

Costruzione di un cluster Beowulf

● MPI_UNSIGNED = unsigned int ;

● MPI_LONG = long int;

● MPI_FLOAT = float ;

● MPI_DOUBLE = double ;

● MPI_LONG_DOUBLE = long double;

5.3. Funzioni principali

Analizziamo qui le principali funzioni che permettono la comunicazione tra i processi e l'elaborazione dei dati.

5.3.1. Rank & Size

Per rank di un processo si intende il suo numero identificativo all'interno di un gruppo di processi, mentre per size si intende il numero di processi che fanno parte di un gruppo.

Come ottenere il rank

Per ottenere il rank di un processo si utilizza la funzione MPI_Comm_rank, a cui viene passata per referenza una variabile intera dove memorizzare il numero del processo:

MPI_Comm_rank(MPI_Comm comm, int *myrank)

Se come oggetto communicator si utilizza la costante MPI_COMM_WORLD, si ottiene il rank globale del processo, che in caso di un processo in esecuzione per ogni nodo corrisponde al numero del nodo.

Esempio:

MPI_Comm_rank(MPI_COMM_WORLD, *myrank); printf(“questo processo è il numero %d”,myrank);

Come ottenere il size (numero di processi)

L'istruzione che permette di ottenere il numero di processi che fanno parte di un certo gruppo funziona in modo analogo a quella utilizzata per ottenere il rank, ed è la seguente:

MPI_Comm_size(MPI_Comm comm, int *size)

Quindi, come per il rank, se si utilizza come oggetto communicaror la costante MPI_COMM_WORLD, si ottiene il numero totale dei processi

Esempio:

MPI_Comm_size(MPI_COMM_WORLD, *size); printf(“In totale ci sono %d processi in esecuzione”,size);

5.3.2. Comunicazioni punto-punto

Per comunicazione punto-punto si intende la spedizione di un insieme di dati omogenei (vettore) da un processo ad un altro.

Ecco le i prototipi della principali funzioni in C che permettono le comunicazioni punto-punto:

MPI_Sendint MPI_Send(void *buf, int count, MPI_Datatype dtype, int dest, int tag,

19

Page 20: Cluster beowulf

Costruzione di un cluster Beowulf

MPI_Comm comm);

Questa funzione permette di spedire un vettore di dati da un processo ad un altro, non termina fino a che il dato non è stato ricevuto dal destinatario.I parametri hanno il seguente significato:

● *buf = indirizzo della prima cella del vettore da trasmettere;● count = lunghezza del vettore (>0);● dtype = tipo di dato da spedire, indicato con una costante di tipo MPI_Datatype;● dest = indica il rank del processo a cui spedire il messaggio;● tag = un qualsiasi numero che si vuole assegnare al messaggio;● comm = oggetto communicator che indica all'interno di quale gruppo di processi

cercare il rank destinatario.

MPI_Recvint MPI_Recv(void *buf, int count, MPI_Datatype dtype, int mitt, int tag,

MPI_Comm comm, MPI_Status *status);

Questa funzione permette di ricevere un vettore di dati da un processo che lo ha spedito, la funzione non termina fino a quando i dati non sono stati ricevuti.I parametri hanno il seguente significato:

● *buf = indirizzo della prima cella del vettore dove memorizzare i dati ricevuti;● count = lunghezza del vettore (>0);● dtype = tipo di dato da ricevere, indicato con una costante di tipo MPI_Datatype;● mitt = indica il rank del processo da cui si attende il messaggio, si può indicare la

costante MPI_ANY_SOURCE che significa “da qualsiasi processo”;● tag = indica il tag che deve avere il messaggio che si riceve, si può indicare

MPI_ANY_TAG che significa “con qualsiasi tag”;● comm = oggetto communicator che indica all'interno di quale gruppo di processi

cercare il rank del mittente;● *status struttura che contiene informazioni sul messaggio ricevuto, come il rank

del mittente (status.source) e il tag (status.tag);

Comunicazioni punto-punto asincrone

Esistono delle varianti delle funzioni MPI_Send e MPI_Recv che non rimangono in attesa della ricezione del dato per terminare, ma che terminano subito, permettendo una comunicazione “asincrona” tra i processi. Naturalmente queste istruzioni andranno usate con molta cautela, tenendo conto che per poter funzionare correttamente bisogna impostare ssi per usare il modulo rpi lamd (vedi sezioni 4.2.3 e 4.4.3).

Queste funzioni utilizzano un tipo di data MPI_Request, che serve per identificare la sessione di trasmissione dei dati, in modo da controllare successivamente se i dati sono stati spediti o no.

Le funzioni invia e ricevi asincrone sono:

int MPI_Isend(void *buf, int count, MPI_Datatype dtype, int dest, int tag, MPI_Comm comm, MPI_Request *request);

int MPI_Irecv(void *buf, int count, MPI_Datatype dtype, int mitt, int tag, MPI_Comm comm, MPI_Request *request);

Per controllare l'avvenuta spedizione o ricezione del dato si possono utilizzare due funzioni, la wait o la test. La funzione wait non termina finchè il messaggio non è stato spedito o ricevuto, mentre la test controlla semplicemente se l'operazione è avvenuta, senza interrompere l'esecuzione del programma.

20

Page 21: Cluster beowulf

Costruzione di un cluster Beowulf

int MPI_Wait( MPI_Request *request, MPI_Status *status );int MPI_Test( MPI_Request *request, int *flag, MPI_Status *status );

Nella funzione di test il flag è 1 se l'operazione è stata completata con successo, mentre è 0 se deve ancora in attesa di essere completata.

Esempio comunicazione punto-punto

Questo programma carica un vettore di interi nel processo 0 e lo invia al processo 1, che stampa a video il vettore.

#include <stdio.h>#include <mpi.h>

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

int myrank,i; long aus; MPI_Status status; double a[10],b[10];

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myrank);

if( myrank == 0 ) /* parte eseguita solo dal processo 0 */ {

for (i=0;i<10;++i)a[i]=(double)i/3.5; /* carica il vettore a */

/* Invia il vettore A, lungo 10, al processo 1, come tag scelgo 10 MPI_Send( a, 10, MPI_DOUBLE, 1, 10, MPI_COMM_WORLD );

} else {

if( myrank == 1 ) /* Parte eseguita solo dal processo 1 */ {

/* il processo 1 rimane in attesa del messaggio dal processo 0 con tag=10*/ MPI_Recv(b, 10, MPI_DOUBLE, 0, 10, MPI_COMM_WORLD, &status );

/* Stampa a video il vettore ricevuto */ for(i=0;i<10;i++) {

printf("%d: %f\n",i,b[i]); }

} }

MPI_Finalize(); /* Fine del programma parallelo */ return(0);}

21

Page 22: Cluster beowulf

Costruzione di un cluster Beowulf

5.3.3. Comunicazioni collettive

Per comunicazioni collettive si intendono quelle funzioni che interessano un gruppo di processi. Una comunicazione collettiva può essere usata per sincronizzare i processi, inviare lo stesso messaggio a tutti i processi, suddividere un vettore tra i vari processi, raccogliere il risultato di un'elaborazione tra tutti i processi per ottenere un unico risultato.

Sincronizzare i processi

Per sincronizzare un gruppo di processi su una stessa linea di codice, per farli ripartire assieme alla successiva istruzione, si utilizza la funzione MPI_Barrier:

int MPI_Barrier(MPI_Comm comm)

I processi che chiamano questa funzione vengono interrotti fino a quando tutti i processi che fanno parte del gruppo comm non chiamano questa funzione

Esempio:

..... /* qualsiasi istruzione */MPI_Barrier(MPI_COMM_WORLD); /* sincronizza tutti i processi qui */..... /* tutti i processi partono assieme dopo la barrier */

Messaggi di broadcast

Per messaggio di broadcast si intende l'invio di un vettore di dati ad un intero gruppo di processi. La funzione è molto semplice:int MPI_Bcast ( void * buffer, int count, MPI_Datatype tipo_di_dato,

int rank, MPI_Comm comm );

Il significato dei vari parametri è il seguente:● *buffer = vettore che nel mittente contiene il vettore da trasmettere, nei riceventi

indica dove memorizzare i dati ricevuti;● count = lunghezza del vettore;● tipo_di_dato = tipo di dato contenuto nel vettore;● rank = rank del processo mittente;● comm = gruppo di processi a cui spedire i dati;

22

Immagine tratta da http://ci-tutor.ncsa.uiuc.edu

Page 23: Cluster beowulf

Costruzione di un cluster Beowulf

Operazione Reduce

Questa funzione è utilizzata per raccogliere un dato dai processi coinvolti, elaborarlo ulteriormente e restituire in uscita un unico dato.

int MPI_Reduce ( void*send_buffer, void*recv_buffer, int count, MPI_Datatype tipo_di_dato, MPI_Operation operazione, int rank, Mpi_Comm comm )

Spiegazione dei parametri:● *send_buffer = indirizzo del dato da raccogliere da ogni processo● *recv_buffer = indirizzo di dove memorizzare il risultato ottenuto

dall'elaborazione di tutti i dati● count = lunghezza dei vettori da cui si prendono i dati;● tipo_di_dato = tipo di dato da elaborare● operazione = operazione da svolgere sull'insieme dei dati, indicata con una

costante di tipo MPI_Operation;● rank = rank del processo dove memorizzare il risultato;● comm = gruppo di processi coinvolti;

Operazioni Gather e Scatter

Le operazioni scatter e gatter eseguono una il compito inverso dell'altra, la prima suddivide un vettore in parti uguali e ne assegna ciascuna parte a un processo. La seconda invece riunisce un vettore, replicato con diversi contenuti su tutti i processi, ottenendo un unico vettore più lungo memorizzato su un processo.

23

Immagine tratta da http://ci-tutor.ncsa.uiuc.edu/index.php

Immagine tratta da http://ci-tutor.ncsa.uiuc.edu/index.php

Page 24: Cluster beowulf

Costruzione di un cluster Beowulf

Disegno 1: Operazione Scatter

Disegno 2: Operazione Gather

Scatter

Un processo invia a tutti gli altri (anche a se stesso) un frammento di un vettore.

int MPI_Scatter ( void* send_buffer, int send_count, MPI_datatype send_type, void* recv_buffer, int recv_count, MPI_Datatype recv_type, int rank,MPI_Comm comm )

Vediamo il significato dei parametri:● *send_buffer = indirizzo della prima cella del vettore da dividere;● send_count = numero di elementi da inviare ad ogni processo (non il numero

totale);● send_type = tipo di dati nel buffer da dividere;● *recv_buffer = indirizzo dove ogni processo si salva il suo frammento del vettore

iniziale;● recv_count = numero di elementi ricevuti da ogni processo (è uguale a

send_count se anche recv_type è uguale a send_type;● recv_type = tipo di dati del buffer di ricezione;● rank = rank del processo che ha in memoria il vettore da suddividere;● comm = oggetto communicator;

Gather

Tutti i processi inviano un vettore lungo uguale ad un processo particolare, che li riunisce in un vettore unico.

int MPI_Gather ( void* send_buffer, int send_count, MPI_datatype send_type, void* recv_buffer, int recv_count, MPI_Datatype recv_type, int recv_rank,MPI_Comm comm )

Vediamo il significato dei parametri:● *send_buffer = indirizzo della prima cella del vettore da inviare;● send_count = numero di elementi che ogni processo invia;● send_type = tipo di dati nel buffer di invio;● *recv_buffer = indirizzo dove il processo ricevente memorizza i dati ricevuti;● recv_count = numero di elementi ricevuti in totale dal processo destinatario;● recv_type = tipo di dati del buffer di ricezione;● recv_rank = rank del processo che riceve tutti i frammenti per unirli;● comm = oggetto communicator;

5.3.4. Conclusioni

A questo punto abbiamo elencato le funzioni base che le librerie MPI rendono disponibili per il

24

Page 25: Cluster beowulf

Costruzione di un cluster Beowulf

linguaggio C. In questa nostra guida mancano tante funzioni, come quelle per lavorare sui file, suddividere matrici di dati, definire gruppi di processi e molte altre ancora; crediamo comunque che sia meglio non entrare nei dettagli.

5.3.5. Programma di esempio – Calcolo integrale

Vediamo di seguito il programma che abbiamo realizzato con l'uso delle funzioni spiegate sopra (tranne gather e scatter). Questo programma calcola l'integrale definito di una funzione polinomiale utilizzando con il metodo dei trapezi. Il lavoro viene suddiviso tra i vari processi, dividendo l'intervallo di integrazione in intervalli più piccoli da assegnare ai vari processi.

Ecco il sorgente del programma:

#include <mpi.h>#include <stdio.h>#include <time.h>

double funzione(double,double,double,double,double);

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

int rank, size;

double a0,a1,a2,a3,sin,des,ris,somma,i,s,d,step;/* a0,a1,a2,a3 = coefficienti della funzione polinomialesin,des = limiti sinistro e destro dell'integralesomma = dove alla fine viene memorizata la somme degli integrali

calcolata da tutti i processis,d = limiti sinistro e destro in cui ogni processo calcola

l'integralestep = passo usato dal metodo dei trapezi, piu' e' piccolo piu' la

precisione e' elevata */

/* Inizio del programma parallelo */MPI_Init(&argc,&argv);

/* Ottiene il rank del processo */MPI_Comm_rank(MPI_COMM_WORLD, &rank);

/*Ottiene il numero di processi*/MPI_Comm_size(MPI_COMM_WORLD, &size);

/* Il processo 0 prende in input i dati*/if(rank==0){

printf("\n0: Scrivi 7 numeri: a0 a1 a2 a3 sinistro destro step\n");

/* prende in input i coefficienti */scanf("%lf %lf %lf %lf %lf %lf

%lf",&a0,&a1,&a2,&a3,&sin,&des,&step);printf("\nI sei numeri sono: a0=%0.2lf, a1=%0.2lf, a2=%0.2lf,

a3=0.2%lf, sin=%0.2lf, des=%0.2lf, step=%0.2lf\n",a0,a1,a2,a3,sin,des,step);}

/* Tutti i processi vengono sincronizzati su questo punto */

25

Page 26: Cluster beowulf

Costruzione di un cluster Beowulf

MPI_Barrier(MPI_COMM_WORLD);

/* prende i dati processo 0 e li copia su tutti gli altri processi */MPI_Bcast(&a0,1,MPI_DOUBLE,0,MPI_COMM_WORLD);MPI_Bcast(&a1,1,MPI_DOUBLE,0,MPI_COMM_WORLD);MPI_Bcast(&a2,1,MPI_DOUBLE,0,MPI_COMM_WORLD);MPI_Bcast(&a3,1,MPI_DOUBLE,0,MPI_COMM_WORLD);MPI_Bcast(&sin,1,MPI_DOUBLE,0,MPI_COMM_WORLD);MPI_Bcast(&des,1,MPI_DOUBLE,0,MPI_COMM_WORLD);MPI_Bcast(&step,1,MPI_DOUBLE,0,MPI_COMM_WORLD);

/* Si inizializza ris a zero */ris=0;s=sin+(((des-sin)/(double)size)*(double)rank);d=sin+(((des-sin)/(double)size)*(double)rank)+(des-sin)/(double)size;

/* ogni processo stampa a video su quale intervallo calcolal'integrale */printf("\n%d: Calcolo l'integrale tra sin=%lf e des=%lf\n",rank,s,d);

/* con somme successive, si ottiene dentro ris l'integrale nell'intervallo assegnato al processo */for(i=s;i<=d-step;i+=step){

s1=funzione(a0,a1,a2,a3,i);s2=funzione(a0,a1,a2,a3,i+step);ris+=(s1+s2)*(step/2);

}

/* ogni processo stampa a video che integrale ha calcolato */printf("\n%d: L'integrale tra %lf e %lf è %lf\n",rank,s,d,ris);

/* aspetta che tutti i processi abbiano finito sincronizzandoli qui */MPI_Barrier(MPI_COMM_WORLD);

/* prende il risultato di ogni processo e ne memorizza la somma sulprocesso 0*/MPI_Reduce(&ris,&somma,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);

/* Il processo 0 stampa a video il risultato */if(rank==0){

printf("\n%d: La somma dei risultati è %lf\n",rank,somma);}

MPI_Finalize; /* FINE DEL PROGRAMMA */return(0);

}

/* calcola il valore che assume la funzione nel punto x, dati ivalori dei coefficienti e il valore di x*/double funzione(double a0,double a1,double a2,double a3,double x){

double ret;ret=a0+(a1*x)+(a2*x*x)+(a3*x*x*x);return(ret);

26

Page 27: Cluster beowulf

Costruzione di un cluster Beowulf

}

Bisogna specificare che l'input (da tastiera) e l'output (a video) possono avvenire solo sul nodo da cui viene lanciato il processo parallelo. Quindi se un processo in esecuzione su uno dei nodi client esegue una printf, essa sarà visibile sullo schermo del nodo principale.

27