Università degli Studi del Sannio Facoltà di Ingegneria Corso di Laurea Specialistica in Ingegneria Informatica Corso di Programmazione Concorrente PROGETTO: K-Means Data Clustering Studenti Flavio Pace Docente Prof. Umberto Villano Rita Di Candia Anno Accademico 2009/2010
Indice 1. K-MEANS... 1 1.1 Cos è?... 1 1.2 Vantaggi e svantaggi... 2 2. Implementazione... 3 2.1 Sequenziale... 3 2.2 Parallelo... 3 2.3 Risultati... 5 2.3.1 Testbad... 5 2.3.2 1 Soluzione... 5 2.3.3 2 Soluzione... 8 3 Hadoop... 15 3.1 Implementazione K-Means con Hadoop... 17 4 Hadoop vs Open MPI... 19 Appendice A Open MPI e Sequenziale... 23 Appendice B MapReduce... 24 KmeansDriver.java... 24 KmeansMapper.java... 28 KmeansCombiner.java... 30 KmeansReducer.java... 31
1. K-MEANS 1.1 Cos è? L'algoritmo K-Means è un algoritmo di clustering, progettato nel 1967 da MacQeen, che permette di suddividere gruppi di oggetti in K partizioni sulla base dei loro attributi. È una variante dell'algoritmo di aspettazione-massimizzazione il cui obiettivo è determinare i K gruppi di dati generati da distribuzioni gaussiane. Si assume che gli attributi degli oggetti possano essere rappresentati come vettori, e che quindi formino uno spazio vettoriale. L'obiettivo che l'algoritmo si prepone è di minimizzare la varianza totale intra-cluster (o la deviazione standard). Ogni cluster viene identificato mediante un centroide o punto medio. L'algoritmo segue una procedura iterativa: inizialmente crea K partizioni e assegna ad ogni partizione i punti d'ingresso o casualmente o usando alcune informazioni euristiche; calcola il centroide di ogni gruppo; costruisce quindi una nuova partizione associando ogni punto d'ingresso al cluster il cui centroide è più vicino ad esso; vengono ricalcolati i centroidi per i nuovi cluster e così via, finché l'algoritmo non converge. Figura 1.1: Esempio algoritmo K-Means Come già accennato l obiettivo è quello di minimizzare la somma delle distanze di ciascun oggetto dal centroide del cluster cui è assegnato: 1
Dove d è una misura di dissimilarità generalmente definita dal quadrato della distanza Euclidea. Per due punti in uno spazio n-dimensionale, P = (p 1,p 2,...,p n ) e Q = (q 1,q 2,...,q n ), la distanza Euclidea è calcolata come: Esistono diverse varianti dell algoritmo che differiscono nella selezione iniziale dei centri dei cluster, nel calcolo della dissimilarità e nelle strategie per calcolare i centri dei cluster. 1.2 Vantaggi e svantaggi Per quanto riguarda i vantaggi: è possibile variare la posizione iniziale dei centroidi per cercare di ridurre la dipendenza dalla condizioni iniziali; è efficiente nel gestire grosse quantità di dati. La complessità computazionale dell algoritmo è O(tkmn) dove m è il numero di attributi, n il numero di oggetti, k il numero dei cluster e t è il numero di iterazioni sull intero data set. In genere k, m, t << n; spesso l algoritmo termina con un ottimo locale. Per trovare un ottimo globale possono essere adottate altre tecniche (deterministic annealing, algoritmi generici) da incorporare al k-means; Per quanto riguarda gli svantaggi: funziona solo su valori numerici in quanto minimizza una funzione di costo calcolando la media dei clusters; i cluster hanno forma convessa, pertanto è difficile usare il k-means per trovare cluster di forma non convessa; occorre fissare a priori k; si potrebbe verificare la situazione in cui a uno o più cluster non vengono associati dei punti. 2
2. Implementazione Schema algoritmo: Figura 2.1: Struttura dell algoritmo 2.1 Sequenziale L algoritmo sequenziale è tratto dal libro di J. MacQueen "Some Methods for Classification and Analysis of Multivariate Observations." 2.2 Parallelo L implementazione parallela è basata sul parallelismo dei dati. I dati in ingresso sono equamente divisi tra tutti i processi, mentre i punti scelti come centroidi (o cluster) vengono replicati. 3
Successivamente alla fine di ogni iterazione, su ogni processo, vengono generati i nuovi cluster, fino a quando l algoritmo non termina. Il file di ingresso è costituito da un numero di punti ognuno dei quali ha 20 coordinate. Mentre in uscita vengono generati due tipi di file: nel primo file vengono scritte le coordinate dei k cluster finali che sono stati generati, chiamato "nomefileinput.cluster_centres"; nel secondo file vengono scritte le coppie punto-cluster, ossia il numero del punto e il numero del cluster al quale è stato associato, chiamato "nomefileinput.membership". Per il calcolo parallelo è stato utilizzato Open MPI e un numero di processi, detti anche rank, pari a 8. Il valore del CPU time è dato dalla somma dei tempi impiegati per eseguire ogni singolo ciclo. Alla fine dell esecuzione dell algoritmo tutti i processi mandano i dati al rank 0, il quale è l unico responsabile della scrittura dei risultati sul file di output. RANK0 RANK1 RANK2 RANK3 FILE_OUTPUT Figura 2.2: Tecnica di scrittura dei dati nel file di output Per calcolare l I/O time, ossia il tempo impiegato dal rank 0 per scrivere i risultati nel file di output, sono state utilizzate due soluzioni: la prima soluzione consiste nel calcolare tale valore facendo la somma del tempo speso per la lettura dei punti dal file in input più la somma del tempo speso per la scrittura del file di uscita; la seconda soluzione consiste nello spostare la scrittura a fine ciclo ed introdurre una variabile str1 che raccoglie prima tutti i dati e successivamente fa la scrittura su file. 4
2.3 Risultati 2.3.1 Testbad Il test è stato effettuato applicando due tipi di input: un file da 188 MB contenente 1 milione di punti ognuno con 20 coordinate; un file da 944 MB contenente 5 milioni di punti ognuno con 20 coordinate. L esecuzione è stata effettuata con due differenti tipi di infrastruttura di rete: Myrinet Fast Ethernet Inoltre le dimensioni dei due tipi di file ottenuti come output sono: output ottenuto dall esecuzione di 1 milione di punti: il file.cluster_centres è da 9.5 KB; il file.membership è da 9.3 MB; output ottenuto dall esecuzione di 5 milioni di punti: il file.cluster_centres è da 9.5 KB; il file.membership è da 51 MB. 2.3.2 1 Soluzione Di seguito sono riportati i risultati ottenuti dalla prima soluzione. Sono stati messi a confronto due tipi di esecuzione effettuate: Sequenziale; Open MPI attraverso Fast Ethernet. 5
secondi Risultati 1 milione n processi 8 10 CLUSTER Float CPU time I/O time Total time Speed-up Efficienza % real user sys MPI_Ethe 61,21 24,88 86,09 5,26 65,74% 1m24.406s 0m0.389s 0m0.068s Seq 436,63 16,13 452,76 7m32.762s 7m32.339s 0m0.392s 20 CLUSTER Float MPI_Ethe 163,53 24,85 188,38 6,55 81,92% 3m6.646s 0m0.363s 0m0.079s Seq 1218,6 15,96 1234,56 20m34.566s 20m34.204s 0m0.361s 50 CLUSTER Float 6000 MPI_Ethe 748,04 24,98 773,02 7,65 95,59% 12m51.262s 0m0.373s 0m0.080s Seq 5895,22 16,14 5911,36 98m31.366s 98m31.009s 0m0.374s 5000 4000 3000 2000 1000 0 MPI_Ethe Seq MPI_Ethe Seq MPI_Ethe Seq 10 CLUSTER 20 CLUSTER 50 CLUSTER CPU time I/O time Figura 2.3: Risultati prima soluzione con input 1 milione di punti e 8 processi 6
secondi Risultati 5 milioni n processi 8 10 CLUSTER Float CPU time I/O time Total time Speed-up Efficienza % real user sys MPI_Ethe 469,45 123,68 593,13 6,19 77,41% 9m41.211s 0m0.393s 0m0.079s Seq 3593,16 79,94 3673,1 61m13.112s 61m11.152s 0m1.903s 20 CLUSTER Float MPI_Ethe 1046,75 122,28 1169,03 7,08 88,44% 19m17.091s 0m0.371s 0m0.079s Seq 8190,06 81,44 8271,5 137m51.518s 137m49.586s 0m1.924s 50 CLUSTER Float MPI_Ethe 2981,46 122,12 3103,58 7,67 95,88% 51m31.664s 0m0.377s 0m0.077s Seq 23724,23 82,27 23806,5 396m46.502s 396m44.732s 0m1.880s 25000 20000 15000 10000 5000 0 MPI_Ethe Seq MPI_Ethe Seq MPI_Ethe Seq 10 CLUSTER 20 CLUSTER 50 CLUSTER CPU time I/O time Figura 2.4: Risultati prima soluzione con input 5 milioni di punti e 8 processi 7
2.3.3 2 Soluzione Di seguito sono riportati i risultati ottenuti dalla seconda soluzione, mettendo a confronto, in questo caso, i tre tipi di esecuzione effettuate: Sequenziale; Open MPI attraverso Fast Ethernet; Open MPI attraverso Myrinet. Come si può notare dai risultati questa seconda soluzione è più prestante, sia in termini di computazione e che di I/O, rispetto alla precedente soluzione, in quanto si è passati, per quanto riguarda il file da 1 milione di punti, da un tempo medio pari a ~25 sec a un tempo medio pari a ~19 sec, mentre per quanto riguarda il file da 5 milioni di punti, si è passati da un tempo medio di ~123 sec a un tempo medio di ~95 sec. 8
Risultati 1 milione n processi 8 10 CLUSTER Float Double CPU time I/O time Total time Speed-up Efficienza % real user sys MPI_Myri 57,89 18,73 76,62 5,91 73,86% 1m18.118s 0m0.403s 0m0.074s MPI_Ethe 61,12 19,9 81,02 5,59 69,85% 1m19.265s 0m0.390s 0m0.074s Seq 436,63 16,13 452,76 7m32.762s 7m32.339s 0m0.392s MPI_Myri 55,09 18,91 74 6,14 76,69% 1m14.621s 0m0.383s 0m0.081s MPI_Ethe 60,23 23,27 83,5 5,44 67,96% 1m19.278s 0m0.376s 0m0.078s Seq 437,84 16,15 453,99 7m34.038s 7m33.483s 0m0.487s 20 CLUSTER Float Double MPI_Myri 159,13 18,76 177,89 6,94 86,75% 2m58.601s 0m0.389s 0m0.076s MPI_Ethe 163,39 19,79 183,18 6,74 84,25% 3m1.426s 0m0.368s 0m0.077s Seq 1218,6 15,96 1234,56 20m34.566s 20m34.204s 0m0.361s MPI_Myri 155,92 18,71 174,63 7,22 90,19% 2m55.255s 0m0.388s 0m0.088s MPI_Ethe 161,28 23,28 184,56 6,83 85,34% 3m0.280s 0m0.379s 0m0.077s Seq 1243,83 16,13 1259,96 20m59.971s 20m59.468s 0m0.501s 50 CLUSTER Float Double MPI_Myri 739,34 18,71 758,05 7,80 97,48% 12m38.761s 0m0.372s 0m0.088s MPI_Ethe 747,04 19,82 766,86 7,71 96,36% 12m45.121s 0m0.380s 0m0.066s Seq 5895,22 16,14 5911,36 98m31.366s 98m31.009s 0m0.374s MPI_Myri 506,09 19,15 525,24 7,72 96,55% 8m45.856s 0m0.356s 0m0.100s MPI_Ethe 512,38 23 535,38 7,58 94,72% 8m51.100s 0m0.370s 0m0.077s Seq 4040,82 16,28 4057,1 67m37.100s 67m36.622s 0m0.489s Figura 2.5: Risultati seconda soluzione con input 1 milione di punti e 8 processi 9
secondi 6000 FLOAT 5000 DOUBLE 4000 3000 2000 FLOAT DOUBLE 1000 FLOAT DOUBLE 0 10 CLUSTER 20 CLUSTER 50 CLUSTER CPU time I/O time Figura 2.6: Rappresentazione risultati seconda soluzione con input 1 milione di punti e 8 processi 10
Risultati 5 milioni n processi 8 10 CLUSTER Float Double CPU time I/O time Total time Speed-up Efficienza % real user sys MPI_Myri 452,81 93,2 546,01 6,73 84,09% 9m6.309s 0m0.380s 0m0.092s MPI_Ethe 469,18 100,92 570,1 6,44 80,54% 9m18.192s 0m0.370s 0m0.079s Seq 3593,16 79,94 3673,1 61m13.112s 61m11.152s 0m1.903s MPI_Myri 447,36 96,57 543,93 6,70 83,74% 9m3.717s 0m0.384s 0m0.077s MPI_Ethe 497,48 145,21 642,69 5,67 70,87% 9m52.697s 0m0.372s 0m0.080s Seq 3563,27 80,63 3643,9 60m43.920s 60m41.394s 0m2.424s 20 CLUSTER Float Double MPI_Myri 1025,04 93 1118,04 7,40 92,48% 18m38.316s 0m0.380s 0m0.078s MPI_Ethe 1044,69 99,21 1143,9 7,23 90,39% 18m51.980s 0m0.380s 0m0.082s Seq 8190,06 81,44 8271,5 137m51.518s 137m49.586s 0m1.924s MPI_Myri 1020,27 95,95 1116,22 7,37 92,16% 18m36.006s 0m0.374s 0m0.091s MPI_Ethe 1045,22 115,82 1161,04 7,09 88,60% 18m56.467s 0m0.384s 0m0.097s Seq 8147,44 81,92 8229,36 137m9.369s 137m5.976s 0m2.556s 50 CLUSTER Float Double MPI_Myri 2947,06 92,18 3039,24 7,83 97,91% 50m39.534s 0m0.381s 0m0.089s MPI_Ethe 2981,83 99,25 3081,08 7,73 96,58% 51m9.175s 0m0.381s 0m0.072s Seq 23724,23 82,27 23806,5 396m46.502s 396m44.732s 0m1.880s MPI_Myri 3502,48 93,41 3595,89 7,81 97,61% 59m55.675s 0m0.370s 0m0.099s MPI_Ethe 3528,11 114,95 3643,06 7,71 96,35% 60m18.422s 0m0.387s 0m0.085s Seq 27997,48 82,53 28080,01 468m0.012s 467m57.314s 0m2.520s Figura 2.7: Risultati seconda soluzione con input 5 milioni di punti e 8 processi 11
secondi 30000 25000 FLOAT DOUBLE 20000 15000 10000 FLOAT DOUBLE 5000 FLOAT DOUBLE 0 10 CLUSTER 20 CLUSTER 50 CLUSTER CPU time I/O time Figura 2.8: Rappresentazione risultati seconda soluzione con input 5 milioni di punti e 8 processi 12
L'implementazione parallela considera i dati in ingresso come variabili float (rapprentazione su 4 byte), valutando i dati ottenuti dalle varie misurazioni abbiamo notato che la differenza tra i due tipi di infrastruttura di rete utilizzati è minima, come mostrato in Figura 2.5 e 2.7. Questa differenza minima è data dal fatto che tra i vari processi non c'è molta comunicazione, come riportato di seguito, in modo da non far prevalere nettamente una infrastruttura su un'altra. -------------------------------------------------------------------------------------------- @--- Callsites: 24 ---------------------------------------------------------------------- -------------------------------------------------------------------------------------------- ID Lev File/Address Line Parent_Funct MPI_Call 1 0 mpi_kmeans.c 131 mpi_kmeans Allreduce 2 0 mpi_io.c 208 mpi_write File_write 3 0 mpi_io.c 216 mpi_write File_close 4 0 mpi_main.c 153 main Allreduce 5 0 mpi_io.c 101 mpi_read Bcast 6 0 mpi_kmeans.c 129 mpi_kmeans Allreduce 7 0 mpi_io.c 102 mpi_read Bcast 8 0 mpi_io.c 274 mpi_write Recv 9 0 mpi_kmeans.c 144 mpi_kmeans Allreduce 10 0 mpi_io.c 282 mpi_write File_write 11 0 mpi_main.c 119 main Barrier 12 0 mpi_kmeans.c 105 mpi_kmeans Allreduce 13 0 mpi_io.c 211 mpi_write File_write 14 0 mpi_io.c 255 mpi_write File_open 15 0 mpi_io.c 191 mpi_write File_open 16 0 mpi_io.c 268 mpi_write File_write 17 0 mpi_io.c 213 mpi_write File_write 18 0 mpi_main.c 194 main Reduce 19 0 mpi_main.c 192 main Reduce 20 0 mpi_io.c 121 mpi_read Send 21 0 mpi_io.c 284 mpi_write File_close 22 0 mpi_main.c 161 main Bcast 23 0 mpi_io.c 287 mpi_write Send 24 0 mpi_io.c 145 mpi_read Recv 13
-------------------------------------------------------------------------------------------- @--- Aggregate Sent Message Size (top twenty, descending, bytes) ---------- -------------------------------------------------------------------------------------------- Call Site Count Total Avrg Sent% Send 20 7 7e+07 1e+07 93.69 Send 23 7 3.5e+06 5e+05 4.68 Allreduce 6 1432 1.15e+06 800 1.53 Allreduce 1 1432 5.73e+04 40 0.08 -------------------------------------------------------------------------------------------- @--- Aggregate I/O Size (top twenty, descending, bytes) ----------------------- -------------------------------------------------------------------------------------------- Call Site Count Total Avrg I/O% File_write 16 125000 1.01e+06 8.11 99.80 File_write 13 200 1.9e+03 9.49 0.19 File_write 10 7 63 9 0.01 File_write 2 10 20 2 0.00 File_write 17 10 10 1 0.00 Come ulteriore controprova abbiamo effettuato una modifica sul tipo di dato in ingresso, passando da un tipo float a un tipo double (rappresentazione su 8 byte). 14
3 Hadoop Hadoop è un framework sviluppato dalla community Open-Source per applicazioni parallele su cluster di grandi dimensioni. Sviluppato con tecnologia Java, è stato pensato per l elaborazione di grosse quantità di dati in applicazioni distribuite. Uno dei paradigmi computazionali utilizzati dal framework è il MapReduce, il quale divide l applicazione in piccoli task, ed ognuno di essi potrà essere eseguito su uno o più nodi in modo da rendere la nostra applicazione distribuita, senza l ausilio di una particolare sintassi grammaticale. Figura 3.1: Descrizione del processo MapReduce MapReduce è diventato popolare grazie a Google che lo utilizza per elaborare ogni giorno molti petabyte di dati. E costituito da due task scritti dall utente, chiamati Map e Reduce, e da un Framework che splitta l input in ingresso in data set indipendenti che vengono processati in modo parallelo su un cluster. Il Map task legge un insieme di record da un file di input, svolge le operazioni di filtraggio e le trasformazioni desiderate, quindi produce una serie di record di output nella forma convenuta (key, value). Dopo essere stati raccolti dal Framework i record di input vengono raggruppati per chiavi (attraverso operazioni di sorting o hashing) e sottoposti al task Reduce. Come per il task Map, Reduce esegue una elaborazione arbitraria attraverso un linguaggio general purpose. Di conseguenza può compiere qualsiasi sorta di operazioni sui record. Per esempio può elaborare 15
alcune funzioni addizionali per altri campi dati del record. Ciascuna istanza Reduce può scrivere record a un file di output e quest ultimo rappresenta una parte della risposta soddisfatta da una elaborazione MapReduce. La coppia key/value prodotta dal task Map può contenere qualsiasi tipo di dati nel campo assegnato al valore del campo. I sistemi attuali implementano MapReduce utilizzando linguaggi come Java, C++, Python, Perl, Ruby, e altri. Le coppie key/value utilizzate nell elaborazione MapReduce possono essere archiviate in un file o in un database. In aggiunta è stato implementato un file system distribuito (HDFS), il quale è stato pensato per avere una elevata larghezza di banda ed un solido meccanismo di recovery-failure. Hadoop ha un architettura master-slave, con un unico host master e con host slave multipli. L host master gestisce: JobTracker il quale schedula e gestisce tutti i task appartenenti a un processo in esecuzione; NameNode il quale gestisce il file system HDFS e regola l accesso ai file da parte dei clienti Mentre l host slave gestisce: TaskTracker il quale lancia i task sul proprio host sulla base delle istruzioni ricevute dal JobTracker; inoltre tiene traccia dei progressi di ogni task sul proprio host; DataNode il quale fornisce blocchi di dati, i quali sono memorizzati sul proprio disco locale, ai clienti HDFS. Figura 3.2: Architettura Hadoop 16
3.1 Implementazione K-Means con Hadoop In figura è riportato un esempio di come funziona l algoritmo K-Means attraverso MapReduce: Figura 3.3: Implementazione dell algoritmo K-Means con Hadoop 0 Vengono scelti come cluster iniziali i primi k punti del file in input, dove k è il numero di cluster scelti; 1 Il set dati in ingresso viene suddiviso in N parti, successivamente ogni parte viene inviata ad un mapper; 2 Nella funzione Map viene calcolata la distanza tra ogni punto e ogni centro del cluster (cluster center), ogni punto è etichettato con l'indice del cluster avente distanza minima. Il Mapper genera in uscita le coppie key-value, dove la key è il numero del cluster assegnato a ogni punto e la value sono le coordinate del punto; 3 Tutti i punti dello stesso cluster (tutti i record con la stessa key) vengono inviati ad un unico Reducer, nella quale vengono calcolate le nuove coordinate dei k cluster attraverso la media dei punti, l output del Reducer è dato dal numero del cluster con le corrispondenti nuove coordinate; 17
4 Successivamente le nuove coordinate vengono confrontate con quelle originali, se sono uguali oppure la loro differenza è all interno di una soglia prefissata, allora vuol dire che i cluster calcolati sono quelli finali e il programma termina, altrimenti vengono considerati gli ultimi cluster calcolati e si ripete iterativamente il procedimento dal punto 2 al punto 4. Molti delle implementazioni effettuate con MapReduce sono limitate dalla larghezza di banda disponibile sul cluster, quindi conviene ridurre al minimo la quantità di dati trasferiti tra i task Map e Reduce. Per tale motivo spesso Hadoop consiglia di utilizzare una funzione Combiner che riceve in ingresso l output della funzione Map e fornisce in uscita l input della funzione Reduce. La funzione Combiner è introdotta per cercare di ottimizzare l implementazione, nel nostro caso è stata molto utile in quanto, avendo un solo Reducer a disposizione, esso fungeva da collo di bottiglia. La funzione Combiner non sostituisce la funzione Reduce, ma contribuisce a ridurre la quantità di dati che vengono scambiati tra Map e Reduce. Input Output Mapper <num point, coords point> < num cluster, coords point associate > Combiner <num cluster, coords point associate> < num cluster, sum partial point > Reducer < num cluster, sum partial point > <num cluster, new coords cluster> Figura 3.4: Input e Output della funzione Map, Combiner e Reducer 18
4 Hadoop vs Open MPI In questo capitolo mettiamo a confronto l implementazione dell algoritmo K-Means sviluppato con OpenMPI e Hadoop. Per poter correttamente interpretare i risultati ottenuti è da tenere in considerazione che il Framework Hadoop è stato pensato e ideato per: una quantità di dati maggiori di 1 TB; massively parallel, cioè con sistemi distribuiti aventi centinai o migliaia di CPUs; facile implementazione: linguaggi Object Oriented; il programmatore non ha bisogno di conoscere i dettagli strutturali del sistema di calcolo. Fault-tolerance. Di seguito sono riportati i risultati ottenuti, mettendo a confronto i valori ottenuti con Hadoop e i valori ottenuto con Open MPI. 19
secondi Risultati 1 milione n processi 4 10 CLUSTER Total time Loop time Map-Red 10689 71/72 Map-Comb-Red 7104 46/47 Open MPI Ethe 135 0,64 20 CLUSTER Map-Red 8837 71/72 Map-Comb-Red 5961 46/47 Open MPI Ethe 360 1,22 50 CLUSTER Map-Red 8892 75 Map-Comb-Red 5995 50 Open MPI Ethe 1502 3 12000 10000 8000 6000 4000 2000 0 10 CLUSTER 20 CLUSTER 50 CLUSTER Total time Figura 4.1: Confronto tra Hadoop e Open MPI mettendo in input 1 milione di punti e 4 processi 20
secondi Risultati 5 milioni n processi 4 10 CLUSTER Total time Loop time Map-Comb-Red 9268 68/69 Open MPI 981 3,2 20 CLUSTER Map-Comb-Red 14251 75 Open MPI 2131 6,1 50 CLUSTER Map-Comb-Red 28038 78 Open MPI 6007 14,73 30000 25000 20000 15000 10000 5000 0 10 CLUSTER 20 CLUSTER 50 CLUSTER Total time Figura 4.2: Confronto tra Hadoop e Open MPI mettendo in input 5 milioni di punti e 4 processi 21
I risultati ottenuti sono stati confrontati con i risultati pubblicati da Wei Jiang, Vignesh T. Ravi e Gagan Agrawal nella seguente documentazione: http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=05289199 3.1 Osservazioni Con l introduzione del task Combiner abbiamo ottenuto un ottimizzazione sul tempo di esecuzione di ogni singolo loop del 36%, in quanto si è passati da un tempo pari a ~71 sec a un tempo pari a ~46 sec, applicando come input 1 milione di punti. Al variare della quantità di dati in input la variazione del tempo impiegato per effettuare ogni singolo loop è minima, cosa che non accade con OpenMPI. Ciò implica che la maggior parte del tempo speso da Hadoop è da imputare all inizializzazione e alla gestione del Framework. Per la stessa quantità di dati in input il numero di loop impiegati da OpenMPI e Hadoop per eseguire l algoritmo differisce. In quanto non è stato possibile implementare lo stesso algoritmo per il calcolo del valore del threshold a causa dell implementazione del Framework stesso. 22
Appendice A Open MPI e Sequenziale Di seguito sono riportati i comandi utilizzati per l esecuzione dell algoritmo K-Means: Comando esecuzione over eth0 mpirun -np 8 --mca btl_tcp_if_include eth0 -machinefile allmachine Simple_Kmeans_fileDouble_mpiP/mpi_main -i /state/partition1/fpace/file_5m_20c -n 10 -o Comando Esecuzione Over myri0 mpirun -np 8 --mca btl_tcp_if_include myri0 -machinefile allmachine Simple_Kmeans_fileDouble_mpiP/mpi_main -i /state/partition1/fpace/file_1m_20c -n 10 -o Comando Esecuzione Sequenziale Simple_Kmeans_fileDouble_mpiP/seq_main -i /state/partition1/fpace/file_1m_20c -n 10 o 23
Appendice B MapReduce Di seguito è riportato il codice, in linguaggio Java, dell implementazione dell algoritmo K- Means attraverso MapReduce: KmeansDriver.java import java.io.bytearrayoutputstream; import java.io.ioexception; import java.io.printstream; import java.util.hashmap; import java.util.stringtokenizer; import org.apache.hadoop.conf.configuration; import org.apache.hadoop.fs.fsdatainputstream; import org.apache.hadoop.fs.filesystem; import org.apache.hadoop.fs.path; import org.apache.hadoop.io.ioutils; import org.apache.hadoop.io.text; import org.apache.hadoop.mapreduce.job; import org.apache.hadoop.mapreduce.lib.input.fileinputformat; import org.apache.hadoop.mapreduce.lib.output.fileoutputformat; import org.apache.hadoop.util.genericoptionsparser; public class KmeansDriver { public static void main(string[] args) throws Exception { long start, start_loop, stop, stop_loop; //start global timer start = System.currentTimeMillis(); Configuration conf = new Configuration(); boolean converged=true; String[] otherargs = new GenericOptionsParser(conf, args).getremainingargs(); if (otherargs.length!= 4) { System.err.println("Usage: KmeansDriver <in_directory> <out_directory> <in_cluster/cluster_file> num_coord_point"); System.exit(2); 24
int i=0; do{ //set global coord value from commandline conf.set("my.num.coord", otherargs[3]); if(i==0){ conf.set("my.cluster.coord",otherargs[2] ); else if(i==1) conf.set("my.cluster.coord",otherargs[1]+"0"+"/part-r-00000" ); else conf.set("my.cluster.coord",otherargs[1]+(i-1)+"/part-r-00000"); Job job = new Job(conf, "Kmeans_unicondor"); //only one reduce job.setnumreducetasks(1); job.setjarbyclass(kmeansdriver.class); job.setmapperclass(kmeansmapper.class); job.setcombinerclass(kmeanscombiner.class); job.setreducerclass(kmeansreducer.class); job.setoutputkeyclass(text.class); job.setoutputvalueclass(text.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]+i)); //start timer loop start_loop = System.currentTimeMillis(); job.waitforcompletion(true); //check if cluster_centroid are equal if(i>=2) converged=isconverged(i,integer.parseint(otherargs[3]), otherargs[1]); i++; //stop timer loop stop_loop = System.currentTimeMillis(); System.out.println("Time_loop_"+(i-1) +": "+ (stop_loop - start_loop)/1000 + " s"); while(i<500 && converged); System.out.println("Clustering..."); //start timer clustering long start_clustering = System.currentTimeMillis(); clustering(otherargs[0],otherargs[1]+"points",conf.get("my.cluster.coord"),otherarg s[3]); 25
//stop timer clustering long stop_clustering = System.currentTimeMillis(); System.out.println("Time Clustering: " + (stop_clustering - start_clustering)/1000 + " s"); //stop timer programma stop = System.currentTimeMillis(); System.out.println("Time Total: " + (stop - start)/1000 + " s"); private static boolean isconverged(int iteration, int num_coord, String dir_output) throws IOException { boolean ret=true; ByteArrayOutputStream byte1=new ByteArrayOutputStream(); PrintStream out2 = new PrintStream(byte1); HashMap<String,double[]> cluster= new HashMap<String,double[]>(); HashMap<String,double[]> cluster1= new HashMap<String,double[]>(); Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(new Path(dir_output+(iteration-1)).toUri(),conf); FSDataInputStream in = null, in1=null; try { in = fs.open(new Path(dir_output+(iteration-1)+"/part-r-00000")); IOUtils.copyBytes(in, out2, 4096, false); String s=byte1.tostring(); String lines[]= s.split("\n"); for(int i=0; i<lines.length; i++){ double[] centers= new double[num_coord]; StringTokenizer itr = new StringTokenizer(lines[i]); String id_cluster= itr.nextelement().tostring(); int j=0; while(itr.hasmoreelements()){ centers[j]=(double.parsedouble(itr.nextelement().tostring())); j++; cluster.put(id_cluster, centers); ByteArrayOutputStream byte2=new ByteArrayOutputStream(); PrintStream out3 = new PrintStream(byte2); 26
FileSystem fs1 = FileSystem.get(new Path(dir_output+iteration).toUri(),conf); in1 = fs1.open(new Path(dir_output+iteration+"/part-r-00000")); IOUtils.copyBytes(in1, out3, 4096, false); String s1=byte2.tostring(); String lines1[]= s1.split("\n"); for(int i=0; i<lines1.length; i++){ double[] centers1= new double[num_coord]; StringTokenizer itr = new StringTokenizer(lines1[i]); String id_cluster= itr.nextelement().tostring(); int j=0; while(itr.hasmoreelements()){ centers1[j]=(double.parsedouble(itr.nextelement().tostring())); j++; cluster1.put(id_cluster, centers1); finally { IOUtils.closeStream(in); int cont=0; double[] first_cluster; double[] second_cluster; for(string key :cluster.keyset()){ first_cluster = cluster.get(key); second_cluster = cluster1.get(key); if(kmeansutil.isequalthreshold(first_cluster, second_cluster)) { cont++; if(cont==cluster.size()) { ret=false; //debug System.out.println("PATH is: " +dir_output+(iteration)+"/part-r-00000"); System.out.println("PATH is: " +dir_output+(iteration-1)+"/part-r-00000"); System.out.println("debug all clusters are equal"); System.out.println("HashMap size:"+ cluster.size()); System.out.println("HashMap1 size:"+ cluster1.size()); System.out.println("Cont :" + cont); return ret; 27
public static void clustering(string input, String output,string cluster, String num_coord) throws Exception { Configuration conf = new Configuration(); conf.set("my.num.coord", num_coord); conf.set("my.cluster.coord",cluster ); Job job = new Job(conf, "Kmeans_unicondor_clustering"); job.setjarbyclass(kmeansdriver.class); job.setmapperclass(kmeansmapper.class); job.setoutputkeyclass(text.class); job.setoutputvalueclass(text.class); FileInputFormat.addInputPath(job, new Path(input)); FileOutputFormat.setOutputPath(job, new Path(output)); job.waitforcompletion(true); KmeansMapper.java import java.io.bytearrayoutputstream; import java.io.ioexception; import java.io.printstream; import java.util.hashmap; import java.util.iterator; import java.util.set; import java.util.stringtokenizer; import org.apache.hadoop.conf.configuration; import org.apache.hadoop.fs.fsdatainputstream; import org.apache.hadoop.fs.filesystem; import org.apache.hadoop.fs.path; import org.apache.hadoop.io.ioutils; import org.apache.hadoop.io.text; import org.apache.hadoop.mapreduce.mapper; public class KmeansMapper extends Mapper<Object, Text, Text, Text>{ private HashMap<String,double[]> meanscluster= new HashMap<String,double[]>(); @Override public void setup(context context){ try { 28
ByteArrayOutputStream byte1=new ByteArrayOutputStream(); PrintStream out2 = new PrintStream(byte1); String uri = context.getconfiguration().get("my.cluster.coord"); Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(new Path(uri).toUri(),conf); FSDataInputStream in = null; try { in = fs.open(new Path(uri)); IOUtils.copyBytes(in, out2, 4096, false); String s=byte1.tostring(); String lines[]= s.split("\n"); for(int i=0; i<lines.length; i++){ double[] centers= new double[integer.parseint(context.getconfiguration().get("my.num.coord"))]; StringTokenizer itr = new StringTokenizer(lines[i]); String id_cluster= itr.nextelement().tostring(); int j=0; while(itr.hasmoreelements()){ centers[j]=(double.parsedouble(itr.nextelement().tostring())); j++; meanscluster.put(id_cluster, centers); finally { IOUtils.closeStream(in); catch (IOException e) { e.printstacktrace(); public void map(object key, Text value, Context context ) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); //get global value passing from main arguments int num_coord= Integer.parseInt(context.getConfiguration().get("my.num.coord")); Text word = new Text(); word.set(itr.nextelement().tostring()); 29
double[] array_points = new double[num_coord]; int k=0; while (itr.hasmoretokens()) { array_points[k]=double.parsedouble(itr.nexttoken()); k++; //get coordinate cluster double nearestdistanceout=double.max_value; double nearestdistance = 100; double[] array_cluster = new double[num_coord]; String cluster=null; Set<String> list = meanscluster.keyset(); for(string s:list) { array_cluster = meanscluster.get(s); double distance = KmeansUtil.getEuclideanDistance( array_cluster, array_points); if (distance < nearestdistance) { nearestdistance = distance; cluster=s; nearestdistanceout=nearestdistance; context.write(new Text(cluster), new Text(KmeansUtil.getString(array_points))); KmeansCombiner.java import java.io.ioexception; import java.util.stringtokenizer; import java.util.vector; import org.apache.hadoop.io.text; import org.apache.hadoop.mapreduce.reducer; public class KmeansCombiner extends Reducer<Text,Text,Text,Text> { public void reduce(text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { int num_coord=integer.parseint(context.getconfiguration().get("my.num.coord")); int i; // all points vector Vector<double[]> all_points_vector = new Vector<double[]>(); for (Text val : values) { 30
// single vector of points only one centroid double[] vec_point_by_key= new double[num_coord]; StringTokenizer itr = new StringTokenizer(val.toString()); i=0; while(itr.hasmoreelements()) { vec_point_by_key[i]=double.parsedouble(itr.nextelement().tostring()); i++; all_points_vector.add(vec_point_by_key); double sum; double[] vec_point_get_by_key= new double[num_coord]; double[] centroid_cluster_partial_sum = new double[num_coord]; int cont=0; for( int j=0; j<num_coord;j++){ cont=0; sum=0; vec_point_get_by_key= new double[num_coord]; for(int k=0; k<all_points_vector.size(); k++){ vec_point_get_by_key=(double[])all_points_vector.get(k); sum+= vec_point_get_by_key[j]; cont++; centroid_cluster_partial_sum[j]=sum; context.write(key, new Text(Integer.toString(cont)+ " "+ KmeansUtil.getStringPadding(centroid_cluster_partial_sum))); KmeansReducer.java import java.io.ioexception; import java.util.stringtokenizer; import java.util.vector; import java.util.iterator; import org.apache.hadoop.io.text; import org.apache.hadoop.mapreduce.reducer; 31
public class KmeansReducer extends Reducer<Text,Text,Text,Text> { public void reduce(text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { int num_coord=integer.parseint(context.getconfiguration().get("my.num.coord")); // Double vector all points of certain centroid Vector<double[]> all_points_vector = new Vector<double[]>(); int i; int total_points=0; for( Text val : values){ StringTokenizer itr = new StringTokenizer(val.toString()); //sum number of element total_points+= Integer.parseInt(itr.nextToken()); double[] vec_point_by_key= new double[num_coord]; i=0; while(itr.hasmoreelements()) { vec_point_by_key[i]=double.parsedouble(itr.nextelement().tostring()); i++; all_points_vector.add(vec_point_by_key); double sum; double[] vec_point_get_by_key= new double[num_coord]; double[] new_centroid_cluster = new double[num_coord]; for(int j=0; j<num_coord;j++){ sum=0; vec_point_get_by_key= new double[num_coord]; for(int k=0; k<all_points_vector.size(); k++){ vec_point_get_by_key=(double[])all_points_vector.get(k); sum+= vec_point_get_by_key[j]; new_centroid_cluster[j]= sum/total_points; context.write(key, new Text(KmeansUtil.getStringPadding(new_centroid_cluster))); 32