Corso di Architettura dei calcolatori e parallelismo anno 2007/2008 Esericizi Marco Trentini matricola 062275 Caratteristiche del Cluster: SCILX: beowulf cluster of Grandi Attrezzature Consortium 20 dual AMD Athlon MP 2000+ SMP nodes (totally 40 CPUs) 1 GByte RAM per node (totally 20 GBytes) NAS Dell Powervault 725N with about 300 GByte of storage 10/100 MBit Ethernet service network Dolphin Interconnect Solutions D334/D335 for High Performance Computing (v3.3.0.2) Operative System: Linux Debian Lenny Compilers: GCC-4.x, GCC-3.x, GFORTRAN, G77-3.x, PGI 7.0 (require license) Shells/Interpreters: BASH, KSH, TCSH, PERL, JAVA-1.6, PYTHON-2.4, PYTHON-2.5 Softwares: MP-MPICH-1.5.0 , OPENMPI-1.2.6 , NMPI-1.3.1 Libraries: ACML-3.6.1 (GNU, GFORTRAN, PGI), FFT3, FFTW, BLACS, BLAS, SCALAPACK, GSL Support to OpenMP Esercizio 1: utilizzo di sistemi a code di famiglia Torque/PBS. (20 min) Tutti gli esercizi seguenti richiederanno le conoscenze acquisite in questo esercizio per poter eseguire i propri programmi sul cluster in adozione. (a) Sottomettete al cluster scilx il comando “uname –a” preparando un opportuno script di sottomissione. Monitorare l’attività del proprio processo. (b) Si provino a modificare i parametri dello script di sottomissione valutando gli effetti ottenuti. #!/bin/bash #PBS -V #PBS -k eo #PBS -N HW_openmpi #PBS -l nodes=10:ppn=1 #PBS -l walltime=00:30:00 echo "Start @" `date` echo "The nodefile is ${PBS_NODEFILE} and it contains:" cat ${PBS_NODEFILE} echo "" source /usr/local/Modules/3.2.5/init/bash module load OPENMPI-1.2.6-GNU cd $PBS_O_WORKDIR NUM_NODES=`cat $PBS_NODEFILE | wc -l` #mpdboot -n ${NUM_NODES} -f ${PBS_NODEFILE} -r rsh -1 mpirun -np ${NUM_NODES} uname -a #mpirun -np ${NUM_NODES} ./first_mpi #mpirun -np ${NUM_NODES} ./trapezoid_mpi_sync #mpirun -np ${NUM_NODES} ./trapezoid_mpi_async #mpirun -np ${NUM_NODES} ./trapezoid_mpi_master # #mpdallexit echo "End @" `date` exit 0 esempio di output: studentipar4@scilx:~/ex$ qsub test_mpi_eth.sh 10711.scilx.disco.unimib.it studentipar4@scilx:~$ cat HW_openmpi.o10711 Start @ Sat May 31 11:14:28 CEST 2008 The nodefile is /var/spool/torque/aux//10711.scilx.disco.unimib.it and it contains: node08 node06 node19 node18 node10 node17 node04 node02 node01 node03 Linux node08 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node03 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node01 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node02 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node10 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node04 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node18 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node17 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node19 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux Linux node06 2.6.22-3-686 #1 SMP Sun Feb 10 20:20:49 UTC 2008 i686 GNU/Linux End @ Sat May 31 11:14:28 CEST 2008 Esercizio 2: Hello World (40 min) Scrivere un semplice programma in linguaggio C che faccia si che ogni nodo della computazione stampi il messaggio “Hi, I’m node X on Y” dove X è l’identificativo del nodo mentre Y è il numero totale di processi coinvolti nella computazione. #include "mpi.h" #include <stdio.h> int main(int argc,char *argv[]){ int numtasks, rank, rc, resultlen; char name[100]; double time; rc = MPI_Init(&argc,&argv); if (rc != MPI_SUCCESS) { printf ("Error starting MPI program. Terminating.\n"); MPI_Abort(MPI_COMM_WORLD, rc); } MPI_Comm_size(MPI_COMM_WORLD,&numtasks); MPI_Comm_rank(MPI_COMM_WORLD,&rank); MPI_Get_processor_name(name,&resultlen); time=MPI_Wtime(); printf ("Number of tasks=%d My rank=%d on %s\n", numtasks,rank,name); printf ("Time for task=%d : %fl\n",rank,time); sleep(2); time=MPI_Wtime(); printf ("Time for task=%d afer about 2 second : %fl\n",rank,time); /******* do some work *******/ MPI_Finalize(); } esempio di output: studentipar4@scilx:~$ cat HW_openmpi.o10712 Start @ Sat May 31 11:17:14 CEST 2008 The nodefile is /var/spool/torque/aux//10712.scilx.disco.unimib.it and it contains: node08 node06 node19 node18 node10 node17 node04 node02 node01 node03 Number of tasks=10 My rank=1 on node06 Time for task=1 : 1212225434.511091l Number of tasks=10 My rank=2 on node19 Time for task=2 : 1212225434.992027l Number of tasks=10 My rank=6 on node04 Time for task=6 : 1212225434.564736l Number of tasks=10 My rank=5 on node17 Time for task=5 : 1212225434.659058l Number of tasks=10 My rank=7 on node02 Number of tasks=10 My rank=9 on node03 Time for task=9 : 1212225434.569362l Number of tasks=10 My rank=0 on node08 Number of tasks=10 My rank=4 on node10 Time for task=4 : 1212225434.502109l Number of tasks=10 My rank=3 on node18 Time for task=3 : 1212225435.181055l Number of tasks=10 My rank=8 on node01 Time for task=8 : 1212225434.862988l Time for task=7 : 1212225434.775596l Time for task=0 : 1212225434.719827l Time for task=1 afer about 2 second : 1212225436.511226l Time for task=2 afer about 2 second : 1212225436.992183l Time for task=6 afer about 2 second : 1212225436.564882l Time for task=5 afer about 2 second : 1212225436.659205l Time for task=7 afer about 2 second : 1212225436.775974l Time for task=9 afer about 2 second : 1212225436.569501l Time for task=0 afer about 2 second : 1212225436.720140l Time for task=4 afer about 2 second : 1212225436.502250l Time for task=3 afer about 2 second : 1212225437.181198l Time for task=8 afer about 2 second : 1212225436.863137l End @ Sat May 31 11:17:17 CEST 2008 Esercizio 3: Integrazione di funzioni tramite il metodo dei trapezi. (1h) Il programma tapezoid.c contiene un’implementazione del metodo dei trapezi generalizzato: un semplice metodo che permette di calcolare il valore dell’integrale di una funzione in un dato intervallo. (a) Si parallelizzi il codice in questione utilizzando comunicazioni punto a punto per accumulare le porzioni di calcolo valutate dalle partizioni del dominio della funzione. (e.g. s_node0>s_node1->...->s_nodeN->s_node0->print risultato). Valutate lo speedup ed isoefficienza dell’implementazione parallela rispetto alla versione sequenziale del codice. Misurare le tempistiche di programma utilizzando le apposite chiamate MPI. (b) Modificate l’implementazione parallela in modo da sovrapporre i tempi di calcolo a quelli di comunicazioni per la propagazione dei risultati parziali. Utilizzate le chiamate MPI_isend, MPI_irecv. Come variano le performance rispetto al punto (a)? Versione Seriale #include <stdio.h> #include <math.h> #include <sys/time.h> #include <time.h> typedef double precision; #define START 1 #define END 4 #define NBROFTRAP 10000000000ULL /*#define FUNCTION sin*/ #define FUNCTION myfunc2 precision myfunc1( precision x){ return x; } precision myfunc2( precision x){ return 3; } precision trapezoid( precision a, precision b, precision h, unsigned int iter){ precision sum; precision x; unsigned int i; sum=0; x=a; for(i=0;i<iter-1;i++){ sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(x+h))); x=x+h; //printf("partial sum of %d trapezoid=%lf \n",i+1,sum); } sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(b))); return sum; } precision a; precision h; precision b; precision n; struct timeval start,end; int main(int argc,char *argv[]){ gettimeofday(&start,NULL); #if 0 printf("Enter value for a: "); scanf("%lf", &a); printf("Enter value for b: "); scanf("%lf", &b); printf("Enter number of rectangles: "); scanf("%d", &n); #endif a=START; b=END; n=NBROFTRAP; h=(b-a)/n; #if 0 x=a; for(i=0;i<n;i++){ f=FUNCTION(x+(h*i)); printf("x=%lf f(x)=%lf \n",x+(h*i),f); } #endif printf("The value of the integral is: %lf\n", trapezoid(a,b,h,n)); gettimeofday(&end,NULL); end.tv_sec=end.tv_sec-start.tv_sec; end.tv_usec=end.tv_usec-start.tv_usec; if(end.tv_usec<0){ end.tv_usec=end.tv_usec+1000000; end.tv_sec--; } printf("Time spent calculating integral : %d sec %d usec\n",end.tv_sec,end.tv_usec); return 0; } Versione (a) #include "mpi.h" #include <stdio.h> #include <math.h> #include <sys/time.h> #include <time.h> typedef double precision; /*#define FUNCTION sin*/ #define FUNCTION myfunc2 precision myfunc1( precision x){ return x; } precision myfunc2( precision x){ return 3; } precision trapezoid( precision a, precision b, precision h, unsigned int iter){ precision sum; precision x; unsigned int i; sum=0; x=a; for(i=0;i<iter-1;i++){ sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(x+h))); x=x+h; //printf("partial sum of %d trapezoid=%lf \n",i+1,sum); } sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(b))); return sum; } check_mpi(int rc){ if (rc != MPI_SUCCESS) { printf ("Error starting MPI program. Terminating.\n"); MPI_Abort(MPI_COMM_WORLD, rc); } } #define START 1 #define END 4 #define NBROFTRAP 1000000000ULL int main(int argc,char *argv[]){ int numtasks, rank, rc, resultlen; char name[100]; double time_start, time_end; precision a=START; precision b=END; precision h; precision sum; precision sum_app; double n=NBROFTRAP; unsigned int iter; int dest, source, tag , count; MPI_Status Stat; struct timeval start,end; gettimeofday(&start,NULL); h=(b-a)/n; sum=0; time_start=MPI_Wtime(); printf ("Time start for task=%d : %fl\n",rank,time_start); rc = MPI_Init(&argc,&argv); check_mpi(rc); MPI_Comm_size(MPI_COMM_WORLD,&numtasks); MPI_Comm_rank(MPI_COMM_WORLD,&rank); MPI_Get_processor_name(name,&resultlen); printf ("Number of tasks=%d My rank=%d on %s\n", numtasks,rank,name); iter=n/numtasks; a=a+(rank*iter*h); if(rank==numtasks-1) iter=n-(iter*(numtasks-1)); else b=a+(iter*h); printf("task=%d a=%lf b=%lf h=%lf iter=%d\n",rank,a,b,h,iter); sum=trapezoid(a,b,h,iter); printf("task=%d sum=%lf\n",rank,sum); if(rank==0){ dest=1; tag=1; source=numtasks-1; rc = MPI_Send(&sum, 1, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD); check_mpi(rc); rc = MPI_Recv(&sum, 1, MPI_DOUBLE, source, tag, MPI_COMM_WORLD, &Stat); check_mpi(rc); rc = MPI_Get_count(&Stat, MPI_DOUBLE, &count); printf("Task %d: Received %d double(s) from task %d with tag %d \n", rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG); printf("The value of the integral is: %lf\n", sum); } else{ if(rank==numtasks-1) dest=0; else dest=rank+1; tag=1; source=rank-1; rc = MPI_Recv(&sum_app, 1, MPI_DOUBLE, source, tag, MPI_COMM_WORLD, &Stat); check_mpi(rc); rc = MPI_Get_count(&Stat, MPI_DOUBLE, &count); printf("Task %d: Received %d double(s) from task %d with tag %d \n", rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG); sum=sum+sum_app; printf("The value of sum piled up is: %lf\n", sum); rc = MPI_Send(&sum, 1, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD); check_mpi(rc); } time_end=MPI_Wtime(); printf ("Time end for task=%d : %fl\n",rank,time_start); printf ("Time for task=%d : %fl\n",rank,time_end-time_start); MPI_Finalize(); gettimeofday(&end,NULL); end.tv_sec=end.tv_sec-start.tv_sec; end.tv_usec=end.tv_usec-start.tv_usec; if(end.tv_usec<0){ end.tv_usec=end.tv_usec+1000000; end.tv_sec--; } printf("gettimeofday for task=%d , %d sec %d usec\n",rank,end.tv_sec,end.tv_usec); } Versione (b) #include "mpi.h" #include <stdio.h> #include <math.h> #include <sys/time.h> #include <time.h> typedef double precision; /*#define FUNCTION sin*/ #define FUNCTION myfunc2 precision myfunc1( precision x){ return x; } precision myfunc2( precision x){ return 3; } precision trapezoid( precision a, precision b, precision h, unsigned int iter){ precision sum; precision x; unsigned int i; sum=0; x=a; for(i=0;i<iter-1;i++){ sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(x+h))); x=x+h; //printf("partial sum of %d trapezoid=%lf \n",i+1,sum); } sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(b))); return sum; } check_mpi(int rc){ if (rc != MPI_SUCCESS) { printf ("Error starting MPI program. Terminating.\n"); MPI_Abort(MPI_COMM_WORLD, rc); } } #define START 1 #define END 4 #define NBROFTRAP 1000000000ULL int main(int argc,char *argv[]){ int numtasks, rank, rc, resultlen; char name[100]; double time_start, time_end; precision a=START; precision b=END; precision h; precision sum; precision sum_rec; precision sum_send; unsigned int n=NBROFTRAP; unsigned int iter; int dest, source, tag , count; MPI_Status Stat; MPI_Request reqs[2]; struct timeval start,end; gettimeofday(&start,NULL); h=(b-a)/n; sum=0; time_start=MPI_Wtime(); printf ("Time start for task=%d : %fl\n",rank,time_start); rc = MPI_Init(&argc,&argv); check_mpi(rc); MPI_Comm_size(MPI_COMM_WORLD,&numtasks); MPI_Comm_rank(MPI_COMM_WORLD,&rank); MPI_Get_processor_name(name,&resultlen); printf ("Number of tasks=%d My rank=%d on %s\n", numtasks,rank,name); iter=n/numtasks; a=a+(rank*iter*h); if(rank==numtasks-1) iter=n-(iter*(numtasks-1)); else b=a+(iter*h); if(rank==0){ dest=1; tag=1; source=numtasks-1; } else{ if(rank==numtasks-1) dest=0; else dest=rank+1; tag=1; source=rank-1; } rc = MPI_Irecv(&sum_rec, 1, MPI_DOUBLE, source, tag, MPI_COMM_WORLD, &reqs[0]); check_mpi(rc); printf("task=%d a=%lf b=%lf h=%lf iter=%d\n",rank,a,b,h,iter); sum=trapezoid(a,b,h,iter); printf("task=%d sum=%lf\n",rank,sum); if(rank==0) sum_send=sum; else{ MPI_Wait(&reqs[0],&Stat); rc = MPI_Get_count(&Stat, MPI_DOUBLE, &count); printf("Task %d: Received %d double(s) from task %d with tag %d \n", rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG); sum_send=sum+sum_rec; printf("The value of sum piled up is: %lf\n", sum_send); } rc = MPI_Isend(&sum_send, 1, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD,&reqs[1]); check_mpi(rc); if(rank==0){ MPI_Wait(&reqs[0],&Stat); rc = MPI_Get_count(&Stat, MPI_DOUBLE, &count); printf("Task %d: Received %d double(s) from task %d with tag %d \n", rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG); printf("The value of the integral is: %lf\n", sum_rec); } time_end=MPI_Wtime(); printf ("Time end for task=%d : %fl\n",rank,time_start); printf ("Time for task=%d : %fl\n",rank,time_end-time_start); MPI_Finalize(); gettimeofday(&end,NULL); end.tv_sec=end.tv_sec-start.tv_sec; end.tv_usec=end.tv_usec-start.tv_usec; if(end.tv_usec<0){ end.tv_usec=end.tv_usec+1000000; end.tv_sec--; } printf("gettimeofday for task=%d , %d sec %d usec\n",rank,end.tv_sec,end.tv_usec); } Esercizio 4: Metodo dei trapezi con comunicazioni di gruppo (1h) Modificate il programma del punto (b) in modo che il nodo con indice 0 agisca da master sia per la distribuzione degli intervalli su cui calcolare gli integrali (scatter) sia per la raccolta e l’aggregazione dei risultati (gather). Gli altri nodi invece dovranno farsi carico della computazione effettiva. Confrontate le tempistiche di comunicazione, lo speedup e l’efficienza ottenuta con le comunicazioni di gruppo rispetto a quanto ottenuto dai programmi dell’Es 3. Versione (c) #include "mpi.h" #include <stdio.h> #include <math.h> #include <string.h> #include <sys/time.h> #include <time.h> typedef double precision; /*#define FUNCTION sin*/ #define FUNCTION myfunc2 precision myfunc1( precision x){ return x; } precision myfunc2( precision x){ return 3; } precision trapezoid( precision a, precision b, precision h, unsigned int iter){ precision sum; precision x; unsigned int i; sum=0; x=a; for(i=0;i<iter-1;i++){ sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(x+h))); x=x+h; //printf("partial sum of %d trapezoid=%lf \n",i+1,sum); } sum=sum+ ( 0.5 * h * ( FUNCTION(x) + FUNCTION(b))); return sum; } check_mpi(int rc){ if (rc != MPI_SUCCESS) { printf ("Error starting MPI program. Terminating.\n"); MPI_Abort(MPI_COMM_WORLD, rc); } } #define START 1 #define END 4 #define NBROFTRAP 1000000000ULL int main(int argc,char *argv[]){ int idx,numtasks, rank, rc, resultlen; char name[100]; double time_start, time_end; precision a=START; precision b=END; precision h; precision sum; precision sum_rec; precision sum_send; double n=NBROFTRAP; unsigned int iter,iter_last; int dest, source, tag , count, sendcount, recvcount; MPI_Status Stat; MPI_Request reqs[2]; precision sendbuf[10][4]; precision recvbuf[4]; precision recvbuf_gat[10]; struct timeval start,end; gettimeofday(&start,NULL); memset(sendbuf,0x0,sizeof(sendbuf)); sum=0; time_start=MPI_Wtime(); printf ("Time start for task=%d : %fl\n",rank,time_start); rc = MPI_Init(&argc,&argv); check_mpi(rc); MPI_Comm_size(MPI_COMM_WORLD,&numtasks); MPI_Comm_rank(MPI_COMM_WORLD,&rank); MPI_Get_processor_name(name,&resultlen); printf ("Number of tasks=%d My rank=%d on %s\n", numtasks,rank,name); if(rank==0){ h=(b-a)/n; iter=n/(numtasks-1); iter_last=n-(iter*(numtasks-2)); for(idx=1;idx<numtasks;idx++){ a=START; a=a+((idx-1)*iter*h); b=a+(iter*h); sendbuf[idx][0]=a; sendbuf[idx][1]=b; sendbuf[idx][2]=h; sendbuf[idx][3]=iter; } sendbuf[numtasks-1][1]=END; sendbuf[numtasks-1][3]=iter_last; } recvcount=4; sendcount=4; source=0; MPI_Scatter(sendbuf,sendcount,MPI_DOUBLE,recvbuf,recvcount, MPI_DOUBLE,source,MPI_COMM_WORLD); if(rank!=0){ a=recvbuf[0]; b=recvbuf[1]; h=recvbuf[2]; iter=recvbuf[3]; printf("rank= %d Results: a=%lf b=%lf h=%lf iter=%lf\n",rank,recvbuf[0], recvbuf[1],recvbuf[2],recvbuf[3]); sum=trapezoid(a,b,h,iter); printf("task=%d sum=%lf\n",rank,sum); } recvcount=1; sendcount=1; source=0; MPI_Gather(&sum,sendcount,MPI_DOUBLE,recvbuf_gat,recvcount,MPI_DOUBLE,source,MPI_COMM_WOR LD); if(rank==0){ sum=0; for(idx=1;idx<numtasks;idx++){ printf("rank= %d Results: sum=%lf \n",idx,recvbuf_gat[idx]); sum=sum+recvbuf_gat[idx]; } printf("The value of the integral is: %lf\n", sum); } time_end=MPI_Wtime(); printf ("Time end for task=%d : %fl\n",rank,time_start); printf ("Time for task=%d : %fl\n",rank,time_end-time_start); MPI_Finalize(); gettimeofday(&end,NULL); end.tv_sec=end.tv_sec-start.tv_sec; end.tv_usec=end.tv_usec-start.tv_usec; if(end.tv_usec<0){ end.tv_usec=end.tv_usec+1000000; end.tv_sec--; } printf("gettimeofday for task=%d , %d sec %d usec\n",rank,end.tv_sec,end.tv_usec); } Tempistiche funzione da integrare: f(x)=3 intervallo di integrazione: 1-4 (cm) risultato di integrazione atteso: 9 (cm^2) numero trapezi=10^9 Versione seriale: Tempo su una esecuzione: 23,060 sec Versione parallela: Con p=2(1CPU) 2 Tp (sec) To=pTp-Ts S=Ts/Tp E=S/p a 13,188 3,316 1,748 0.874 b 12,771 2,482 1,805 0,902 c 22,879 22,698 1,007 0,503 Con p=7(1CPU) 7 Tp (sec) To=pTp-Ts S=Ts/Tp E=S/p a 3,867 4,009 5,96 0,85 b 3,655 2,53 6,31 0,9 c 4,310 7,11 5,35 0,76 Con p=14(1CPU) 14 Tp (sec) To=pTp-Ts S=Ts/Tp E=S/p a 1,997 4,898 11,55 0,82 b 1,920 3,82 12,01 0,86 c 2,319 9,41 9,94 0,71 Con p=20(1CPU) 20 Tp (sec) To=pTp-Ts S=Ts/Tp E=S/p a 1,391 4,760 16,58 0,83 b 1,404 5,02 16,42 0,82 c 1,470 6,34 15,69 0,78 numero trapezi=10^8 Versione seriale: Tempo su una esecuzione: 2,474 sec Versione parallela: Con p=2(1CPU) 2 Tp (sec) To=pTp-Ts S=Ts/Tp E=S/p a 1,693 0,91 1,46 0,73 b 1,482 0,49 1,67 0,83 c 2,382 2,29 1,04 0,52 Con p=7(1CPU) 7 Tp (sec) To=pTp-Ts S=Ts/Tp E=S/p a 0,479 0,88 5,16 0,74 b 0,493 0,98 5,02 0,72 c 0,557 1,43 4,44 0,63 Con p=14(1CPU) 14 Tp (sec) a 0,350 b c To=pTp-Ts S=Ts/Tp E=S/p 2,43 7,07 0,5 0,329 2,13 7,52 0,54 0,325 2,08 7,61 0,54 Con p=20(1CPU) 20 Tp (sec) To=pTp-Ts S=Ts/Tp E=S/p a 0,263 2,79 9,41 0,47 b 0,282 3,17 8,77 0,44 c 0,295 3,43 8,39 0,42 Risposte ai quesiti: 3(a) La versione dell'algoritmo che utilizza send e receive sincrone (a) per comunicare ottiene un buono speedup rispetto alla versione seriale: aumentando il numero di processori coinvolti nella computazione aumenta lo speedup (con una efficienza più o meno costante). Nel caso di 10^9 trapezi si passa da un tempo di esecuzione di 23,06 secondi per la versione seriale a 1,391 secondi per la versione parallela con 20 processori; in questo caso abbiamo uno speedup pari a 16,58 e una efficienza di 0,83. 3(b) La versione dell'algoritmo che utilizza send e receive asincrone (b) per comunicare ottiene prestazioni del tutto simili a quelle dell'algoritmo con send e receive sincrone. In alcune casistiche i due algoritmi hanno dei tempi di comunicazione differenti, con valori inferiori per la versione con comunicazioni asincrone che risultano essere un pò meno bloccanti (“un pò meno” perchè comunque ci sono delle barriere/wait) rispetto alla versione con comunicazioni sincrone. 4 Con la versione dell'algoritmo che utilizza comunicazioni di gruppo (c) notiamo subito un fenomeno che ci aspettavamo: avendo più comunicazioni rispetto alle versioni con send e receive abbiamo dei tempi di overhead maggiori e questo pregiudica il tempo di calcolo parallelo e quindi anche speedup ed efficenza che sono inferiori rispetto a quelli degli algoritmi con send e receive. Aumentando di un ordine di grandezza la dimensione del problema, passando da 10^8 triangoli a 10^9, notiamo che, a parità del numero di processori, speedup ed efficienza aumentano. Aumentando il numero di processori, mantenendo costante la dimensione del problema, notiamo che l'efficienza di minuisce. Queste due caratteristiche ci portano a dire che la formulazione parallela del problema è scalabile: riusciamo ad ottenere una efficienza costante aumentando la dimensione del problema e il numero di processori coinvolti nella comunicazione.