UNIVERSITA’ CA’ FOSCARI – VENEZIA Facoltà di Scienze Matematiche, Fisiche e Naturali Corso di Laurea in Informatica Progetto di Calcolo Parallelo Prof. S. Orlando Algoritmi paralleli per la moltiplicazione di matrici Anno Accademico 2006-2007 Franzin Alberto Savian Dario Marchetto Giovanni Mat. 807980 Mat. 808548 Mat. 810134 INTRODUZIONE............................................................................................................................................................. 4 IMPLEMENTAZIONE ................................................................................................................................................... 5 MATRIX-MATRIX MULTIPLICATION ............................................................................................................................... 5 Funzionamento.......................................................................................................................................................... 5 [master] Lettura e partizionamento matrici A e B .............................................................................................. 6 [master] Spedizione sottoblocchi ai processi....................................................................................................... 7 [master e slave] Ricezione dei sottoblocchi .......................................................................................................... 8 [master e slave] Scambio blocchi mancanti tra processi ...................................................................................... 9 [master e slave] Moltiplicazione sottoblocchi (calcolo sottoblocco della matrice C) ........................................ 10 [master e slave] Spedizione blocco calcolato al master...................................................................................... 12 [master] Raccolta di tutti i blocchi e creazione della matrice C ....................................................................... 13 CANNON ALGORITHM ................................................................................................................................................... 14 Funzionamento........................................................................................................................................................ 14 [master] Lettura e partizionamento matrici A e B ............................................................................................ 17 [master] Allineamento delle matrici A e B......................................................................................................... 17 [master] Spedizione sottoblocchi ai processi..................................................................................................... 19 [master e slave] Ricezione dei sottoblocchi ........................................................................................................ 20 [master e slave] Rotazione sottoblocchi e calcolo blocco della matrice C ......................................................... 20 [master e slave] Spedizione blocco calcolato al master...................................................................................... 25 [master] Raccolta di tutti i blocchi e creazione della matrice C ....................................................................... 26 ALGORITMI SEQUENZIALI PER LA MOLTIPLICAZIONE DELLE MATRICI............................................. 34 ALGORITMO NORMALMENTE UTILIZZATO ..................................................................................................................... 34 Algoritmo di Strassen .............................................................................................................................................. 34 TEST PROGETTO ........................................................................................................................................................ 36 PRINCIPALI GRAFICI REALIZZATI CON RELATIVI COMMENTI.......................................................................................... 36 COMMENTO GRAFICI NUMERO PROCESSI VS. TEMPO .................................................................................................... 37 Matrice 384 ............................................................................................................................................................. 37 . Matrice 1920 ......................................................................................................................................................... 38 COMMENTO GRAFICI RITARDO VS. TEMPO................................................................................................................... 41 Numero processi 4................................................................................................................................................... 41 Numero processi 16................................................................................................................................................. 43 GRAFICI DIMENSIONE MATRICE – TEMPO ...................................................................................................................... 43 Funzione di rallentamento disattivata..................................................................................................................... 43 Funzione di rallentamento attivata ......................................................................................................................... 45 GRAFICI DIMENSIONE NUMERO PROCESSORI – SPEEDUP................................................................................................ 46 Matrice 384 ............................................................................................................................................................. 46 Matrice 1152 ........................................................................................................................................................... 48 MATRICE 1920.............................................................................................................................................................. 49 GRAFICI DIMENSIONE NUMERO PROCESSORI – EFFICIENZA ........................................................................................... 51 Matrice 384 ............................................................................................................................................................. 52 Matrice 1152 ........................................................................................................................................................... 53 MATRICE 1190.............................................................................................................................................................. 54 VALUTAZIONE BILANCIAMENTO ................................................................................................................................... 57 Matrice 384 ............................................................................................................................................................. 57 Matrice 1920 ........................................................................................................................................................... 60 CARATTERISTICHE DEL SISTEMA ....................................................................................................................... 64 ELENCO FILE PROGETTO ............................................................................................................................................... 64 RAPPRESENTAZIONE MATRICE ...................................................................................................................................... 66 FUNZIONI DI UTILITÀ .................................................................................................................................................... 66 IMPLEMENTAZIONE DELLA FUNZIONE F DI RALLENTAMENTO ....................................................................................... 68 MANUALE D’USO ........................................................................................................................................................ 69 COMPILAZIONE ATTRAVERSO MAKEFILE ...................................................................................................................... 69 MODIFICA HEADER DEI FILE .......................................................................................................................................... 69 UTILIZZO ...................................................................................................................................................................... 70 DOCUMENTAZIONE DELLE MISURE DELLE PRESTAZIONI......................................................................... 71 2 PRIMITIVE MPI............................................................................................................................................................ 73 PRIMITIVE GESTIONE AMBIENTE MPI ........................................................................................................................... 73 MPI_Init( ) .............................................................................................................................................................. 73 MPI_Comm_size ..................................................................................................................................................... 73 MPI_Comm_rank.................................................................................................................................................... 73 MPI_Abort .............................................................................................................................................................. 73 MPI_Get_Processor_Name .................................................................................................................................... 73 MPI_Wtime ............................................................................................................................................................. 74 MPI_Finalize .......................................................................................................................................................... 74 PRIMITIVE COMUNICAZIONE POINT TO POINT ............................................................................................................... 74 MPI_Send( ) ............................................................................................................................................................ 74 MPI_Recv( ) ............................................................................................................................................................ 74 MPI_Sendrecv_replace ........................................................................................................................................... 75 COLLETTIVE DI COMUNICAZIONE.................................................................................................................................. 75 MPI_Barrier............................................................................................................................................................ 75 MPI_Bcast............................................................................................................................................................... 75 MPI_Scatter ............................................................................................................................................................ 76 MPI_Gather ............................................................................................................................................................ 76 MPI_Allgather ........................................................................................................................................................ 76 PRIMITIVE PER LA GESTIONE DEI COMUNICATORI ......................................................................................................... 77 MPI_Comm_Split.................................................................................................................................................... 77 TIPI PREDEFINITI IN MPI............................................................................................................................................... 77 RISULTATI TEST ......................................................................................................................................................... 78 ALGORITMO MATRIX MATRIX ...................................................................................................................................... 78 ALGORITMO CANNON ................................................................................................................................................... 79 ALGORITMO PROCESSOR FARM .................................................................................................................................... 81 ALGORITMO SEQUENZIALE ........................................................................................................................................... 82 CONCLUSIONE ............................................................................................................................................................ 83 APPENDICE ................................................................................................................................................................... 84 GRAFICI NUMERO PROCESSORI – TEMPO ....................................................................................................................... 84 Matrice 384 ............................................................................................................................................................. 84 Matrice 768 ............................................................................................................................................................. 86 Matrice 1152 ........................................................................................................................................................... 88 Matrice 1920 ........................................................................................................................................................... 90 GRAFICI RITARDO – TEMPO ........................................................................................................................................... 91 Matrice 384 ............................................................................................................................................................. 91 Matrice 768 ............................................................................................................................................................. 93 Matrice 1152 ........................................................................................................................................................... 94 Matrice 1920 ........................................................................................................................................................... 95 GRAFICI DIMENSIONE MATRICE – TEMPO ...................................................................................................................... 97 Funzione di rallentamento disattivata..................................................................................................................... 97 Funzione di rallentamento attivata : 5 cicli ............................................................................................................ 98 Funzione di rallentamento attivata : 10 cicli .......................................................................................................... 99 Funzione di rallentamento attivata : 40 cicli ........................................................................................................ 100 GRAFICI NUMERO PROCESSI – SPEEDUP....................................................................................................................... 101 Matrice 384 ........................................................................................................................................................... 101 Matrice 768 ........................................................................................................................................................... 103 Matrice 1152 ......................................................................................................................................................... 104 Matrice 1920 ......................................................................................................................................................... 105 GRAFICI NUMERO PROCESSORI – EFFICIENZA .............................................................................................................. 107 Matrice 384 ........................................................................................................................................................... 107 Matrice 768 ........................................................................................................................................................... 109 Matrice 1152 ......................................................................................................................................................... 110 Matrice 1920 ......................................................................................................................................................... 112 GRAFICI BILANCIAMENTO DEL CARICO ....................................................................................................................... 114 Matrice 384 ........................................................................................................................................................... 114 Matrice 1920 ......................................................................................................................................................... 115 3 Introduzione Il progetto consiste nell’implementazione di tre algoritmi paralleli per la moltiplicazione delle matrici. Gli algoritmi implementati differiscono in base al processo di parallelizzazione realizzato: Matrix-Matrix Multiplication: assegnazione statica del job ai processi; sono necessari meccanismi di comunicazione per fornire il sottoblocco posseduto a tutti i processi che hanno un blocco che è nella stessa riga o colonna di quello posseduto. Cannon Algorithm: assegnazione statica del job ai processi; prima di applicare l’algoritmo bisogna allineare le matrici. Lo scambio dei sottoblocchi tra processi avviene tramite una rotazione dei sottoblocchi posseduti dai processi. Processor Farm: mappatura dinamica dei job ai processi; l’assegnazione dei blocchi è eseguita dal master e non si ha nessuna iterazione tra i worker. Il progetto è stato implementato tramite lo standard MPI (Massage Passing Interface). MPI è lo standard de facto per la comunicazione tra processi in ambiente distribuito tra nodi appartenenti ad un cluster, che eseguono il programma parallelo secondo il modello SIMD (Single Istruction Multiple Data). Tale libreria ha il vantaggio di essere portabile poiché esistono implementazioni per diverse architetture e veloce, infatti è ottimizzata in base all’architettura presente. L’implementazione più diffusa di MPI, che è quella che è stata adotta nel progetto, è MPICH: opensource, testata in svariate piattaforme (incluse Linux (on IA32 and x86-64), Mac OS/X (PowerPC and Intel), Solaris (32- and 64-bit), e Windows). Requisiti del progetto: Le matrici di input devono essere accedute solamente dal processo 0 Le matrici sono di tipo FP e sono memorizzate nel formato row-major come ASCII file nel disco Nell’algoritmo 1 e 2, i blocchi devono essere trasmessi dal processo 0 che ha il compito di recuperare i blocchi calcolati dai worker Nell’algoritmo 3, il master è responsabile della distribuzione dei dati a tutti gli slave La matrice di output deve essere “ricomposta” dal master che provvederà poi a salvarla sul disco Deve essere possibile disabilitare l’input dei dati dal/al disco Obiettivi: Valutare l’eterogeneità e lo sbilanciamento del cluster Valutare lo speedup con differenti granularità e differenti dimensioni del problema Siccome la moltiplicazione delle matrici non è sufficiente per ottenere un vantaggio nell’utilizzo degli algoritmi paralleli, computare C = f(A) × B invece che C = A × B. La funzione f deve essere indipendentemente applicata a tutti gli elementi A[i,j] per permettere l’incremento della granularità minima dei task. 4 Implementazione Viene ora spiegato il funzionamento degli algoritmi e l’implementazione che è stata realizzata. Matrix-Matrix Multiplication Funzionamento Consideriamo la moltiplicazione di due matrici quadrate di dimensione n × n : C = A × B. Il primo passo eseguito dall’algoritmo è quello di partizionare le matrici A e B in p sottoblocchi, dove p è il numero dei processi che eseguono l’algoritmo. Ogni sottoblocco Ai,j della matrice A e Bi,j della matrice B (0≤i, j<p) ha dimensione (n/ p ) × (n/ p ) ciascuno: tale blocco è assegnato dal master al processo Pi,j. A questo punto, il processo Pi,j per essere in grado di calcolare il blocco Ci,j della matrice C, deve perciò possedere tutti i sottoblocchi Ai,k e Bk,j (0≤k< p ), cioè tutti i sottoblocchi che appartengono alla stessa riga del sottoblocco posseduto della matrice A e di tutti i sottoblocchi che appartengono alla stessa colonna del sottoblocco posseduto della matrice B. Per fare ciò, i processi si scambiano i sottoblocchi mancanti per poter eseguire la moltiplicazione: I sottoblocchi mancanti della riga per la matrice A I sottoblocchi mancanti della colonna per la matrice B Ogni processo a questo punto possiede tutti gli elementi necessari per il calcolo del blocco Cij di competenza al processo. Terminata la computazione del sottoblocco, ogni processo Pi,j invia tale blocco al processo master che ha il compito di assemblare i blocchi ricevuti ed ottenere la matrice C. In base alla dimensione delle matrici A e B, poiché i sottoblocchi devono essere quadrati, non è possibile eseguire l’algoritmo con un numero casuale di processi. Per chiarire tale limitazione, viene fornito un esempio con una matrice di dimensione 48. Il numero dei processi che sono congrui con tale dimensione sono: 1 processo 4 processi 9 processi 16 processi …… 1 sottoblocco di dimensione 48 4 sottoblocchi di dimensione 24 9 sottoblocchi di dimensione 16 16 sottoblocchi di dimensione 12 5 Riassumendo: [master] [master] [master] [master e slave] [master e slave] [master e slave] [master e slave] [master] Lettura matrice A e B Partizionamento delle matrici A e B Spedizione sottoblocchi ai processi Ricezione dei sottoblocchi Scambio blocchi mancanti tra processi Moltiplicazione sottoblocchi (calcolo sottoblocco della matrice C) Spedizione blocco calcolato al master Raccolta di tutti i blocchi e creazione della matrice C Graficamente, le operazioni eseguite da un certo processo sono: [master] Lettura e partizionamento matrici A e B Il master è l’unico che può accedere alle matrici A e B. La lettura di tali matrici avviene da disco se è settata la variabile FROM_FILE altrimenti vengono generate automaticamente della dimensione specificata in DIM. Viene controllato che se sia la matrice A che la matrice B possano essere suddivise in sottoblocchi quadrati, in base alla loro dimensione e al numero dei processi sui quali si intende eseguire l’algoritmo. Tale compito è eseguito dalla funzione computabile. Si procede inoltre all’allocazione delle spazio necessario a contenere a matrice C e delle strutture dati necessarie all’esecuzione dell’algoritmo. A questo punto si deve procedere al partizionamento delle matrici. Sono state, a tale scopo, realizzate le funzioni getCoordX e getCoordY; la prima data la dimensione della matrice e il rank del processo restituisce la coordinata dell’asse X dell’angolo più in alto a sinistra del sottoblocco della matrice assegnata al processo; la seconda calcola la coordinata Y. 6 Viene chiarito tutto ciò con un esempio: Supponiamo di voler eseguire l’algoritmo con 4 processi e di avere la seguente matrice A: 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 Quello che si vorrebbe ottenere è il seguente partizionamento: 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 Rank 0 Rank 1 Rank 2 Rank 3 Per ottenere ciò, le funzioni getCoordX e getCoordY restituiscono le seguenti coordinate: Rank processo 0 1 2 3 [master] Coordinata X 0 0 2 2 Coordinata Y 0 2 0 2 Spedizione sottoblocchi ai processi Conosciute le coordinate assegnate al processo, il passo seguente è quello di creare un array contenente tutti i dati che devono essere spediti. Per prima cosa si inseriscono nell’array gli elementi del sottoblocco assegnato al processo 0, poi gli elementi del sottoblocco assegnati al processo 1 e così via; tale funzionalità è implementata dalla funzione mat2Array. 1 2 5 6 3 4 7 8 9 10 13 14 11 12 15 16 Per effettuare la distribuzione dei blocchi, siccome siamo in presenza di un contesto distribuito, è necessaria per prima cosa comunicare a tutti gli slave quale è la dimensione del sottoblocco che gli viene assegnato tramite la funzione MPI_BCast(): // area di ogni sottoblocco della matrice assegnato ad un processo b=(int)(n/num_proc); //distribuzione della dimensione del sottoblocco MPI_Bcast(&b,1,MPI_INT,0,MPI_COMM_WORLD); 7 Per la distribuzione vera e propria dei sottoblocchi è stata utilizzata un’operazione di MPI_Scatter(): tale funzione prende in ingresso l’array, lo divide e lo distribuisce automaticamente a tutti i processi (compreso il master stesso), garantendo l’ordinamento rispetto al numero di rank. //conversione matrice A in array arrA=(double *)mat2Array(matA,num_proc); //conversione matrice B in array arrB=(double *)mat2Array(matB,num_proc); //scatter dei sottobolcchi di A ai processi MPI_Scatter(arrA,b,MPI_DOUBLE,subArrA,b,MPI_DOUBLE,0,MPI_COMM_WORLD); //scatter dei sottobolcchi di B ai processi MPI_Scatter(arrB,b,MPI_DOUBLE,subArrB,b,MPI_DOUBLE,0,MPI_COMM_WORLD); 1 2 5 Rank 0 6 3 4 7 8 9 Rank 1 10 13 14 11 Rank 2 12 15 16 Rank 3 [master e slave] Ricezione dei sottoblocchi Tutti gli slave e il master, invocando la funzione MPI_Scatter, memorizzano il sottoblocco ricevuto in un array di double (subArrA per matrice A e subArrB per matrice B). // allocazione spazio per i sottobloblocchi della matrice A e B subArrA=(double*)calloc((int)b,sizeof(double)); subArrB=(double*)calloc((int)b,sizeof(double)); //ricezione del sottoblocco di A e B MPI_Scatter(arrA,b,MPI_DOUBLE,subArrA,b,MPI_DOUBLE,0,MPI_COMM_WORLD); MPI_Scatter(arrB,b,MPI_DOUBLE,subArrB,b,MPI_DOUBLE,0,MPI_COMM_WORLD); 8 Rank 0 possiede 1 2 5 6 Rank 1 possiede 3 4 7 8 Rank 2 possiede 9 10 13 14 Rank 3 possiede 11 12 15 16 [master e slave] Scambio blocchi mancanti tra processi Tutti i processi hanno così ottenuto il proprio sottoblocco della matrice; ora è necessaria la fase di scambio dei sottoblocchi (ricevuti sotto forma di array) tra processi. A questo proposito, ogni processo determina, sempre tramite le funzioni getCoorX e getCoordY, le coordinate dell’asse delle X e Y dell’angolo più in alto a sinistra del sottoblocco assegnatoli: myCoordX e myCoordY. Quindi ogni processo crea un comunicatore MyComm_row contenente tutti e solo i processi che hanno la stessa coordinata X e MyComm_col la stessa coordinata Y. MyComm_row sarà utilizzato per lo scambio dei sottoblocchi tra processi che hanno il blocco ricevuto nella stessa riga della matrice A mentre MyComm_col per lo scambio dei sottoblocchi tra processi che hanno il blocco ricevuto nella stessa colonna della matrice B. Abbiamo la seguente situazione: Rank Processo 0 1 2 3 MyComm_row 0,1 0,1 2,3 2,3 MyComm_col 0,2 1,3 0,2 1,3 Infatti, ad esempio, il processo 0 e il processo 1 hanno il sottoblocco della matrice A che è nella stessa riga e perciò hanno lo stesso comunicatore MyComm_row. // coordinata x e y del processo myCoordX= getCoordX(rank,lato_mat,(int)sqrt(b)); myCoordY= getCoordY(rank,lato_mat,(int)sqrt(b)); //creazione di nuovi comunicatori MPI_Comm_split(MPI_COMM_WORLD,myCoordX,rank,&MyComm_row); MPI_Comm_split(MPI_COMM_WORLD,myCoordY,rank,&MyComm_col); 9 Per la comunicazione dei sottoblocchi tra processi è stata utilizzata la funzione MPI_Allgather; tramite l’utilizzo di tale funzione ogni processo memorizza due array : rowA contiene tutti i sottoblocchi della stessa riga della matrice A del blocco associato processo colB possiede tutti i sottoblocchi della stessa colonna della matrice B del blocco associato processo Ad esempio, l’array rowA (degli elementi della matrice A) posseduto dal processo 0 e dal processo 1 sono uguali mentre sono identici l’array colB del processo 0 e 2. Processo 0: rowA 1 2 5 6 3 4 7 8 colB 1 2 5 6 9 10 13 14 rowA 1 2 5 6 3 4 7 8 colB 3 4 7 8 11 12 15 16 Processo 1: //array di tutte le righe ricevute MPI_Allgather(subArrA,b,MPI_DOUBLE,rowA,b,MPI_DOUBLE,MyComm_row); //array di tutte le colonne ricevute MPI_Allgather(subArrB,b,MPI_DOUBLE,colB,b,MPI_DOUBLE,MyComm_col); A questo punto ogni processo possiede la riga della matrice A e la colonna della matrice B: è quindi in grado di calcolare il sottoblocco della matrice C di sua competenza. [master e slave] Moltiplicazione matrice C) sottoblocchi (calcolo sottoblocco della L’ultimo passo prima della moltiplicazione è trasformare l’ array rowA e colB nella forma di un’array di sottomatrici, denominati rispettivamente riga e colonna 10 rowA 1 2 5 1 5 riga colB 6 1 2 5 riga 3 2 6 6 1 5 4 3 7 9 2 6 8 13 14 4 8 10 9 13 7 10 14 Adesso è possibile effettuare la moltiplicazione tra l’array riga e colonna ottenendo la matrice calC : il blocco della matrice C che il processo dove calcolare. matrice *calC, *ris_temp; for(i=0;i<sqrt(num_proc);i++){ ris_temp=ProdottoMat(riga[i],colonna[i],((int)sqrt(b))); if(i==0) calC=ris_temp; else SommaMat(calC,ris_temp); } Il codice della funzione ProdottoMat è riportato qui sotto. Si può notare che se non è settata SLOW non viene applicata la funzione di ritardo. Quando invece tale variabile è settata, la variabile RITARDO specifica il numero di cicli da eseguire; variando tale valore è possibile verificare il comportamento dell’algoritmo con diversi livelli di granularità dei task. 11 // calcolo del prodotto tra due matrici matrice* ProdottoMat(matrice * a, matrice* b, int d){ double somma=0.0; int i,j,h; matrice *calC=(matrice*)malloc(sizeof(matrice)); calC->dim = d; calC->val = allocaMatrice(d); for(i=0; i<d;i++){ for(j=0;j<d; j++){ somma=0.0; for(h=0;h<d;h++){ if(SLOW){ int g, s=0; for(g=0;g<RITARDO;g++) s++; } somma=somma+(a->val[i][h]*b->val[h][j]); } calC->val[i][j]= (double) somma; } } return calC; } [master e slave] Spedizione blocco calcolato al master Ogni processo possiede il sottoblocco da spedire al master e per prima cosa lo deve convertire in array per la spedizione. double *subArrC; // conversione del blocco della matrice C calcolata dal processo subArrC=subMat2subArray(calC); 77 79 78 80 77 78 79 80 subArrC Attraverso la funzione MPI_Gather ogni processo spedisce il proprio array al master. // spedizione sottoblocchi del sottoblocco calcolato della matrice C e ricezione dei MPI_Gather(subArrC,b,MPI_DOUBLE,arrC,b,MPI_DOUBLE,0,MPI_COMM_WORLD); 12 [master] Raccolta di tutti i blocchi e creazione della matrice C Il master tramite l’invocazione della funzione MPI_Gather raccoglie tutti i sottoblocchi (spediti sottoforma di array) e li memorizza nell’array arrC, ordinati per numero del rank dei processi mittenti (prima quelli del processo 0, poi quelli del processo 1 e così via). MPI_Gather(subArrC,b,MPI_DOUBLE,arrC,b,MPI_DOUBLE,0,MPI_COMM_WORLD); Rank 0 22 23 24 25 Rank 1 26 27 28 29 Rank 2 30 31 32 33 Rank 3 34 34 36 37 arrC 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 Ottenuto l’array rimane da convertirlo in matrice: la matrice finale C = A × B. Tale conversione viene realizzata dalla funzione array2MatFormatted. // conversione dell'array in matrice matC=array2MatFormatted(arrC,matA->dim,num_proc); arrC 22 23 24 25 26 27 28 29 30 22 24 30 32 23 25 31 33 26 28 34 36 27 29 35 37 31 32 33 34 35 36 37 13 Cannon Algorithm Funzionamento Consideriamo ancora la moltiplicazione di due matrici quadrate di dimensione n, C = A × B. Anche in questo algoritmo, come nel precedente, le matrici A e B vengono partizionate in p sottoblocchi quadrati, dove p è il numero di processi sul quale si intende eseguire l’algoritmo. I processi vengono etichettati da P0,0 a P p -1, Ai,j e Bi,j al processo Pi,j. p -1 ed inizialmente viene associato il sottoblocco Ogni processo della riga i-esima richiede tutte le p sottomatrici Ai,k (0≤k< p ); è possibile schedulare la computazione dei p processi nell’ i-esima riga in moda tale che, in un certo istante di tempo, ogni processo usa un differente sottoblocco Ai,k. Questi blocchi possono così essere ruotati sistematicamente tra i processi dopo ogni moltiplicazione delle sottomatrici così che ogni processo possiede in ogni rotazione un nuovo sottoblocco Ai,k. Se una identica schedulazione è applicata per le colonne nessun processo necessita di più di un sottoblocco di ogni matrice alla volta , ottenendo un risparmio della memoria necessaria per la computazione. Il passo iniziale consiste nell’allineamento delle matrici A e B in modo che ogni processo possa moltiplicare i propri sottoblocchi posseduti in maniera indipendente. A tale scopo viene applicato uno shift a sinistra (con wraparound) di i step a tutti i sottoblocchi Ai,j della matrice A. Inoltre, tutti i sottoblocchi Bi,j della matrice B sono shiftati in alto di j step (con wraparound). Tali operazioni fanno si che il processo Pi,j abbia associato il sottoblocco Ai,(j+i)mod p e B(i+j)mod p ,j. A questo punto ogni processo è pronto per eseguire la prima moltiplicazione tra le sottomatrici. Dopo aver eseguito la moltiplicazione, ogni blocco della matrice A si “muove” di uno step a sinistra e ogni blocco della matrice B si muove di uno step in alto (ancora con wraparound). Ogni processo esegue la moltiplicazione dei sottoblocchi ricevuti, somma il risultato a quello ottenuto nel passo precedente e si ripete tale meccanismo fino a che tutti i processi non hanno eseguito tutte p moltiplicazioni. Terminata la rotazione, ogni processo Pi,j invia il blocco calcolato della matrice Ci,j al processo master che ha il compito di assemblare i blocchi ricevuti ed ottenere la matrice C. Come per l’algoritmo precedente, in base alla dimensione delle matrici A e B, poiché i sottoblocchi devono essere quadrati, non è possibile eseguire l’algoritmo con un numero casuale di processi. 14 Riassumendo: [master] [master] [master] [master] [master e salve] [master e slave] [master e slave] [master e slave] [master e slave] [master e slave] [master e slave] [master e slave] [master e slave] [master] Lettura delle matrici A e B Partizionamento delle matrici A e B Allineamento delle matrici A e B Spedizione sottoblocchi ai processi Ricezione dei sottoblocchi for k=1 to p Esecuzione prodotto sottoblocchi Somma risultato con quello ottenuto al passo precedente Spedizione blocco della matrice A al processo a sinistra Ricezione blocco della matrice A dal processo a destra Spedizione blocco della matrice B al processo sopra Ricezione blocco della matrice B dal processo sotto Spedizione blocco calcolato al master Raccolta di tutti i blocchi e creazione della matrice C Graficamente, l’algoritmo può essere spiegato nel seguente modo: Allineamento iniziale delle matrici A e B eseguita dal master 15 Matrici A e B dopo l’allineamento eseguito dal master (c) e dopo il primo shift Matrici A e B dopo il secondo shift (e) e dopo il terzo shift (f) 16 [master] Lettura e partizionamento matrici A e B Come per l’algoritmo precedente, il master è l’unico che può accedere alle matrici A e B. La lettura di tali matrici avviene da disco se è settata la variabile FROM_FILE altrimenti vengono generate automaticamente della dimensione specificata da DIM. Viene controllato se sia la matrice A che la matrice B possono essere suddivise in sottoblocchi quadrati, in base alla loro dimensione e al numero dei processi sui quali si intende eseguire l’algoritmo. Tale compito è eseguito dalla funzione computabile. Si procede inoltre all’allocazione delle spazio necessario a contenere la matrice C e delle strutture dati necessarie all’esecuzione dell’algoritmo. [master] Allineamento delle matrici A e B Prima di consegnare il sottoblocchi della matrice A e B bisogna procedere all’allineamento delle matrici. L’allineamento della matrice A è eseguito dalla funzione SwapRow mentre quello della matrice B è realizzato dalla funzione SwapCol. // passo iniziale: allinemento della matrice A e B SwapRow(matA,num_proc); SwapCol(matB, num_proc); Consideriamo ad esempio la matrice A partizionata nel seguente modo: 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 Rank 0 Rank 1 Rank 2 Rank 3 Ogni sottoblocco Ai,j viene spostato a sinistra di i posizioni (con wraparound). I sottoblocchi della prima riga rimangono nella posizione dove sono, quelli della seconda riga si spostano a sinistra di una posizione. Quello che si vuole ottenere quindi è la seguente situazione: 1 5 11 15 2 6 12 16 3 7 9 13 4 8 10 14 Rank 0 Rank 1 Rank 2 Rank 3 17 Per ottenere ciò è stata realizzata una funzione che, date le coordinate dell’angolo più in alto a sinistra del sottoblocco, calcola le nuove coordinate del blocco (siccome avviene uno spostamento a sinistra la coordinata dell’asse delle x rimane uguale). Rank processo 0 1 2 3 Coordinata X 0 0 2 2 Coordinata Y 0 2 0 2 Rank processo 0 1 2 3 Coordinata X 0 0 2 2 Coordinata Y 0 2 2 0 A questo punto sono disponibili le nuove coordinate di ogni processo; per eseguire lo spostamento vero e proprio e realizzare tutto ciò con un utilizzo limitato di memoria, si procede per righe e si utilizza un array di sottoblocchi temporaneo (utilizzato per contenere i blocchi della riga considerata nella posizione corretta): viene presa in considerazione la prima riga e vengono ricopiati i sottoblocchi nell’array in posizione corretta e quando tutti i sottoblocchi sono stati copiati, viene ricopiato il contenuto dell’array nella prima riga della matrice. Si procede successivamente con la seconda riga, si ricopiano i sottoblocchi nell’array in posizione corretta e poi si ricopia il contenuto dell’array nella seconda riga della matrice. Vediamo l’esempio con la seconda riga, i sottoblocchi devono essere spostati di una posizione a sinistra. 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 Riga presa in considerazione I blocchi vengono copiati nell’array temporaneo in posizione corretta: 11 15 12 16 9 13 10 14 1 5 11 15 2 6 12 16 3 7 9 13 4 8 10 14 Il contenuto dell’array viene copiato nella seconda riga della matrice ottenendo il risultato voluto. 18 Un procedimento simile è realizzato per la matrice B ma invece di procedere per righe si procede per colonne. [master] Spedizione sottoblocchi ai processi Il passo seguente è quello di creare un array contenente tutti i dati che devono essere spediti: la matrice deve essere convertita in array per la spedizione. Per prima cosa si inseriscono nell’array gli elementi del sottoblocco assegnato al processo 0, poi gli elementi del sottoblocco assegnati al processo 1 e così via; tale funzionalità è implementata dalla funzione mat2Array. 1 2 5 6 3 4 7 8 11 12 15 16 9 10 13 14 Per effettuare la distribuzione dei blocchi è necessaria per prima cosa comunicare a tutti gli slave quale è la dimensione del sottoblocco che gli viene assegnato tramite la funzione di MPI_BCast(): // area di ogni sottoblocco della matrice assegnato ad un processo b=(int)(n/num_proc); //distribuzione della dimensione del sottoblocco MPI_Bcast(&b,1,MPI_INT,0,MPI_COMM_WORLD); Per la distribuzione vera e propria dei sottoblocchi è stata utilizzata un’operazione di MPI_Scatter(): tale funzione prende in ingresso l’array, lo divide e lo distribuisce automaticamente a tutti i processi (compreso il master stesso), garantendo l’ordinamento rispetto al numero di rank. //conversione matrice A in array arrA=(double *)mat2Array(matA,num_proc); //conversione matrice B in array arrB=(double *)mat2Array(matB,num_proc); //scatter dei sottobolcchi di A ai processi MPI_Scatter(arrA,b,MPI_DOUBLE,subArrA,b,MPI_DOUBLE,0,MPI_COMM_WORLD); //scatter dei sottobolcchi di B ai processi MPI_Scatter(arrB,b,MPI_DOUBLE,subArrB,b,MPI_DOUBLE,0,MPI_COMM_WORLD); 19 1 2 5 6 3 Rank 0 4 7 8 11 12 Rank 1 15 16 9 Rank 2 10 13 14 Rank 3 [master e slave] Ricezione dei sottoblocchi Tutti gli slave e il master, invocando la funzione MPI_Scatter, memorizzano il sottoblocco ricevuto in un array di double (subArrA per la matrice A e subArrB per la matrice B). // allocazione spazio per i sottobloblocchi della matrice A e B subArrA=(double*)calloc((int)b,sizeof(double)); subArrB=(double*)calloc((int)b,sizeof(double)); //ricezione del sottoblocco di A e B MPI_Scatter(arrA,b,MPI_DOUBLE,subArrA,b,MPI_DOUBLE,0,MPI_COMM_WORLD); MPI_Scatter(arrB,b,MPI_DOUBLE,subArrB,b,MPI_DOUBLE,0,MPI_COMM_WORLD); Rank 0 possiede 1 2 5 6 Rank 1 possiede 3 4 7 8 Rank 2 possiede 11 12 15 16 Rank 3 possiede 9 10 13 14 [master e slave] Rotazione sottoblocchi e calcolo blocco della matrice C L’array ricevuto (sia quello della matrice A che B) viene convertito in matrice tramite la funzione array2Mat. /* conversione del blocco della matrice A ricevuto da array in matrice l: lato del sottoblocco */ ricA=(matrice*)malloc(sizeof(matrice)); int l=(int)(sqrt(b)); array2Mat(ricA,subArrA,l); 20 // conversione del blocco della matrice B ricevuto da array in matrice ricB=(matrice*)malloc(sizeof(matrice)); array2Mat(ricB,subArrB,l); Ad esempio il processo 0 converte subArrA in matrice: 1 2 5 1 5 6 [master e slave] for k=1 to 1. [master e slave] 2. [master e slave] 3. [master e slave] 4. [master e slave] 5. [master e slave] 6. [master e slave] 2 6 p Esecuzione prodotto sottoblocchi Somma risultato con quello ottenuto al passo precedente Spedizione blocco della matrice A al processo a sinistra Ricezione blocco della matrice A dal processo a destra Spedizione blocco della matrice B al processo sopra Ricezione blocco della matrice B dal processo sotto Vengono eseguiti p cicli, in ogni ciclo ogni processo esegue la moltiplicazione del sottoblocco posseduto della matrice A con quello della matrice B; eseguito ciò bisogna sommare il risultato con quello ottenuto nelle fasi precedenti. Viene allocata, a tale scopo, la matrice c_final utilizzata per memorizzare i risultati calcolati fino al passo precedente e c_parz per contenere il prodotto delle matrici eseguito nel ciclo; tale prodotto deve essere sommato al risultato ottenuto nelle fasi precedenti, il quale è presente nella matrice c_final. Il risultato di tale somma viene memorizzato nella matrice c_final. In ogni ciclo quindi: c_parz = (Ai,j ricevuto) * (Bi,j ricevuto) c_final = c_final + c_parz // calcolo del prodotto del sottoblocco della matrice A e B ricevute c_parz=(matrice*)ProdottoMat(ricA,ricB,l); // somma del risultato dell'iterazione corrente con quelli ottenuti nelle iterazioni precedenti c_final=(matrice*)SommaMat2(c_parz,c_final); Come per l’algoritmo precedente, dato che viene utilizzata la stessa funzione di moltiplicazione delle matrici, se non è settata SLOW non viene applicata la funzione di ritardo. Quando invece tale variabile è settata, la variabile RITARDO specifica il numero di cicli da eseguire; variando tale valore è possibile verificare il comportamento dell’algoritmo con diversi livelli di granularità dei task. 21 Per eseguire lo scambio dei blocchi della matrice A, ogni processo deve spedire il sottoblocco della matrice A al processo a sinistra e deve ricevere il nuovo blocco dal processo a destra. Per fare ciò sono state implementate due funzioni: getRankRowDest determina il rank del processo a cui devo inviare il sottoblocco della matrice A getRankRowMit specifica il rank del processo da cui devo ricevere il sottoblocco della matrice A // determinazione del rank del processo a cui devo inviare il sottoblocco di A posseduto row_dest=getRankRowDest(rank,num_proc); // determinazione del rank del processo da cui devo ricevere il sottoblocco di A row_mit=getRankRowMit(rank,num_proc); Ad esempio, consideriamo una esecuzione con 16 processi: 1 5 9 13 2 6 10 14 3 7 11 14 4 8 12 16 Rank processo 1 2 3 4 Da chi devo ricevere 2 3 4 1 A chi devo spedire 4 1 2 3 Il processo a cui inviare il blocco è quello a sinistra tranne il caso particolare in cui il blocco si trova nella prima posizione della riga: il processo destinatario è l’ultimo della riga. Nell’esempio, il processo 1 deve inviare il blocco posseduto al processo 4. 1 5 9 13 2 6 10 14 3 7 11 14 4 8 12 16 int getRankRowDest(int rank, int np){ int rank_dest; int proc_lato=(int)sqrt(np); //caso elemento ad inizio riga if((rank%proc_lato)==0) rank_dest=rank+(proc_lato-1); else rank_dest = rank-1; return rank_dest; } 22 Il processo da cui ricevere il blocco è quello a destra tranne il caso particolare dove il processo è nell’ultima posizione della riga: il processo mittente è quello all’inizio della riga. Nell’esempio fornito, il processo 4 deve ricevere il blocco dal processo 1. 1 5 9 13 2 6 10 14 3 7 11 14 4 8 12 16 // determinazione del rank del processo da cui ricevere il sottoblocco della matrice A int getRankRowMit(int rank, int np){ int rank_mit; int proc_lato=(int)sqrt(np); if((rank+1)%proc_lato==0) // se siamo sull'ultima colonna rank_mit=rank-(proc_lato-1); else rank_mit=rank+1; return rank_mit; } Per realizzare invece lo scambio dei blocchi tra processi della matrice B, ogni processo spedisce il sottoblocco posseduto a quello in alto e riceve il nuovo blocco dal processo in basso. A tale scopo sono state implementate due funzioni: getRankColDest determina il rank del processo a cui devo inviare il sottoblocco della matrice B getRankColMit specifica il rank del processo da cui devo ricevere il sottoblocco della matrice B // determinazione del rank del processo a cui devo inviare il sottoblocco di B posseduto col_dest=getRankColDest(rank,num_proc); // determinazione del rank del processo da cui ricevere il sottoblocco di B col_mit=getRankColMit(rank,num_proc); Ad esempio, consideriamo una esecuzione con 16 processi: 1 5 9 13 2 6 10 14 3 7 11 14 4 8 12 16 Rank processo 1 5 9 13 Da chi devo ricevere 5 9 13 1 A chi devo spedire 13 1 5 9 23 Il processo a cui inviare il blocco è quello in alto tranne il caso particolare in cui il blocco si trova nella prima posizione della colonna: il processo destinatario è l’ultimo della colonna. Nell’esempio, il processo 1 deve inviare il blocco posseduto al processo 13. 1 5 9 13 2 6 10 14 3 7 11 14 4 8 12 16 // determinazione del rank del processo a cui inviare il proprio sottoblocco della matrice B int getRankColDest(int rank,int np){ int rank_dest; int proc_lato=(int)sqrt(np); if (rank < proc_lato) rank_dest=np-(proc_lato-rank); else rank_dest=rank-proc_lato; return rank_dest; } Il processo da cui ricevere il blocco è quello in basso tranne il caso particolare nel quale il processo è nell’ultima posizione della colonna: il processo mittente è quello all’inizio della colonna. Nell’esempio fornito, il processo 13 deve ricevere il blocco dal processo 1. // determinazione del rank del processo da cui ricevere il sottoblocco della matrice B int getRankColMit(int rank, int np){ int rank_mit; int proc_lato=(int)sqrt(np); if(rank>=(np-proc_lato)) rank_mit=(rank+proc_lato)%np; else rank_mit=rank+proc_lato; return rank_mit; } Conosciuti i rank dei mittenti e dei destinatari, il passo successivo è la spedizione e la ricezione dei blocchi corrispondenti. Per realizzate tutto questo è stata utilizzata la funzione MPI_Sendrecv_replace. 24 // invio e ricezione del blocco A MPI_Sendrecv_replace(subArrA,b,MPI_DOUBLE,row_dest,1,row_mit,1,MPI_COMM_WO RLD,&statA); // invio e ricezione del blocco B MPI_Sendrecv_replace(subArrB,b,MPI_DOUBLE,col_dest,1,col_mit,1,MPI_COMM_WO RLD,&statB); E’ stato deciso di adottare MPI_sendrecv_replace perché MPI_Send e MPI_recv non possono essere utilizzati per la comunicazione punto a punto poiché se tutti i processi invocano MPI_Send o MPI_Recv, in un qualche ordine, si può verificare una situazione di deadlock. L’ultimo passo all’interno del ciclo for è quello di convertire i due sottoblocchi ricevuti (uno della matrice A e l’altro della matrice B) in forma matriciale dato che sono stati ricevuti due array. Tale compito è realizzato dalla funzione array2Mat. // conversione dell'array corrispondente al blocco della matrice A ricevuto in matrice array2Mat(ricA,subArrA,l); // conversione dell'array corrispondente al blocco della matrice B ricevuto in matrice array2Mat(ricB,subArrB,l); Ad esempio se è stato ricevuto il seguente array subArrA viene convertito nella seguente matrice: 1 2 5 6 1 5 2 6 Al termine del ciclo for, la matrice c_final contiene il sottoblocco della matrice C che il processo doveva calcolare. [master e slave] Spedizione blocco calcolato al master Ogni processo possiede il sottoblocco da spedire al master e per prima cosa lo deve convertire in array per la spedizione. double *subArrC; // conversione del blocco della matrice C calcolata dal processo subArrC=subMat2subArray(calC); 25 77 79 78 80 77 78 79 80 subArrC Attraverso la funzione MPI_Gather ogni processo spedisce il proprio array al master. // spedizione sottoblocchi del sottoblocco calcolato della matrice C e ricezione dei MPI_Gather(subArrC,b,MPI_DOUBLE,arrC,b,MPI_DOUBLE,0,MPI_COMM_WORLD); [master] Raccolta di tutti i blocchi e creazione della matrice C Il master tramite l’invocazione della funzione MPI_Gather raccoglie tutti i sottoblocchi (spediti sottoforma di array) e li memorizza nell’array arrC ordinati per numero del rank dei processi mittenti. MPI_Gather(subArrC,b,MPI_DOUBLE,arrC,b,MPI_DOUBLE,0,MPI_COMM_WORLD); Rank 0 22 23 24 25 Rank 1 26 27 28 29 Rank 2 30 31 32 33 Rank 3 34 34 36 37 arrC 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 Ottenuto l’array rimane da convertirlo in matrice: la matrice finale C = A × B. Tale conversione viene realizzata dalla funzione array2MatFormatted. // conversione dell'array in matrice matC=array2MatFormatted(arrC,matA->dim,num_proc); 26 arrC 22 23 24 25 26 27 28 29 30 22 24 30 32 23 25 31 33 26 28 34 36 27 29 35 37 31 32 33 34 35 36 37 27 Processor Farm Funzionamento Nell’algoritmo Processor Farm (o Worker Pool), a differenza degli altri due algoritmi, l’assegnazione dei task avviene in maniera dinamica, consentendo così un miglior bilanciamento del carico. In questo algoritmo il processo master non si occupa di eseguire i calcoli parziali delle matrici, ma solo di distribuire i task agli slaves e di raccogliere i risultati. I processi slaves invece si occupano dei calcoli parziali della matrice C inviando poi i risultati al processo master. Il primo passo di questo algoritmo consiste nel replicare la matrice B a tutti i worker. Successivamente, il processo master distribuisce a tutti gli altri processi, ad ogni passo, una riga della matrice A. Gli slaves, una volta ricevuta la riga, effettuano il calcolo vettore per matrice, tra la riga ricevuta e la matrice B e spediscono il risultato al processo master che a sua volta spedisce un’altra riga di A se ne sono disponibili ancora. L’algoritmo termina una volta esaurite le righe della matrice A. In particolare: • Viene replicata la matrice B a tutti i worker. • I task consistono nel calcolare k righe della matrice C con k < (n / p ) , dove n è la dimensione del lato della matrice e p è il numero di processi. • Il processo master assegna dinamicamente i task ai worker inviando k righe della matrice A. 28 C A B K Master Riga di C Riga di C Riga di A Riga di A B B Slave 1 Slave n Riassumendo: [master] [master] [master] [slaves] [slaves] [slaves] [master] [master] [master] Lettura delle matrici A e B Replica della matrice B a tutti gli altri worker Invio deii task a tutti i worker disponibili (k righe) Ricezione della riga della matrice A Prodotto vettore per matrice (riga di A per matrice B) Invio dei risultati e attesa di una eventuale nuova riga Finché non ho ricevuto tutte le righe della matrice A Ricezione dei risultati parziali e aggiornamento della matrice C Invio di eventuali altre righe della matrice A 29 [master] Lettura delle matrici A e B e replica della matrice B Il master è l’unico che può accedere alle matrici A e B. La lettura di tali matrici avviene da disco se è settata la variabile FROM_FILE altrimenti vengono generate automaticamente della dimensione specificata in DIM. La matrice B viene spedita tramite la funzione MPI_BCast( ); prima della spedizione della matrice vengono effettuate due operazioni: 1. Per effettuare la distribuzione della matrice B, siccome siamo in presenza di un contesto distribuito, è necessaria per prima cosa comunicare a tutti gli slave quale è la dimensione della matrice che gli viene assegnato tramite la funzione di MPI_BCast(): // assegno a dim la dimensione della matrice B dim= matB->dim // distribuzione della dimensione della matrice MPI_Bcast (&dim, 1, MPI_INT, 0, MPI_COMM_WORLD); 2. La matrice B viene convertita in array tramite la funzione subMat2subArray(matB). // conversione matrice B in array arrB = (double*) subMat2subArray(matB); L’array arrB, che rappresenta la matrice B,viene poi spedito a tutti gli altri worker tramite la funzione MPI_BCast() //spedizione e ricezione della matrice MatB MPI_Bcast(arrB, dim*dim, MPI_DOUBLE, 0, MPI_COMM_WORLD); [master] Invia i task a tutti i worker disponibili (k righe) In questa fase, la prima cosa fatta è controllare se il numero di processi allocati sia sufficiente affinché possa essere eseguito l’algoritmo. Il numero di processi deve essere almeno due: uno per il master e uno che effettua la computazione. Successivamente avviene l’assegnazione statica dei task ai vari processi. Questo problema si divide in due casi: 1. Il numero di righe della matrice A è minore rispetto al numero di processi allocati. In questo caso tutti i processi che non eseguano la computazione devono essere settati subito allo stato di IDLE 2. Il numero di righe della matrice A è maggiore o uguale al numero di processi allocati. In questo caso tutti i processi partecipano alla computazione. 30 Viene creato un array (map) per mantenere la corrispondenza tra il processo e il numero di riga assegnata. In questo modo è possibile sapere in ogni momento quale processo computa una determinata riga della matrice A. Quindi map [i] = j significa che il processo i ha computato la riga j della matrice A. //Map mantiene la corrispondenza tra processo e riga della matrice assegnata map=(int*)calloc(num_proc,sizeof(int)); // il master non effettua il prodotto delle matrici map[0]=0; Viene poi effettuata l’operazione di assegnamento delle righe ai processi: for (i=0;i<limite;i++){ prelevaRiga(matA, i, rowA); MPI_Send(rowA, dim, MPI_DOUBLE, i+1, INVIO, MPI_COMM_WORLD); //corrispondenza riga--->processo map[i+1]=i } L’i-esima riga della matrice A viene prelevata tramite la funzione prelevaRiga( ) e inviata al processo i+1-esimo (i+1 perché il processo 0 essendo il master non effettua la computazione). Inoltre viene aggiornata la corrispondenza processo – riga. Come detto in precedenza nel caso in cui il numero di righe sia inferiore al numero di processi allocati, tutti quelli che non lavorano vengono settati a IDLE. Tutto questo viene fatto inviando al processo un array contenete tutti valori nulli (il vettore riga_zero); tale messaggio inviato ha tag IDLE. //caso numero processori maggiore del numero di righe della matrice, setto a idle tutti i processori che non lavorano if(dim <= num_proc-1){ int q; for(q=limite+1;q<num_proc;q++){ MPI_Send(riga_zero, dim, MPI_DOUBLE, q, IDLE, MPI_COMM_WORLD); } [slaves] operazioni lato slaves I processi slaves compiono queste operazioni: 1. 2. 3. 4. ricevono la dimensione della matrice B e tutta la matrice B; ricevono la riga della matrice A; effettuano il prodotto vettore per matrice (riga della matrice A per matrice B); inviano il vettore risultato al master e si mettono in attesa di una nuova riga da calcolare finché non vengono settati dal master allo stato di IDLE. La ricezione della dimensione della matrice B e di tutta la matrice stessa avviene tramite l’operazione di collettiva MPI_BCast( ); I processi non ricevono la matrice vera e propria ma un 31 vettore che rappresenta la matrice. Tale array verrà poi convertito in matrice da ogni worker attraverso la funzione arrayToMat(). MPI_Bcast (&dim, 1, MPI_INT, 0, MPI_COMM_WORLD); arrB = (double *) calloc(dim*dim, sizeof(double)); //ricezione della matrice Matrice B MPI_Bcast(arrB, dim*dim, MPI_DOUBLE, 0, MPI_COMM_WORLD); ricB = (matrice *) malloc (sizeof(matrice)); array2Mat(ricB,arrB,dim); La seconda operazione è quella di ricevere una riga della matrice A per poi successivamente farne il prodotto con la matrice B. Tale operazione continua ad avvenire fino a che il worker continua ricevere dati dal master. Quando il master ha terminato le righe, il processo worker riceve un vettore nullo e viene settato allo stato di IDLE riga_temp = (double*) calloc (dim, sizeof(double)); riga_ris = (double*) calloc (dim, sizeof(double)); MPI_Recv(riga_temp, dim, MPI_DOUBLE, 0 , MPI_ANY_TAG , MPI_COMM_WORLD, &statA); // Fino a che ricevo dati while (statA.MPI_TAG == INVIO){ //Esecuzione del prodotto della riga della matrice A ricevuto con la matrice B ProdVetMat(riga_temp, ricB, riga_ris); //Invio della riga calcolata e attesa della prossima riga MPI_Send(riga_ris, dim, MPI_DOUBLE, 0, RICEZIONE, MPI_COMM_WORLD); MPI_Recv(riga_temp, dim, MPI_DOUBLE,0,MPI_ANY_TAG,MPI_COMM_WORLD, &statA); } La riga ricevuta dal processo master viene memorizzata nell’array riga_temp, mentre il risultato del prodotto riga per matrice che viene calcolato attraverso la funzione ProdVetMat() e viene memorizzato nell’array riga_ris. Il risultato viene spedito al processo master attraverso la funzione MPI_Send(), mentre la ricezione delle righe viene effettuata attraverso la funzione MPI_Recv(). Il controllo sul tipo di dato ricevuto o inviato viene fatto attraverso la verifica dello stato del valore del tag associato al messaggio: i valori del tag sono tre: 1. INVIO significa che sono stati spediti dei dati da computare (da master a slaves); 2. RICEZIONE significa che sono stati spediti dati già computati (da slaves a master); 3. IDLE significa che è un messaggio che setta a IDLE un processo (da master a slaves). Il processo, se non è idle, si mette poi in attesa di un nuovo messaggio. I messaggi che può ricevere sono di due tipi: 1. un’altra riga della matrice A da computare. In questo caso il messaggio ha tag di tipo INVIO; 2. un messaggio contenente un array nullo con associato un tag di tipo IDLE. In questo caso il processo viene settato allo stato di IDLE. Il processo, quindi non sapendo a priori quale tipo di messaggio può ricevere, associa alla primitiva MPI_Recv( ) il tag di tipo MPI_ANY_TAG. In questo modo il tag del messaggio ricevuto può essere qualsiasi. 32 [master] Raccolta dei risultati In questa sezione il master attraverso la funzione MPI_Recv ( ) attende tutti i risultati delle righe della matrice A spedite precedentemente e calcolati dai processi slaves. L’attesa procede finché il numero di righe ricevute non raggiunge il numero di righe (la dimensione del lato) della matrice A. In questo caso il messaggio ha tag di tipo RICEZIONE. Infatti il processo è in attesa di una riga già calcolata. Il processo master non conosce a priori nemmeno da chi riceverà la riga calcolata, visto che distribuisce le righe a più processi. Quindi il campo mittente della funzione MPI_Recv ha valore MPI_ANY_SOURCE che identifica che il mittente del messaggio può essere uno qualsiasi. //Fino a il master non ha ricevuto tutte le righe dagli slaves while (num_righe_rec < dim){ MPI_Recv(riga_temp,dim,MPI_DOUBLE,MPI_ANY_SOURCE,RICEZIONE,MPI_COMM_WORLD, &statB); Ricevuta una riga calcolata, il processo master inserisce la righe nella matrice C (lo spazio necessario per la matrice deve essere già stato allocato). L’inserimento avviene attraverso l’utilizzo della funzione settaRiga( ) che inserisce la riga riga_temp nella posizione specificata dall’array map che era stato precedentemente aggiornato. L’indice di map, che rappresenta il processo che ha calcolato la riga, può essere trovato estraendo dallo stato del messaggio appena ricevuto il valore MPI_SOURCE che rappresenta il mittente del messaggio. settaRiga(matC, riga_temp, map[statB.MPI_SOURCE]); Riassumendo nella matrice C, alla posizione map[statB.MPI_SOURCE], viene inserita la riga calcolata riga_temp. A questo punto, se sono disponibili altre righe della matrice A da calcolare, al processo che ha inviato l’ultima riga calcolata, viene spedita un’altra riga oppure viene inviato un messaggio che lo setta a IDLE. //Se sono disponibili ancora righe da inviare /calcolare if(num_righe_send < dim){ prelevaRiga(matA, num_righe_send, rowA); MPI_Send(rowA, dim, MPI_DOUBLE, statB.MPI_SOURCE, INVIO, MPI_COMM_WORLD); map[statB.MPI_SOURCE]=num_righe_send; //aggiorno il numero di righe inviate num_righe_send++; } else{ //altrimenti setto a IDLE i processi che hanno finito di lavorare MPI_Send(riga_zero,dim,MPI_DOUBLE,statB.MPI_SOURCE,IDLE,MPI_COMM_WORLD); } 33 Algoritmi Sequenziali per la moltiplicazione delle matrici Algoritmo normalmente utilizzato n −1 Il prodotto C delle matrici A e B è definito come cij = ∑ a ik × bkj , dove aij, bij, and cij sono gli k =0 elementi dell’ i-esima riga e della j-esima colonna delle matrici A, B e C rispettivamente. Per poter eseguire la moltiplicazione delle matrici, la dimensione di AB=C deve soddisfare (n × m)(m × p ) = (n × p ) . Per semplicità, supponiamo di utilizzare matrici quadrate: in tal caso le matrici A, B e C sono tutte n × n. L’algoritmo sequenziale per la moltiplicazione della matrici può essere descritto nel seguente modo: for i = 0 to n-1 for j = 0 to n-1 cij =0 for k = 0 to n-1 cij = cij + aik × bkj Tale algoritmo richiede n3 addizioni e moltiplicazioni: la complessità è dell’ordine di O(n3). Algoritmo di Strassen L’algoritmo di Strassen reduce la complessità a O(n2.81). In questo algoritmo, le matrici n × n sono suddivise in quattro n/2 × n/2 sottomatrici. La figura sotto riportata mostra una moltiplicazione di matrici 2 × 2. Poiché tale algoritmo divide le matrici in 2 e le moltiplica ricorsivamente, per poter eseguire tali operazioni, le matrici devono essere quadrate e la loro dimensione deve essere una potenza di 2. Tale limitazione può essere superata tramite l’utilizzo dell’algoritmo di Winograd, una variante dell’algoritmo di Stassen. Se le matrici A e B sono della dimensione 2dm × 2dm, l’algoritmo di Stassen viene eseguito ricorsivamente d volte. Dopo d ritorsioni, l’algoritmo standard è utilizzato per la moltiplicazione delle matrici m × m. 34 Un esempio di esecuzione dell’algoritmo di Stassen per la moltiplicazione delle matrici di dimensione 2 × 2 è riportato qui sotto. Necessita di 7 moltiplicazioni e 18 addizioni/sottrazioni. c00c01 a00 a01 b00b01 = c c a a b b 10 11 10 11 10 11 S1 = a10 + a11 S2 = S1 – a00 S3 = a00 – a10 S4 = a01 – S2, P1 = a00b00 P2 = a01b10 P3 = S1T1 P4 = S2T2 P5 = S3T3 P6 = S4b11 P1 = a11T4 Algorito di Stassen per la mo c00 = U1 c10 =U4 T1 = b01 – b00 T2 = b11 – T0 T1 = b11 – b01 T1 = b10 – T1 U1 = P1 + P2 U2 = P1 + P4 U3 = U2 + P5 U4 = U3 + P7 U5 = U3 + P3 U6 = U2 + P3 U7 = U6 + P6 c01 = U7 c11=U5 35 Test progetto Gli obiettivi dei test è la valutazione delle seguenti caratteristiche: l’eterogeneità e lo sbilanciamento del cluster lo speedup con differenti granularità e differenti dimensioni del problema Per tale motivo, le dimensioni delle matrici (dense) scelte sono: 384 768 1152 1920 Questo permette di valutare ogni algoritmo con una variazione consistente della dimensione delle matrici e quindi del relativo costo computazionale: si parte da una matrice di dimensioni ridotte fino ad arrivare ad una di dimensioni elevate. Dato che la moltiplicazione delle matrici non è sufficiente per ottenere un vantaggio sull’utilizzo degli algoritmi paralleli è stata implementata una funzione f, detta di rallentamento, in modo da eseguire C = f(A) × B invece che C = A× B. Tale funzione di rallentamento può eseguire il seguente numero di cicli: 0 5 10 40 (non presenza) Il cluster sul quale sono stati eseguiti i test utilizza macchine Linux con kernel 2.6.17.8 accesso remoto a tale cluster avviene attraverso [email protected]. Principali grafici realizzati con relativi commenti Di seguito vengono riportati i grafici delle prestazioni dei 3 algoritmi implementati, ottenuti attraverso i test effettuati. 36 Commento Grafici Numero processi vs. tempo Matrice 384 Come si può notare dal primo grafico, con matrici di dimensioni ridotte e quindi con carico computazionale molto limitato, è possibile constatare che passando da 4 a 9 processi il tempo di esecuzione rimane pressoché invariato, mentre nel passaggio da 9 a 16 il tempo di esecuzione degli Algoritmi Processor Farm e Matrix Matrix aumenta, quello dell’altro algoritmo rimane costante. Questo comportamento anomalo è dovuto al maggior numero di comunicazioni che gli algoritmi devono effettuare all’aumentare del numero di processi; questo si nota maggiormente nel Processor Farm visto che bisogna inviare l’intera matrice B ad ogni processo creando così un rallentamento del processo master con conseguente degradazione delle prestazioni. Matrice 384 − Funzione rallentamento disattivata 2 Matrix Multiply Cannon Algorithm Processor Farm 1.8 1.6 1.4 Tempo (sec) 1.2 1 0.8 0.6 0.4 0.2 0 4 6 8 10 Numero Processi (np) 12 14 16 La situazione migliora con l’attivazione della funzione di rallentamento, infatti il costo computazionale aumenta rispetto ai costi di comunicazione e quindi traiamo vantaggio nell’esecuzione con un numero di processi maggiore 37 Matrice 384 − Funzione rallentamento attivata a 40 cicli 5 Matrix Multiply Cannon Algorithm Processor Farm 4.5 4 3.5 Tempo (sec) 3 2.5 2 1.5 1 0.5 0 4 6 8 10 Numero Processi (np) 12 14 16 . Matrice 1920 38 Matrice 1920 − Funzione rallentamento disattivata 300 Matrix Multiply Cannon Algorithm Processor Farm 250 Tempo (sec) 200 150 100 50 0 4 6 8 10 12 Numero Processi (np) 14 16 In questa situazione possiamo notare che anche se la funzione di rallentamento non è attivata, grazie al maggior costo computazionale dovuto all’aumento della dimensione della matrice, il tempo di esecuzione diminuisce al crescere del numero dei processi. Il miglioramento delle prestazioni è dovuto alla minore quantità di dati elaborati da ogni processo poiché sono a disposizione più processi. Il miglioramento più accentuato del Processor Farm può essere giustificato dal maggior numero di righe che vengono processate parallelamente. Infatti, nel caso di 4 processi, un processo ha il compito di gestire le comunicazioni delle righe e solo 3 processi sono disponibili per il calcolo. Con 9 processi sono disponibili ben 8 processi che possono eseguire i calcoli parallelamente. Il Processor Farm non riesce comunque a raggiungere le prestazioni degli altri algoritmi perché le comunicazioni hanno ancora costi maggiori rispetto ai costi computazionali. 39 Matrice 1920 − Funzione rallentamemto attivata a 40 cicli Matrix Multiply Cannon Algorithm Processor Farm 700 600 Tempo (sec) 500 400 300 200 100 0 4 6 8 10 12 Numero Processi (np) 14 16 Possiamo notare infatti che abilitando la funzione di rallentamento a 40 cicli, abbiamo numero di computazioni sufficienti a rendere il Processor Farm più performante rispetto agli altri due. 40 Commento Grafici Ritardo vs. tempo Numero processi 4 Matrice 384 − Numero Processi 4 5 Matrix Multiply Cannon Algorithm Processor Farm 4.5 4 3.5 Tempo (sec) 3 2.5 2 1.5 1 0.5 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 41 Matrice 1152 − Numero Processi 4 150 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 100 50 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 Si noti che con un numero di processi pari a 4, aumentando il numero di cicli eseguiti dalla funzione di rallentamento, cresce il maniera lineare il tempo impiegato dai vari algoritmi, con la matrice di arietà minore. Tale crescita è molto accentuata fino ad un ritardo pari a 10 cicli dopodiché tende a crescere meno rapidamente. In entrambi i grafici il Processor Farm ha prestazioni peggiori. I risultati del primo grafico sono dovuti al tempo di comunicazione che rimane invariato, mentre il tempo di computazione in ogni processo viene incrementato in maniera lineare attraverso la funzione di rallentamento. Un discorso simile può essere fatto per il secondo grafico. Il tempo superiore impiegato dal Processor Farm è dovuto al numero limitato di processi disponibili (in questo caso 3 invece che 4). 42 Numero processi 16 Matrice 1920 − Numero processi 16 200 Matrix Multiply 180 Cannon Algorithm Processor Farm 160 Tempo (sec) 140 120 100 80 60 40 20 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 Quanto detto prima, può essere confermato dalla figura sopra riportata; grazie al maggior numero dei processi a disposizione, il Processor Farm raggiunge prestazioni più elevate rispetto agli altri algoritmi. Grafici dimensione matrice – tempo Funzione di rallentamento disattivata 43 Funzione rallentamento disattivata − numero processi 4 300 Matrix Multiply 250 Cannon Algorithm Processor Farm Tempo (sec) 200 150 100 50 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Con 4 processi e con una funzione di rallentamento disattivata, all’aumentare della dimensione della matrice il tempo di esecuzione dei tre algoritmi aumenta. Tale aumento è dovuto all’incremento della dimensione dei sottoblocchi spediti, negli algoritmi Matrix Matrix e Cannon mentre nel Processor Farm viene aumentata sia la dimensione dei dati inviati (dato che la dimensione delle righe è maggiore), sia il numero di comunicazioni necessarie (incremento numero righe) . Nel Processor Farm le prestazioni sono peggiori grazie all’elevato numero di comunicazioni eseguite. Gli algoritmi Matrix Matrix e Cannon hanno prestazioni, in questo caso, molto simili perché il numero di comunicazioni eseguite e la dimensione dei dati inviati si equivalgono. La differenza di prestazioni tra l’algoritmo Processor Farm e gli altri due aumenta con la crescita della dimensione della matrice; questo succede perché il costo di computazione è basso e quindi il tempo di comunicazione tende a “sovrastare” quest’ultimo. 44 Funzione rallentamento disattivata − numero processi 16 70 Matrix Multiply Cannon Algorithm 60 Processor Farm Tempo (sec) 50 40 30 20 10 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Si noti che con 16 processi, il divario tra il tempo di esecuzione del Processor Farm e quello degli altri algoritmi diminuisce dato che gli algoritmi vengono eseguiti su di un numero maggiore di processi e quindi aumentano il numero delle comunicazioni da eseguire negli algoritmi Matrix Matrix e Cannon. Funzione di rallentamento attivata 45 Funzione rallentamento attivata a 40 cicli − numero processi 16 200 180 Matrix Multiply Cannon Algorithm 160 Processor Farm Tempo (sec) 140 120 100 80 60 40 20 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Con la funzione di rallentamento attivata a 40 cicli, viene aumentato il tempo necessario alla computazione e questo fa si che le performance del Processor Farm si avvicinino a quelle degli altri algoritmi, fino a diventare le migliori. Dal passaggio da una situazione senza funzione di rallentamento a una con la funzione di rallentamento di 40 cicli, il costo delle comunicazioni è invariato ma quello del calcolo aumenta notevolmente. In questa situazione, trae vantaggio l’algoritmo Processor Farm perché il prodotto vettore per matrice ha complessità minore rispetto al prodotto matrice per matrice. Grafici dimensione numero processori – speedup Matrice 384 46 Calcolo Speedup − Funzione disattivata − Matrice: 384 X 384 2.5 Speedup (Sp(n)) 2 Matrix Multiply Cannon Algorithm 1.5 1 Processor Farm 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Speedup − Funzione attivata 40 cicli − Matrice: 384 X 384 10 9 8 Speedup (Sp(n)) 7 6 5 Matrix Multiply Cannon Algorithm 4 Processor Farm 3 2 1 4 6 8 10 12 Numero Processori (np) 14 16 47 Nel caso della matrice di dimensione 384 con funzione disattivata, il problema risulta troppo piccolo per ottenere degli speedup lineari. Appare evidente che il tempo di comunicazione incida nettamente rispetto al tempo di computazione. Infatti all’aumentare del numero di processi, e quindi dei tempi di comunicazione, lo speedup decresce. Attivando però la funzione di rallentamento (nel caso a 40 cicli) , e quindi aumentando il peso computazionale, i risultati migliorano ottenendo anche degli speedup lineari nel caso in cui il costo per la comunicazione è basso ( uso di 4 processi ). Questo è dovuto dal fatto che l’algoritmo sequenziale è limitato dalla CPU, mentre gli algoritmi paralleli possono suddividere il carico ai vari processori disponibili. Si potrebbe pensare che all’aumentare del numero di processi lo speedup si mantenga lineare, ma questo non accade perché il peso computazionale rispetto a quello di comunicazione risulta inferiore (la matrice 384 è troppo piccola). Matrice 1152 Calcolo Speedup − Funzione F attivata : 40 − Matrice: 1152 X 1152 12 11 10 Speedup (sp(n)) 9 8 7 6 5 Matrix Multiply 4 Cannon Algorithm Processor Farm 3 2 1 4 6 8 10 12 Numero Processori (np) 14 16 Con questa matrice di grandi dimensioni e funzione f attivata a 40 cicli riusciamo ad avere un calcolo computazionale elevato, avvantaggiando quindi tutti gli algoritmi paralleli. Infatti come possiamo vedere in figura nel caso in cui i processi variano da 4 a 9 riusciamo ad avere degli speedup lineari (per gli algoritmi Matrix Matrix e Cannon), mentre nel passaggio da 9 a 16 processi lo speedup ottenuto tende a diminuire (rispetto a quello lineare) tranne nel caso dell’algoritmo Processor Farm (che mantiene una crescita lineare ma non raggiunge mai uno speedup lineare, neanche nel caso di 4 e 9 processi). Questa diminuzione è dovuta sempre dal fatto che all’aumentare del numero di processi aumenta il costo di comunicazione e diminuisce il costo del calcolo e quindi gli overhead di comunicazione diventano la frazione maggiore del tempo totale. L’algoritmo 48 Processor Farm mantiene una crescita dello speedup lineare perché il numero di comunicazioni all’aumentare del numero di processi rimane invariato, ma il singolo processo esegue globalmente un numero di computazioni inferiori. Matrice 1920 Calcolo Speedup − Funzione F disattivata − Matrice: 1920 X 1920 25 Matrix Multiply Cannon Algorithm Speedup (sp(n)) 20 Processor Farm 15 10 5 2 4 6 8 10 Numero Processori (np) 12 14 16 49 Calcolo Speedup − Funzione F attivata : 10 − Matrice: 1920 X 1920 25 Matrix Multiply Cannon Algorithm Processor Farm Speedup (sp(n)) 20 15 10 5 4 6 8 10 12 Numero Processori (np) 14 16 50 Con una funzione di rallentamento disattivata lo speedup in questo caso è superlineare. Il motivo dello speedup superlineare è l’utilizzo di un algoritmo sequenziale non ottimo. L’algoritmo utilizzato nel nostro caso per moltiplicazione tra matrici, ha complessità O(m3) dove m è la dimensione della matrice. Un algoritmo che ha complessità asintotica minore è l’algoritmo di Strassen, introdotto nel 1969, che ha complessità O(mlg(7)) dove lg(x) denota il logaritmo in base 2 di x e lg(7) ≈ 2,807. Non è stato utilizzato questo algoritmo perché è molto difficile da implementare e richiede un notevole utilizzo della memoria. Se fosse stato utilizzato questo algoritmo, probabilmente, lo speedup raggiungerebbe risultati più consoni, anche negli altri casi. All’aumentare della numero di cicli eseguiti dalla funzione di rallentamento, però, lo speedup tende a migliorare, diventando in alcuni casi addirittura lineare, come ad esempio quando la funzione di rallentamento è a 10 cicli con un numero di processi pari a 16. I risultati dell’algoritmo Processor Farm sono peggiori rispetto agli altri due algoritmi. Le prestazioni tendono comunque a migliorare aumentando il numero di cicli della funzione di rallentamento e nel caso in cui il numero è 40 raggiunge risultati leggermente migliori rispetto agli algoritmi Matrix Matrix e Cannon. Grafici dimensione numero processori – efficienza Essendo i risultati dell’efficienza strettamente legati ai risultati dello speedup, quando quest’ultimo è minore del numero di processi abbiamo un’efficienza minore di 1 mentre nel caso di speedup lineare l’efficienza tende al valore 1. Infine nel caso di speedup superlineare l’efficienza ha valori maggiori di 1. Quindi riportiamo i grafici dell’efficienza solo per completezza senza commenti. I commenti sono gli stessi dei grafici dello speedup. 51 Matrice 384 Calcolo Efficenza − Funzione F disattivata : − Matrice: 384 X 384 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 Numero Processori (np) 12 14 16 52 Calcolo Efficenza − Funzione F attivata : 40 − Matrice: 384 X 384 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 Numero Processori (np) 12 14 16 Matrice 1152 53 Matrice 1190 54 Calcolo Efficenza − Funzione F disattivata − Matrice: 1920 X 1920 2.5 Matrix Multiply Cannon Algorithm Efficenza (E(n)) 2 Processor Farm 1.5 1 0.5 0 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Efficienza − Funzione arrivata 10 cicli − Matrice: 1920 X 1920 2.5 Matrix Multiply 2 Cannon Algorithm Efficenza (E(n)) Processor Farm 1.5 1 0.5 0 4 6 8 10 12 Numero Processori (np) 14 16 55 Calcolo Efficienza − Funzione arrivata 40 cicli − Matrice: 1920 X 1920 2 1.8 Matrix Multiply Cannon Algorithm 1.6 Processor Farm Efficenza (E(n)) 1.4 1.2 1 0.8 0.6 0.4 0.2 0 4 6 8 10 12 Numero Processori (np) 14 16 56 Valutazione bilanciamento Matrice 384 Matrice 384 − Funzione rallentamento disattivata − Numero processi = 4 1.2 1.1 Tempo (sec) 1 0.9 0.8 Matrix Multiply Cannon Algorithm Processor Farm 0.7 0.6 0.5 0 0.5 1 1.5 Rank processo 2 2.5 3 57 Matrice 384 − Funzione rallentamento 40 cicli − Numero processi = 4 4.5 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 4 3.5 3 2.5 0 0.5 1 1.5 Rank processo 2 2.5 3 Con un numero di processi pari a 4 e con una matrice di dimensione relativamente piccola (384) nel caso dell’algoritmo Processor Farm il carico risulta essere ben bilanciato tra tutti i nodi del cluster. Per quanto concerne gli algoritmi Matrix Matrix e Cannon risultano avere un lieve sbilanciamento e il pattern seguito da questi due algoritmi risulta essere simile. 58 Matrice 384 − Funzione rallentamento disattivata − Numero processi = 16 1.4 Matrix Multiply Cannon Algorithm Processor Farm 1.3 1.2 Tempo (sec) 1.1 1 0.9 0.8 0.7 0.6 0.5 0.4 0 5 10 15 Rank processo Matrice 384 − Funzione rallentamento 40 cicli − Numero processi = 16 2 Matrix Multiply Cannon Algorithm Processor Farm 1.9 1.8 Tempo (sec) 1.7 1.6 1.5 1.4 1.3 1.2 1.1 1 0 5 10 15 Rank processo 59 All’aumentare del numero dei processi la situazione risulta essere simile a quella descritta in precedenza. In tutti e tre gli algoritmi il tempo impiegato dal processo 0 è lievemente maggiore rispetto a quello degli altri dato che ha il compito della gestione della matrice C. Matrice 1920 Matrice 1920 − Funzione rallentamento disattivata − Numero processi = 4 300 Tempo (sec) 250 Matrix Multiply Cannon Algorithm Processor Farm 200 150 100 0 0.5 1 1.5 Rank processo 2 2.5 3 60 Matrice 1920 − Funzione rallentamento 40 cicli − Numero processi = 4 750 700 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 650 600 550 500 450 400 0 0.5 1 1.5 Rank processo 2 2.5 3 Passando ad una situazione nella quale la dimensione della matrice è maggiore si può constatare che anche in questo caso il Processor Farm è l’algoritmo che offre un bilanciamento migliore; il bilanciamento degli altri algoritmi comunque è più che accettabile. 61 Matrice 1920 − Funzione rallentamento disattivata − Numero processi = 16 80 70 Tempo (sec) 60 Matrix Multiply Cannon Algorithm Processor Farm 50 40 30 20 0 5 10 15 Rank processo Matrice 1920 − Funzione rallentamento 40 cicli − Numero processi = 16 200 180 Tempo (sec) 160 Matrix Multiply Cannon Algorithm Processor Farm 140 120 100 80 0 5 10 15 Rank processo 62 In una situazione con grande granularità e soprattutto con un numero di cicli elevato, l’algoritmo Processor Farm risulta essere ancora ben bilanciato, il Cannon ha un comportamento abbastanza bilanciato mentre con l’algoritmo Matrix Matrix siamo in presenza di una situazione di forte sbilanciamento del carico. 63 Caratteristiche del sistema In questa sezione vengono descritte le caratteristiche principali del sistema implementato. Elenco file progetto Il codice degli algoritmi paralleli è presente nei seguenti file: AlgoritmoMatrixMatrix.c implementazione dell’algoritmo MatrixMatrix AlgoritmoCannon.c realizzazione dell’algoritmo Cannon AlgoritmoWorkerPool implementazione dell’algoritmo Processor Farm Ogni algoritmo ha definito le seguenti variabili: #define MATRIX_A "/tmp/matriceA" Path della matrice A scelto staticamente #define MATRIX_B "/tmp/matriceB" Path della matrice B scelto staticamente #define PATH "/tmp/algo_3_" Path dove memorizzare la matrice C (risultato) deciso staticamente #define BENCH "/tmp/algo_bench_3_" Path dove memorizzare il file contenente i tempi riscontrati Per poter conoscere il tempo di esecuzione dell’algoritmo Sequenziale, utilizzato ad esempio per il calcolo dello Speedup e dell’ Efficienza, è stato creato il file “AlgoritmoSequenziale.c”. Siccome la lettura delle matrici deve avvenire anche da file è stata realizzato, a tale scopo, il file “creamatrix.c”: viene richiesta la dimensione della matrice quadrata che si vuole creare e il path dove si desidera salvarla. Inserire la dimensione della matrice: 1920 Inserire il nome del file (percorso assoluto): /tmp/marice/MatriceCreata Le funzioni di utilità per l’implementazione degli algoritmi sono presenti nel file “matrice.c”. 64 E’ presente anche un header “matrice.h”, il quale contiene alcuni parametri che permettono di diversificare il comportamento degli algoritmi. #define DEBUG (0/1) Se settata specifica che si vuole eseguire gli algoritmi in modalità di debug. #define TEMPO (0/1) Nel caso in cui sia uguale ad 1 indica che vengono attivati i meccanismi per il calcolo del tempo impiegato da ciascun processo. #define FROM_FILE (0/1) Se uguale a 1, le matrici A e B vengono lette da file altrimenti vengono generate in maniera random. #define AUTO (0/1) Se è settata, viene richiesto il path delle matrici A e B quando vengono lette da file e il percorso dove memorizzare la matrice C; altrimenti il path della matrice A è quello presente in MATRIX_A, quello della matrice B in MATRIX_B e quello della matrice C in PATH. Inoltre, se ha valore 1, il file che contiene le performance è memorizzato nel path specificato in BENCH; in caso contrario, il path utilizzato viene specificato dall’utente. #define DIM 768 Specifica della dimensione della matrice nel caso in cui viene generata automaticamente (FROM_FILE = 0). #define SLOW 1 Se ha valore 1 significa che è attivata la funzione di rallentamento. #define RITARDO 5 Specifica del numero di cicli che deve eseguire la funzione di rallentamento #define #define #define #define #define #define #define COL(x) COL_RED COL_GREEN COL_YELLOW COL_BLUE COL_WHITE COL_GRAY "\033[" #x ";1m" COL(31) COL(32) COL(33) COL(34) COL(37) "\033[0m" Definizione del colore con cui si desidera stampare l’output (esempio la stampa delle matrici a video è blu). 65 Come spiegato nel manuale d’uso, è stato creato un “makefile” per facilitare la compilazione dei file e uno script “runalgo” per l’invocazione e l’esecuzione degli algoritmi con i parametri desiderati. Rappresentazione matrice Per rappresentare una matrice è stata definita la seguente struttura: typedef struct { double ** val; int dim; }matrice; Dove: val matrice bidimensionale di double utilizzata per contenere i valori della matrice dim la dimensione della matrice, ossia il numero di elementi presenti in una riga/colonna Funzioni di utilità Tutte le funzioni di utilità degli algoritmi sono state inserite nel file matrice.c: InizializzaMatrice: lettura della matrice da file e allocazione di tale matrice. Viene controllato se la matrice ha una dimensione quadrata esci: uscita dal programma a seguito del verificarsi di un errore e stampa a schermo del messaggio di errore verificatosi array2Mat: trasformazione di array in una matrice array2MatFormatted: conversione di un array in una matrice suddivisa in sottoblocchi mat2Array: la matrice suddivisa i sottoblocchi viene convertita in un array per la spedizione subMat2subArray: conversione di una matrice (non suddivisa in sottoblocchi) in array prelevaPath: lettura del path della matrice inserito dell’utente stampamatrice: stampa a video del contenuto di una matrice stampaArray: stampa a video del contenuto di una array (in forma matriciale) stampaArrayWP: stampa a video dell’array (non in forma matriciale) getCoordX: determinazione della coordinata x dell’angolo in alto a sinistra del sottoblocco assegnato al processo 66 getCoordY: determinazione della coordinata y dell’angolo in alto a sinistra del sottoblocco assegnato al processo allocaMatrice: allocazione dello spazio alla matrice ProdottoMat: calcolo del prodotto tra due matrici SommaMat: calcolo della somma tra due matrici; il risultato viene salvato nella prima matrice A = A + B SommaMat2: calcolo della somma tra due matrici, viene creata una nuova matrice per contenere il risultato C = A + B SwapRow: per l’algoritmo Cannon, allineamento della matrice A per righe SwapCol: per l’algoritmo Cannom, allinemanto della matrice B per colonne newCoordY: calcolo della nuova coordinata Y del sottoblocco newCoordX: calocolo della nuova coordinata X del sottoblocco getRankRowDest: determinazione del processo a cui inviare il sottoblocco della matrice A getRankColDest: determinazione del processo a cui inviare il sottoblocco della matrice B getRankRowMit: individuazione del processo da cui ricevere il sottoblocco della matrice A getRankColMit: individuazione del processo da cui ricevere il sottoblocco della matrice B resettaMatrice: set di tutti gli elementi della matrice a zero checkNumProc: per l’algoritmo Processor Farm, controllo se il numero di processi sul quale si esegue l’algoritmo è sufficiente prelevaRiga: estrazione di una riga dalla matrice ProdVetMat: calcolo del prodotto vettore per matrice settaRiga: set degli elementi di una riga in base ai valori presenti nel vettore passato liberaMemoria: libero la spazio occupato dalla matrice buildMatrice: crea una matrice random della dimensione specificata scriviMatrice: scrittura della matrice nel file computabile: controllo se la matrice è computabile per gli algoritmi Matrix Matrix e Cannon savePerformance: scrittura dei tempi eseguiti dai vari processi in un file 67 Implementazione della funzione f di rallentamento A tutti gli algoritmi (compreso quello sequenziale) è possibile applicare una funzione f (detta di rallentamento) a tutti gli elementi A[i,j], per incrementare la granularità dei task paralleli. Come già spiegato, settando SLOW a 1 si applica la funzione e il numero di cicli che si vogliono eseguire sono specificati in RITARDO. Per realizzare la moltiplicazione gli algoritmi MatrixMatrix e Cannon utilizzano la funzione: // calcolo del prodotto tra due matrici matrice* ProdottoMat(matrice * a, matrice* b, int d){ double somma=0.0; int i,j,h; matrice *calC=(matrice*)malloc(sizeof(matrice)); calC->dim = d; calC->val = allocaMatrice(d); for(i=0; i<d;i++){ for(j=0;j<d; j++){ somma=0.0; for(h=0;h<d;h++){ if(SLOW){ int g, s=0; for(g=0;g<RITARDO;g++) s++; } somma=somma+(a->val[i][h]*b->val[h][j]); } calC->val[i][j]= (double) somma; } } return calC; } Invece, la funzione per la moltiplicazione utilizzata dall’algoritmo Processor Farm è la seguente: // calcolo del prodotto vettore - matrice void ProdVetMat(double *riga, matrice* b, double *riga_ris){ int dim = b->dim; double somma=0.0; int i,j; for(i=0; i<dim;i++){ somma = 0.0; for(j=0;j<dim;j++){ if(SLOW){ int g, s=0; for(g=0;g<RITARDO;g++) s++; } somma = somma + (riga[j] * b->val[j][i]); } riga_ris[i] = somma; } } 68 Manuale d’uso In questa sezione vengono fornite alcune informazioni sulla compilazione e sull’utilizzo degli programmi realizzati. Compilazione attraverso Makefile Il progetto realizzato include un makefile, il quale permette di compilare i file del progetto “lanciando” in comando ‘make’ dalla directory nella quale di trovano i file. Attraverso il comando ‘make all’ si esegue il “build” dei seguenti algoritmi: MatrixMatrix: Cannon: Processor Farm: Sequenziale: AlgoritmoMatrixMatrix.o AlgoritmoCannon.o AlgoritmoWorkerPool.o AlgoritmoSequenziale.o Viene creato l’eseguibile anche per il file creamatrix. Modifica header dei file Come già specificato, modificando gli header del file “matrice.h” è possibile variare il comportamento dei vari algoritmi. Gli header da settare opportunamente sono: SLOW RITARDO DEBUG TEMPO FROM_FILE AUTO DIM Per il loro significato si consulti la sezione delle “Caratteristiche del sistema”. 69 Utilizzo Per “lanciare” uno dei programmi è sufficiente invocare: ./runalgo.sh A video compare un messaggio che spiega l’utilità di tale script: Questo script invoca MPI con i parametri corretti Viene quindi chiesto su quanti processi si desidera eseguire l’algoritmo Quanti Processi? E quale tipo di algoritmo si vuole eseguire: inserire algo1 o algo2 oppure algo3 Quale algoritmo eseguire? [algo1 : algo2 : algo3] Infine si deve premere invio quando si è pronti per eseguire l’ algoritmo Enter per iniziare -> 70 Documentazione delle misure delle prestazioni Le misure delle prestazioni vengono riassunte in file che vengono generati automaticamente da una particolare funzione savePerformance presente nel file matrice.c. Tali pagine vengono divise in due sezioni: • Intestazione • Corpo Nell’intestazione, che è evidenziata da una cornice, vengono specificati il nome dell’algoritmo utilizzato, il numero di processi, se la funzione di rallentamento è attivata oppure no e la dimensione della matrice presa in considerazione. Nel corpo invece viene specificata la lista dei processi con assegnato il tempo impiegato da tale processo per effettuare le operazioni. Inoltre viene fornita una media del tempo impiegato tra tutti i processi. Qui sotto portiamo un esempio di pagina dove si è utilizzato l’algoritmo Processor Farm, 9 processi, la funzione di rallentamento attivata a 40 cicli e con una matrice di dimensione 1920 × 1920. 71 ############################################################################### Algoritmo utilizzato: Algoritmo Processor Farm Numero di processi 9 Funzione di rallentamento attivata: 40 ms Dimensione della matrici 1920 ############################################################################### Processo numero: 0 Tempo totale del root: 289.926316 Processo numero: 1 Tempo impiegato: 289.905706 Processo numero: 2 Tempo impiegato: 289.270183 Processo numero: 3 Tempo impiegato: 289.115312 Processo numero: 4 Tempo impiegato: 289.920823 Processo numero: 5 Tempo impiegato: 288.998096 Processo numero: 6 Tempo impiegato: 289.000793 Processo numero: 7 Tempo impiegato: 289.007451 Processo numero: 8 Tempo impiegato: 289.248978 Media tempo processi: 289.377073 72 Primitive MPI In questa sezione vengono presentate le principali primitive MPI che sono state utilizzate nel codice del progetto. Primitive gestione ambiente MPI MPI_Init( ) Inizializza l’esecuzione di un ambiente MPI. Questa funzione deve essere chiamata in ogni programma MPI prima di qualsiasi funzione MPI e solo una volta. MPI_Init può essere utilizzata per passare gli argomenti della linea di comando a tutti i processi. MPI_Init (&argc,&argv) MPI_Comm_size Determina il numero di processi nel gruppo associato a un comunicatore. In generale viene utilizzato con il comunicatore MPI_COMM_WORLD per determinare il numero di processi usati nella propria applicazione. MPI_Comm_size (comm,&size) MPI_Comm_rank Determina il rank dei processi all’interno di un comunicatore. Inizialmente ad ogni processo viene assegnato un numero intero compreso tra 0 e il numero di processi -1 dentro il comunicatore MPI_COMM_WORLD. Il valore del rank è unico per ogni processo nel comunicatore . MPI_Comm_rank(comm,&rank) MPI_Abort Termina tutti i processi MPI associati al comunicatore. MPI_Abort(comm,errorcode) MPI_Get_Processor_Name Ritorna il nome dei processori e la lunghezza dei nomi. MPI_Get_processor_name(&name,&resultlength) 73 MPI_Wtime Ritorna il tempo in secondi (in precisione double) di chiamata dei processori MPI_Wtime() MPI_Finalize Termina l’esecuzione dell’ambiente MPI. Questa funzione deve essere presente in ogni programma MPI. MPI_Finalize() Primitive comunicazione Point to Point MPI_Send( ) Procedura che invia un buffer di memoria ad una particolare destinazione. Operazione di send bloccante. La procedura ritorna il controllo solo quando il buffer dell’applicazione è libero per il riutilizzo. MPI_Send(buf, count, datatype, dest, tag, comm) • • • • • • buf : Indirizzo del buffer di dati che devono essere spediti count : numero di elementi di un certo tipo che devono essere inviati datatype : tipo di dati predefiniti in MPI dest : Rank del processo ricevente tag : intero non negativo che identifica il messaggio comm : contesto di comunicazione. Il comunicatore MPI_COMM_WORLD. predefinito è MPI_Recv( ) Procedura di ricezione di un buffer. Tale procedura è bloccante: ritorna il controllo solo quando l’intero buffer è stato ricevuto. MPI_Recv (buf, count, datatype, source, tag, com, status) • • • • buf : Indirizzo del buffer dove verranno ricevuti i dati count : numero di elementi di un certo tipo che devono essere ricevuti datatype : tipo di dati predefiniti in MPI. source : rank del processo sorgente 74 • • • tag : intero non negativo che identifica il messaggio comm : contesto di comunicazione. Il comunicatore MPI_COMM_WORLD. status: struttura dati che contiene la sorgente e il tag del messaggio. predefinito è MPI_Sendrecv_replace MPI_Sendrecv_replace esegue una send e una receive bloccante. Lo stesso buffer è utilizzato sia per il send che per il receive così che il messaggio inviato è sostituito dal messaggio ricevuto. MPI_Sendrecv_replace(buf,count,datatype,dest,sendtag,source,recvtag,comm, status) • • • • • • • • • buf : indirizzo del buffer inviato e successivamente ricevuto count : numero di elementi di un certo tipo che devono essere ricevuti datatype : tipo del dato dest : rank del processo ricevente sendtag : intero non negativo che identifica il messaggio di invio source : rank del processo sorgente recvtag : intero non negativo che identifica il messaggio di ricezione comm: comunicatore status : struttura dati che contiene la sorgente e il tag del messaggio Collettive di comunicazione MPI_Barrier Crea una barriera di sincronizzazione in un gruppo. Ogni processo quando ha raggiunto la barriera si blocca finché tutti gli processi del comunicatore hanno raggiunto la barriera. MPI_Barrier(Comm) MPI_Bcast Invio di un messaggio dal processo root a tutti gli altri processi presenti nel comunicatore. MPI_Bcast (buf, count, datatype, root, comm) • • • • • buf : indirizzo del buffer inviato count : numero di elementi di un certo tipo che devono essere ricevuti datatype : tipo dei dati inviati root : rank del processo sorgente comm : comunicatore 75 MPI_Scatter Distribuisce distinti messaggi da un singolo processo sorgente ad ogni processo presente nel comunicatore MPI_Scatter(sendbuf, sendcount,sendtype,recvbuf,revcount,recvtype,root,comm) • • • • • • • • sendbuf: indirizzo del send buffer sendcount: numero elementi inviati ad ogni processo sendtype: tipo degli elementi presenti nel send buffer recvbuf: indirizzo del receive buffer recvcount: numero elementi nel receive buffer recvtype: : tipo degli elementi presenti nel receive buffer root: rank del processo che invia i dati comm: comunicatore MPI_Gather Riunisce i messaggi distinti di ogni processo nel comunicatore in un singolo processo. Tale routine è l’operazione inversa di MPI_Scatter. MPI_Gather(sendbuf, sendcount,sendtype,recvbuf,revcount,recvtype,root,comm) • • • • • • • • sendbuf: indirizzo iniziale del send buffer sendcount: numero elementi nel send buffer sendtype: tipo degli elementi presenti nel send buffer recvbuf: indirizzo del receive buffer recvcount: numero elementi per ogni singolo receive buffer recvtype: : tipo degli elementi presenti nel receive buffer root: rank del processo che raccoglie i dati comm: comunicatore MPI_Allgather Concatenazione dei dati su tutti i processi presenti nel comunicatore. Ogni processo nel gruppo effettua prima un’operazione di broadcasting del proprio dato a tutti i processi presenti nel gruppo, poi effettua un’operazione MPI_Gather per concatenare tutti i dati. MPI_Gather(sendbuf, sendcount,sendtype,recvbuf,revcount,recvtype,comm) • • • • • • sendbuf: indirizzo iniziale del send buffer sendcount: numero elementi nel send buffer sendtype: tipo degli elementi presenti nel send buffer recvbuf: indirizzo del receive buffer recvcount: numero elementi ricevuti da ogni processo recvtype: : tipo degli elementi presenti nel receive buffer 76 • comm: comunicatore Primitive per la gestione dei comunicatori MPI_Comm_Split Partiziona il comunicatore associato in sotto-comunicatori disgiunti tra loro. MPI_Comm_Split(comm,color, key, newcomm) • • • • comm.: comunicatore da partizionare color: controllo dei sottoinsiemi assegnati key: controllo dei rank assegnati newcomm: nuovo comunicatore Tipi Predefiniti in MPI Nella tabella riassumiamo i principali tipi definiti in MPI confrontandoli con i tipi predefiniti del linguaggio C. Tipo dati MPI MPI_CHAR MPI_INT : MPI_DOUBLE MPI_LONG MPI_SHORT MPI_UNSIGNED_CHAR MPI_UNSIGNED_SHORT MPI_UNSIGNED MPI_UNSIGNED_LONG MPI_FLOAT MPI_LONG_DOUBLE Tipo dati C signed char signed int double signed long int signed short int unsigned char unsigned short int unsigned int unsigned long int float Long double 77 Risultati test Di seguito vengono riportati tutti i risultati dei test effettuati. Per ogni algoritmo viene specificato la dimensione delle matrici quadrate A e B, il numero dei processi sui quali si è eseguito l’algoritmo, il numero di cicli eseguiti dalla funzione di rallentamento ed il tempo impiegato dal processo che ha accesso alle matrici A, B e C (processo con rank 0). Algoritmo Matrix Matrix Dimensione matrice 384 Ritardo 0 5 10 40 768 0 5 10 40 Numero processi Tempo medio root 4 0.929744 9 0.814007 16 0.979227 4 1.175010 9 1.042601 16 0.872501 4 1.572951 9 1.019708 16 1.019708 4 3.034295 9 1.753060 16 1.456334 4 5.485505 9 3.988348 16 3.241951 4 8.131361 9 5.192860 16 4.293923 4 11.308421 9 7.691423 16 5.690817 4 23.677962 9 12.240944 16 11.187293 78 1152 0 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 18.897312 14.488862 9.523324 27.892869 14.418259 15.640759 38.955858 38.158344 18.195319 80.652735 39.478073 39.620218 98.851678 70.104640 45.202001 160.31033 122.023573 73.313982 325.469852 189.836414 97.380354 390.462323 173.413946 192.205315 Ritardo Numero processi Tempo medio 0 4 9 16 4 9 16 4 9 16 4 9 16 1.033331 0.845044 0.786103 1.323578 0.977513 0.923690 1.621842 1.072127 1.206426 3.210334 1.731938 1.354892 5 10 40 1920 0 5 10 40 Algoritmo Cannon Dimensione matrice 384 5 10 40 79 768 0 5 10 40 1152 0 5 10 40 1920 0 5 10 40 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 7.685075 4.166739 3.559943 8.963937 5.150870 4.271693 12.003956 22.007625 5.501585 25.334788 11.769905 8.183016 20.524266 11.854486 9.380467 28.261071 15.149448 11.750840 42.015027 34.130077 23.439799 84.131099 38.468620 33.750748 100.070636 69.150346 36.066406 162.561834 121.151701 64.537436 322.829983 215.558784 93.485949 398.580245 174.680116 186.814245 80 Algoritmo Processor Farm Dimensione matrice 384 Ritardo Numero processi Tempo medio 0 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 4 9 16 1.013475 0.790527 1.163005 1.833057 1.060500 0.945750 2.226456 1.221536 1.064294 3.991906 1.914332 1.781579 8.016242 3.711925 3.351492 12.444352 6.634593 4.914889 17.815742 8.692787 5.510070 31.391664 13.784691 8.992444 28.760064 14.044216 10.144463 46.567995 23.288855 15.677090 68.100770 28.066548 17.275973 109.154053 45.902152 29.362681 296.034891 121.558528 71.804350 557.816135 198.322609 110.366643 5 10 40 768 0 5 10 40 1152 0 5 10 40 1920 0 5 81 10 40 4 9 16 4 9 16 564.184569 247.105293 127.070426 743.951466 289.926316 168.583426 Algoritmo sequenziale Dimensione Matrice 384 768 1152 1920 Ritardo Tempo 0 5 10 40 0 5 10 40 0 5 10 40 0 5 10 40 1.874726 3.141316 4.691039 12.002368 17.945671 26.697897 39.426890 92.198587 70.770948 122.784403 153.114975 328.350799 881.414489 1360.528847 1548.154627 2308.199205 82 Conclusione Tramite gli algoritmi da noi implementati abbiamo potuto constatare che ilo comportamento degli algoritmi Matrix Matrix e Cannon si equivalgono in termini di tempo, efficienza e speedup. L’algoritmo Matrix Matrix soffre però di un forte sbilanciamento in presenza di in costo computazionale elevato e quindi non applicabile a situazioni con tale carico. In presenza di basso costo computazionale gli algoritmi Matrix Matrix e Cannon sono migliori a quelle del Processor Farm che però riduce tale gap con l’aumento del numero computazioni e di processi. In generale quindi, in presenza di un problema computazionalmente elevato e quindi il caso in cui si adottano gli algoritmi paralleli, l’algoritmo Processor Farm risulta essere il migliore sia in termini di tempo che in termini di bilanciamento del carico. Nella lettura dei risultati deve essere tenuto in considerazione il fatto che si è utilizzato un cluster le cui prestazioni possono essere state “influenzate” dal carico variabile della rete. 83 Appendice Grafici numero processori – tempo Matrice 384 Matrice 384 − Funzione rallentamento attivata a 5 cicli 2 Matrix Multiply Cannon Algorithm Processor Farm 1.8 1.6 1.4 Tempo (sec) 1.2 1 0.8 0.6 0.4 0.2 0 4 6 8 10 Numero Processi (np) 12 14 16 84 Matrice 384 − Funzione rallentamento attivata a 10 cicli 3 Matrix Multiply Cannon Algorithm Processor Farm 2.5 Tempo (sec) 2 1.5 1 0.5 0 4 6 8 10 Numero Processi (np) 12 14 16 Matrice 384 − Funzione rallentamento attivata a 40 cicli 5 Matrix Multiply Cannon Algorithm Processor Farm 4.5 4 3.5 Tempo (sec) 3 2.5 2 1.5 1 0.5 0 4 6 8 10 Numero Processi (np) 12 14 16 85 Matrice 768 Matrice 768 − Funzione rallentamemto disattivata 9 Matrix Multiply Cannon Algorithm Processor Farm 8 7 Tempo (sec) 6 5 4 3 2 1 0 4 6 8 10 12 Numero Processi (np) 14 16 Matrice 768 − Funzione rallentamemto attivata a 5 cicli 13 Matrix Multiply Cannon Algorithm Processor Farm 12 11 Tempo (sec) 10 9 8 7 6 5 4 3 4 6 8 10 12 Numero Processi (np) 14 16 86 Matrice 768 − Funzione rallentamemto attivata a 10 cicli 18 Matrix Multiply Cannon Algorithm Processor Farm 16 Tempo (sec) 14 12 10 8 6 4 4 6 8 10 12 Numero Processi (np) 14 16 Matrice 768 − Funzione rallentamemto attivata a 40 cicli Matrix Multiply Cannon Algorithm Processor Farm 30 Tempo (sec) 25 20 15 10 4 6 8 10 12 Numero Processi (np) 14 16 87 Matrice 1152 Matrice 1152 − Funzione rallentamento disattivata 40 Matrix Multiply Cannon Algorithm Processor Farm 35 30 Tempo (sec) 25 20 15 10 5 0 4 6 8 10 Numero Processi (np) 12 14 16 Matrice 1152 − Funzione rallentamento attivata a 5 cicli 50 Matrix Multiply Cannon Algorithm Processor Farm 45 40 35 Tempo (sec) 30 25 20 15 10 5 0 4 6 8 10 Numero Processi (np) 12 14 16 88 Matrice 1152 − Funzione rallentamento attivata 10 cicli Matrix Multiply Cannon Algorithm Processor Farm 70 60 Tempo (sec) 50 40 30 20 10 0 4 6 8 10 Numero Processi (np) 12 14 16 Matrice 1152 − Funzione rallentamento attivata 40 cicli 120 Matrix Multiply Cannon Algorithm Processor Farm 110 100 90 Tempo (sec) 80 70 60 50 40 30 20 4 6 8 10 Numero Processi (np) 12 14 16 89 Matrice 1920 Matrice 1920 − Funzione rallentamento disattivata 300 Matrix Multiply Cannon Algorithm Processor Farm 250 Tempo (sec) 200 150 100 50 0 4 6 8 10 12 Numero Processi (np) 14 16 Matrice 1920 − Funzione rallentamemto attivata a 5 cicli 600 Matrix Multiply Cannon Algorithm Processor Farm 500 Tempo (sec) 400 300 200 100 0 4 6 8 10 12 Numero Processi (np) 14 16 Matrice 1920 − Funzione rallentamemto attivata a 10 cicli 600 Matrix Multiply Cannon Algorithm Processor Farm 500 Tempo (sec) 400 300 200 100 0 4 6 8 10 12 Numero Processi (np) 14 16 90 Matrice 1920 − Funzione rallentamemto attivata a 40 cicli Matrix Multiply Cannon Algorithm Processor Farm 700 600 Tempo (sec) 500 400 300 200 100 0 4 6 8 10 12 Numero Processi (np) 14 16 Grafici ritardo – tempo Matrice 384 Matrice 384 − Numero Processi 4 5 Matrix Multiply Cannon Algorithm Processor Farm 4.5 4 3.5 Tempo (sec) 3 2.5 2 1.5 1 0.5 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 91 Matrice 384 − Numero Processi 9 Matrix Multiply Cannon Algorithm Processor Farm 2 Tempo (sec) 1.5 1 0.5 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 Matrice 384 − Numero Processi 16 2.5 Matrix Multiply Cannon Algorithm Processor Farm 2 Tempo (sec) 1.5 1 0.5 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 92 Matrice 768 Matrice 768 − Numero processi 9 14 Tempo (sec) 12 10 8 Matrix Multiply Cannon Algorithm 6 Processor Farm 4 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 30 35 40 Matrice 768 − Numero processi 16 12 11 Matrix Multiply Cannon Algorithm 10 Processor Farm Tempo (sec) 9 8 7 6 5 4 3 0 5 10 15 20 25 Ritardo (numero cicli) 93 Matrice 1152 Matrice 1152 − Numero Processi 4 150 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 100 50 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 Matrice 1152 − Numero Processi 9 80 Matrix Multiply Cannon Algorithm Processor Farm 70 60 Tempo (sec) 50 40 30 20 10 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 94 Matrice 1152 − Numero Processi 16 50 Matrix Multiply Cannon Algorithm Processor Farm 45 40 35 Tempo (sec) 30 25 20 15 10 5 0 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 Matrice 1920 Matrice 1920 − Numero processi 4 700 Tempo (sec) 600 500 400 300 Matrix Multiply Cannon Algorithm 200 Processor Farm 100 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 95 Matrice 1920 − Numero processi 9 300 Tempo (sec) 250 200 150 Matrix Multiply Cannon Algorithm Processor Farm 100 0 5 10 15 20 25 Ritardo (numero cicli) 30 35 40 30 35 40 Matrice 1920 − Numero processi 16 200 Matrix Multiply 180 Cannon Algorithm Processor Farm 160 Tempo (sec) 140 120 100 80 60 40 20 0 0 5 10 15 20 25 Ritardo (numero cicli) 96 Grafici dimensione matrice – tempo Funzione di rallentamento disattivata Funzione rallentamento disattivata − numero processi 4 300 Matrix Multiply 250 Cannon Algorithm Processor Farm Tempo (sec) 200 150 100 50 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 1800 2000 Funzione rallentamento disattivata − numero processi 9 120 Matrix Multiply Cannon Algorithm 100 Processor Farm Tempo (sec) 80 60 40 20 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 97 Funzione rallentamento disattivata − numero processi 16 70 Matrix Multiply Cannon Algorithm 60 Processor Farm Tempo (sec) 50 40 30 20 10 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Funzione di rallentamento attivata : 5 cicli Funzione rallentamento attivata a 5 cicli − numero processi 4 450 Matrix Multiply 400 Cannon Algorithm Tempo (sec) 350 Processor Farm 300 250 200 150 100 50 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Funzione rallentamento attivata a 5 cicli − numero processi 9 200 180 Matrix Multiply 160 Cannon Algorithm Processor Farm Tempo (sec) 140 120 100 80 60 40 20 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 98 Funzione rallentamento attivata a 5 cicli − numero processi 16 100 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 80 60 40 20 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Funzione di rallentamento attivata : 10 cicli Funzione rallentamento attivata a 10 cicli − numero processi 4 500 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 400 300 200 100 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Funzione rallentamento attivata a 10 cicli − numero processi 9 250 Matrix Multiply Cannon Algorithm Tempo (sec) 200 Processor Farm 150 100 50 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 99 Funzione rallentamento attivata a 10 cicli − numero processi 16 120 Matrix Multiply Cannon Algorithm Tempo (sec) 100 Processor Farm 80 60 40 20 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Funzione di rallentamento attivata : 40 cicli Funzione rallentamento attivata a 40 cicli − numero processi 4 700 Matrix Multiply 600 Cannon Algorithm Processor Farm Tempo (sec) 500 400 300 200 100 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Funzione rallentamento attivata a 40 cicli − numero processi 9 Matrix Multiply Cannon Algorithm 250 Processor Farm Tempo (sec) 200 150 100 50 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 100 Funzione rallentamento attivata a 40 cicli − numero processi 16 200 180 Matrix Multiply Cannon Algorithm 160 Processor Farm Tempo (sec) 140 120 100 80 60 40 20 0 400 600 800 1000 1200 1400 Dimensione matrice 1600 1800 2000 Grafici numero processi – speedup Matrice 384 Calcolo Speedup − Funzione disattivata − Matrice: 384 X 384 2.5 Speedup (Sp(n)) 2 Matrix Multiply Cannon Algorithm 1.5 1 Processor Farm 4 6 8 10 12 Numero Processori (np) 14 16 101 Calcolo Speedup − Funzione attivata 5 cicli − Matrice: 384 X 384 4 3.5 Speedup (Sp(n)) 3 2.5 Matrix Multiply Cannon Algorithm Processor Farm 2 1.5 1 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Speedup − Funzione attivata 10 cicli − Matrice: 384 X 384 6 5.5 5 Speedup (Sp(n)) 4.5 4 3.5 3 Matrix Multiply Cannon Algorithm 2.5 Processor Farm 2 1.5 1 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Speedup − Funzione attivata 40 cicli − Matrice: 384 X 384 10 9 8 Speedup (Sp(n)) 7 6 5 Matrix Multiply Cannon Algorithm 4 Processor Farm 3 2 1 4 6 8 10 12 Numero Processori (np) 14 16 102 Matrice 768 Calcolo Speedup − Funzione disattivata − Matrice: 768 X 768 6 5 Speedup (Sp(n)) 4 Matrix Multiply 3 Cannon Algorithm Processor Farm 2 1 0 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Speedup − Funzione attivata 5 cicli − Matrice: 768 X 768 7 6 Speedup (Sp(n)) 5 4 Matrix Multiply 3 Cannon Algorithm Processor Farm 2 1 0 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Speedup − Funzione attivata 10 cicli − Matrice: 768 X 768 7 6 Speedup (Sp(n)) 5 4 Matrix Multiply 3 Cannon Algorithm Processor Farm 2 1 0 4 6 8 10 12 Numero Processori (np) 14 16 103 Calcolo Speedup − Funzione attivata 40 cicli − Matrice: 768 X 768 12 10 Speedup (Sp(n)) 8 6 Matrix Multiply Cannon Algorithm 4 Processor Farm 2 0 4 6 8 10 12 Numero Processori (np) 14 16 Matrice 1152 Calcolo Speedup − Funzione F disattivata − Matrice: 1152 X 1152 12 Matrix Multiply Cannon Algorithm Processor Farm 11 10 Speedup (sp(n)) 9 8 7 6 5 4 3 2 1 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Speedup − Funzione F attivata : 5 − Matrice: 1152 X 1152 12 Matrix Multiply 11 Cannon Algorithm 10 Processor Farm Speedup (sp(n)) 9 8 7 6 5 4 3 2 1 4 6 8 10 12 Numero Processori (np) 14 16 104 Calcolo Speedup − Funzione F attivata : 10 − Matrice: 1152 X 1152 12 Matrix Multiply Cannon Algorithm Processor Farm 11 10 Speedup (sp(n)) 9 8 7 6 5 4 3 2 1 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Speedup − Funzione F attivata : 40 − Matrice: 1152 X 1152 12 11 10 Speedup (sp(n)) 9 8 7 6 5 Matrix Multiply 4 Cannon Algorithm Processor Farm 3 2 1 4 6 8 10 12 Numero Processori (np) 14 16 Matrice 1920 Calcolo Speedup − Funzione F disattivata − Matrice: 1920 X 1920 25 Matrix Multiply Cannon Algorithm Speedup (sp(n)) 20 Processor Farm 15 10 5 2 4 6 8 10 Numero Processori (np) 12 14 16 105 Calcolo Speedup − Funzione F attivata : 5 − Matrice: 1920 X 1920 25 Matrix Multiply Cannon Algorithm Speedup (sp(n)) 20 Processor Farm 15 10 5 2 4 6 8 10 Numero Processori (np) 12 14 16 Calcolo Speedup − Funzione F attivata : 10 − Matrice: 1920 X 1920 25 Matrix Multiply Cannon Algorithm Processor Farm Speedup (sp(n)) 20 15 10 5 4 6 8 10 12 Numero Processori (np) 14 16 106 Grafici numero processori – efficienza Matrice 384 Calcolo Efficenza − Funzione F disattivata : − Matrice: 384 X 384 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 Numero Processori (np) 12 14 16 Calcolo Efficenza − Funzione F attivata : 5 − Matrice: 384 X 384 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 Numero Processori (np) 12 14 16 107 Calcolo Efficenza − Funzione F attivata : 10 − Matrice: 384 X 384 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 Numero Processori (np) 12 14 16 Calcolo Efficenza − Funzione F attivata : 40 − Matrice: 384 X 384 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 Numero Processori (np) 12 14 16 108 Matrice 768 Calcolo Efficienza − Funzione disattivata − Matrice: 768 X 768 1 0.9 Matrix Multiply Cannon Algorithm 0.8 Processor Farm Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Efficienza − Funzione attivata 5 cicli − Matrice: 768 X 768 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Efficienza − Funzione attivata 10 cicli − Matrice: 768 X 768 1 0.9 Matrix Multiply 0.8 Cannon Algorithm Processor Farm Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 12 Numero Processori (np) 14 16 109 Calcolo Efficienza − Funzione attivata 40 cicli − Matrice: 768 X 768 1 0.9 Efficenza (E(n)) 0.8 0.7 Matrix Multiply 0.6 Cannon Algorithm Processor Farm 0.5 0.4 0.3 4 6 8 10 12 Numero Processori (np) 14 16 Matrice 1152 Calcolo Efficenza − Funzione F disattivata Matrice: 1152 X 1152 1 Matrix Multiply Cannon Algorithm Processor Farm 0.9 0.8 Efficenza (E(n)) 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 4 6 8 10 Numero Processori (np) 12 14 16 110 Calcolo Efficenza − Funzione F attivata : 5 − Matrice: 1152 X 1152 1.4 Matrix Multiply Cannon Algorithm Processor Farm 1.2 Efficenza (E(n)) 1 0.8 0.6 0.4 0.2 0 4 6 8 10 Numero Processori (np) 12 14 16 Calcolo Efficenza − Funzione F attivata : 5 − Matrice: 1152 X 1152 1.4 Matrix Multiply Cannon Algorithm Processor Farm 1.2 Efficenza (E(n)) 1 0.8 0.6 0.4 0.2 0 4 6 8 10 Numero Processori (np) 12 14 16 111 Calcolo Efficenza − Funzione F attivata : 5 − Matrice: 1152 X 1152 1.4 Matrix Multiply Cannon Algorithm Processor Farm 1.2 Efficenza (E(n)) 1 0.8 0.6 0.4 0.2 0 4 6 8 10 Numero Processori (np) 12 14 16 Matrice 1920 Calcolo Efficenza − Funzione F disattivata − Matrice: 1920 X 1920 2.5 Matrix Multiply Cannon Algorithm Efficenza (E(n)) 2 Processor Farm 1.5 1 0.5 0 4 6 8 10 12 Numero Processori (np) 14 16 112 Calcolo Efficienza − Funzione arrivata 5 cicli − Matrice: 1920 X 1920 2.5 Matrix Multiply Cannon Algorithm Processor Farm Efficenza (E(n)) 2 1.5 1 0.5 0 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Efficienza − Funzione arrivata 10 cicli − Matrice: 1920 X 1920 2.5 Matrix Multiply 2 Cannon Algorithm Efficenza (E(n)) Processor Farm 1.5 1 0.5 0 4 6 8 10 12 Numero Processori (np) 14 16 Calcolo Efficienza − Funzione arrivata 40 cicli − Matrice: 1920 X 1920 2 1.8 Matrix Multiply Cannon Algorithm 1.6 Processor Farm Efficenza (E(n)) 1.4 1.2 1 0.8 0.6 0.4 0.2 0 4 6 8 10 12 Numero Processori (np) 14 16 113 Grafici bilanciamento del carico Matrice 384 Matrice 384 − Funzione rallentamento disattivata − Numero processi = 4 1.2 1.1 Tempo (sec) 1 0.9 0.8 Matrix Multiply Cannon Algorithm Processor Farm 0.7 0.6 0.5 0 0.5 1 1.5 Rank processo 2 2.5 3 Matrice 384 − Funzione rallentamento 40 cicli − Numero processi = 4 4.5 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 4 3.5 3 2.5 0 0.5 1 1.5 Rank processo 2 2.5 3 Matrice 384 − Funzione rallentamento disattivata − Numero processi = 16 1.4 Matrix Multiply Cannon Algorithm Processor Farm 1.3 1.2 Tempo (sec) 1.1 1 0.9 0.8 0.7 0.6 0.5 0.4 0 5 10 15 Rank processo 114 Matrice 384 − Funzione rallentamento 40 cicli − Numero processi = 16 2 Matrix Multiply Cannon Algorithm Processor Farm 1.9 1.8 Tempo (sec) 1.7 1.6 1.5 1.4 1.3 1.2 1.1 1 0 5 10 15 Rank processo Matrice 1920 Matrice 1920 − Funzione rallentamento disattivata − Numero processi = 4 300 Tempo (sec) 250 Matrix Multiply Cannon Algorithm Processor Farm 200 150 100 0 0.5 1 1.5 Rank processo 2 2.5 3 Matrice 1920 − Funzione rallentamento 40 cicli − Numero processi = 4 750 700 Matrix Multiply Cannon Algorithm Processor Farm Tempo (sec) 650 600 550 500 450 400 0 0.5 1 1.5 Rank processo 2 2.5 3 115 Matrice 1920 − Funzione rallentamento disattivata − Numero processi = 16 80 70 Tempo (sec) 60 Matrix Multiply Cannon Algorithm Processor Farm 50 40 30 20 0 5 10 15 Rank processo Matrice 1920 − Funzione rallentamento 40 cicli − Numero processi = 16 200 180 Tempo (sec) 160 Matrix Multiply Cannon Algorithm Processor Farm 140 120 100 80 0 5 10 15 Rank processo 116