SlideShare a Scribd company logo
Facoltà di Ingegneria Informatica Automatica e Gestionale
Corso di Laurea Triennale in Ingegneria Dei Sistemi Informatici
Tesi di Laurea
Implementazione e valutazione di un algoritmo per il
partizionamento di grafi su piattaforma SPARK
Relatore: Laureando:
Prof. Leonardo Querzoni Andrea Cingolani
Matricola: 1477231
Anno accademico 2016/2017
4
Indice
Introduzione................................................................................................................................ 5
Capitolo 1: L’algoritmo HDRF ............................................................................................... 10
Capitolo 2: Strumenti usati per l’analisi................................................................................ 13
2.1: Apache Spark................................................................................................................. 13
2.1.1: Caratteristiche di Apache Spark........................................................................... 13
2.1.2: Spark Core............................................................................................................... 14
2.1.3: Spark SQL................................................................................................................ 15
2.1.4: Spark Streaming ..................................................................................................... 15
2.1.5: MLlib........................................................................................................................ 15
2.1.6: GraphX..................................................................................................................... 16
2.4: Scala................................................................................................................................. 21
Capitolo 3: Realizzazione e implementazione del progetto............................................... 22
3.1: Acquisizione del file di testo da utilizzare come grafo............................................ 22
3.2: Creazione del grafo....................................................................................................... 22
3.3 Implementazione algoritmo.......................................................................................... 24
Capitolo 4 – Validazione e Test.............................................................................................. 33
Capitolo 5: Conclusioni ........................................................................................................... 34
Bibliografia................................................................................................................................ 35
5
Introduzione
Negli ultimi anni abbiamo assistito a un'enorme crescita di produzioni di
informazioni.
Alcune società come IBM stimano che "ogni giorno vengono creati 2,5
quintilioni di byte di dati", pari al 90% dei dati nel mondo creati negli
ultimi due anni.
Di fronte a questa crescita, i ricercatori del mondo accademico e
industriale si sono concentrati per la progettazione di nuovi, efficienti,
approcci per analisi parallela dei dati in grado di resistere al diluvio di dati
atteso nei prossimi anni. Data la proliferazione di dati che possono essere
rappresentati come grafici di vertici interconnessi, un paradigma di
calcolo basato sul grafico fornisce un'astrazione piacevole, adatta, per
eseguire calcoli su di esso.
Grandi quantità di dati, in particolare grafici a invarianza di scala o grafici
power-law rientrano in questo paradigma. Inoltre, il calcolo basato sui
grafici trova applicazione in molti campi diversi e importanti come i social
network, la biologia, la chimica e la sicurezza informatica. Un problema
chiave nel calcolo dei grafici è che è spesso difficile scalare con l’aumento
delle dimensioni dei dati in input poiché i grafici non sono facilmente
partizionabili in sotto grafi indipendenti che possono essere calcolati in
parallelo.
Per poter lavorare su dataset di grandi dimensioni, framework
Graphcomputing (DGC) distribuiti (come GraphLab o Pregel) separa
forzatamente il grafo di input posizionando i suoi elementi costituenti,
siano essi vertici o bordi, in partizioni distinte, una per ciascuna risorsa di
6
calcolo disponibile. Durante la fase di partizionamento, gli elementi di dati
che condividono le connessioni con altri elementi già posizionati in altre
partizioni risultano avere connessioni remote tra di loro. Poiché queste
partizioni vengono solitamente posizionate su macchine diverse, ciò può
comportare una rete non necessaria o eccessiva e costi di calcolo.
Per risolvere questo problema, frequentemente la tecnica utilizzata è
quella di creare e posizionare localmente delle repliche di dati connessi in
remoto tra queste partizioni. Mentre questo riduce il costo di accesso, gli
elementi di dati replicati devono essere sincronizzati durante il calcolo in
modo da evitare che gli stati di replica divergano e generino risultati di
calcolo privi di significato. Questa sincronizzazione può ostacolare in
modo significativo le prestazioni poiché costringe le repliche a coordinare
e scambiare dati più volte durante il calcolo.
Il modo in cui il set di dati di input è partizionato ha un grande impatto
sulle prestazioni del calcolo del grafico. Una strategia di partizionamento
ingenua può finire per replicare una grande parte degli elementi di input
su diverse partizioni, intralciando gravemente le prestazioni inducendo
un over head di sincronizzazione delle repliche di grandi dimensioni
durante la fase di calcolo. Inoltre, la fase di partizionamento dovrebbe
produrre partizioni bilanciate in modo uniforme (cioè partizioni con
dimensioni simili) per evitare possibili distorsioni del carico in un cluster
di macchine su cui i dati sono partizionati. Diversi approcci recenti hanno
esaminato questo problema. Qui focalizziamo la nostra attenzione su
algoritmi di partizionamento grafico, ovvero gli algoritmi che
partizionano gli elementi in entrata uno alla volta sulla base delle sole
proprietà dell'elemento corrente e su assegnazioni precedenti alle
7
partizioni (nessuna conoscenza globale sul grafico di input). Inoltre, questi
algoritmi sono solitamente one-pass, cioè si astengono dal modificare
l'assegnazione di un elemento di dati ad una partizione una volta fatto
questo. Questi algoritmi sono i candidati ideali nelle impostazioni in cui le
dimensioni dei dati di input e i vincoli sulle risorse disponibili limitano il
tipo di soluzioni che può essere impiegato.
Altre caratteristiche dei dati di input svolgono anche un ruolo importante
nel partizionamento. È stato dimostrato che gli algoritmi vertex-cut sono
l'approccio migliore per gestire grafici di input caratterizzati da
distribuzioni di gradi di power-law. Questo lavoro precedente ha anche
delineato chiaramente l'importante ruolo svolto dai nodi ad alta
gradazione dal punto di vista della qualità del partizionamento. Tuttavia,
pochi algoritmi tengono conto di questo aspetto. Comprensibilmente,
questo è un problema complesso da risolvere per gli approcci basati sul
flusso a causa della loro natura a un solo passaggio. In questo progetto,
facciamo leva sull'idea che un algoritmo di partizionamento dovrebbe fare
del suo meglio per tagliare, cioè replicare, vertici con grado elevato. In
particolare, introduciamo High Degree (are) Replicated First (HDRF), un
algoritmo di partizionamento grafico basato su un avido vertex-cut che
sfrutta le informazioni sui gradi dei vertici. HDRF è caratterizzato dalle
seguenti proprietà desiderabili:
8
Emette partizioni con il più piccolo fattore di replicazione medio tra tutte
le soluzioni concorrenti quando vengono applicate grafici power-law
mentre fornisce un bilanciamento del carico quasi perfetto.
Il primo è ottenuto replicando avidamente i vertici con gradi maggiori,
mentre quest'ultimo è fornito da un termine di bilanciamento
parametrizzabile il cui impatto può essere regolato per adattare il
comportamento dell'algoritmo a qualsiasi ordine di input dei dati. Da un
lato, ridurre il fattore di replicazione medio è importante per ridurre il
costo della larghezza di banda della rete, l'utilizzo della memoria e il
sovraccarico della sincronizzazione delle repliche a tempo di calcolo.
Un'equa distribuzione del carico sulle partizioni, d'altra parte, consente un
utilizzo più efficiente delle risorse di calcolo disponibili. HDRF tiene conto
9
di entrambi questi aspetti in modo integrato, riducendo significativamente
il tempo necessario per eseguire calcoli su grafici su larga scala. Nei
capitoli successivi spiegheremo cosa è più nel dettaglio l’algoritmo HDRF,
cosa abbiamo usato per implementarlo, e infine tutto il procedimento
svolto.
10
Capitolo 1: L’algoritmo HDRF
L’algoritmo HDRF è un algoritmo adatto ai grafi power-law. Due
informatici hanno dimostrato che solo alcuni vertici di grado alto vengono
rimossi da un grafo power-law, e vengono trasformati in un gruppo di
cluster isolati. Inoltre nei grafici power-law la distribuzione dei coefficienti
di cluster diminuisce con l’aumento del grado dei vertici. Ciò implica che
i vertici a basso grado appartengono ai sottogruppi molto densi e quei
sotto-grafi sono collegati tra loro attraverso vertici di grado alto. Questo
schema di partizionamento sfrutta queste proprietà concentrandosi sulle
località dei vertici di basso grado. In particolare, cerca di posizionare
ciascun elemento fortemente connesso con i vertici di basso grado in una
singola partizione, tagliando i vertici di alto grado e replicandoli su un
gran numero di partizioni. Poiché il numero dei vertici di alto grado nei
grafici power-law è molto basso, incoraggiare la replica per questi vertici
porta ad una riduzione complessiva del fattore di replica. In concreto,
quando HDRF crea una replica, lo fa per il vertice con massimo grado.
Tuttavia, ottenere gradi di vertice per un grafico che viene consumato in
streaming non è banale. Per evitare il sovraccarico di un passo di pre-
elaborazione (dove il grafo di input viene scandito per calcolare i gradi
esatti del vertice) è possibile mantenere una tabella con gradi parziali dei
vertici che viene aggiornata mentre l’input viene analizzato. Poiché ogni
nuovo bordo viene considerato nell’input, i valori di gradazione dei vertici
corrispondenti vengono aggiornati nella tabella. I valori di grado parziale
raccolti in fare di run-time sono di solito un buon indicatore per il grado
11
di un vertice poiché è più probabile che un bordo osservato appartiene ad
un vertice di alto grado, piuttosto che a uno di basso grado.
Matematicamente quando elaborano i vertici vi e vj di bordo appartenente
ad E (bordi), l’algoritmo HDRF recupera i loro gradi parziali e li
incrementa di uno. Sia (vi) il grado parziali di vi e (vj) quelli di vj. I valori
di grado vengono normalizzati in modo da arrivare ad uno:
Per quanto riguarda l’elogio euristico, l’algoritmo HDRF calcola un
punteggio CHDRF (vi, vj, p) per tutte le partizioni p appartenenti a P e poi
assegna e alla partizione p* che massimizza CHDRF. Il punteggio per ogni
partizione p appartenente a P è definito come segue:
Il parametro λ consente di controllare l’entità dello squilibrio di
dimensione delle partizioni nel calcolo dei punteggi. È stato introdotto
perché equilibra partizioni particolarmente squilibrate. Per vedere questo
problema nota che CBAL
greedy
(p) è sempre < 1 mentre CREP
greedy
e CREP
HDRF
sono
sempre o 0 o > 1. Per questa ragione, il termine di equilibrio CBAL va messo
o nell’algoritmo greedy o quando 0 < λ <= 1 è usato solo per scegliere tra
partizione che presentano lo stesso valore per il termine di replica CREP,
12
rompendo così la simmetria. Tuttavia, questo potrebbe non essere
sufficiente per garantire il saldo del carico. Ad esempio, se il flusso di bordi
è ordinato secondo un ordine di visita sul grafo (ad esempio la prima
ricerca di larghezza e profondità), quando si elaborano i vertici vi e vj di
bordo e appartenente ad E, c’è sempre una singola partizione p* con
CREP
Greedy (vi, vj, p*) >= 1 (o CHDRF
REP(vi,vj,p) > 1) e tutte le altre partizioni p
appartenente a P se p =! p* hanno CREP
Greedy (vi,vj,p*) = 0 (o CHDRF
REP(vi,vj,p) =
0). In questo caso il termine di equilibrio è inutile in quanto non esiste una
simmetria per rompere l’euristica, finisce per mettere tutti i bordi in una singola
partizione p*. Questo problema può essere risolto impostando un valore λ > 1.
In questa valutazione si è studiato l’andamento del fattore di replicazione
e il bilanciamento del carico variando λ. Inoltre, da notare quando λ -> ∞
l’algoritmo somiglia a un euristico casuale, dove le osservazioni passate
sono ignorate e contano solo partizioni di uguale dimensione. HDRF può
essere eseguito come un singolo processo o in istanze parallele per
aumentare la velocità della fase di partizionamento. Come per i greedy,
HDRF ha anche bisogno di uno stato da condividere tra istanze parallele
durante la partizione.
13
Capitolo 2: Strumenti usati per l’analisi
Il progetto è stato realizzato con il framework Apache Spark su sistema
Linux Lubuntu 17.04 programmato in Scala. Nei prossimi paragrafi viene
spiegato il funzionamento sia di Scala che di Spark, descrivendo alcune
delle librerie da noi usate di Spark.
2.1: Apache Spark
Apache Spark è una piattaforma di calcolo a cluster costruita per essere
veloce e multiuso. Il suo obiettivo è di eseguire più attività possibile su un
unico sistema dove prima questo non era possibile. Alcune di esse sono:
• Streaming di dati
• Query interattive
• Algoritmi iterativi
• Applicazioni di batch
Supportando tutti questi carichi di lavoro su un'unica macchina, Spark
rende semplice e poco costoso la combinazione di più processi.
2.1.1: Caratteristiche di Apache Spark
Nel suo core, Spark offre un motore di computazione che viene sfruttato
da tutti gli altri suoi componenti. Questo significa che un’ottimizzazione
al core di Spark si manifesta su tutte le applicazioni che usato questo
framework. Il core di Spark si occupa di:
• Scheduling
14
• Distribuzione
• Controllo dell’esecuzione dell’applicazione dell’utente
A sfruttare il core di ci sono vari tools:
• Spark SQL
• Spark Streaming
• MLlib
• GraphX
Essi si integrano tra di loro, facendo sì che un’applicazione possa usare
tutti i componenti di Spark contemporaneamente. Tutti i moduli di Spark
sono di seguito descritti.
2.1.2: Spark Core
Lo Spark Core contiene le funzionalità base di Spark, includendo:
• Componenti della pianificazione
• Management di memoria
• Recupero dei guasti
• Interazione con i sistemi di memorizzazione
15
Lo Spark Core include le API che definiscono gli RDD (Resilient
Distributed Datasets) che rappresentano dati distribuiti sul cluster e su cui
vengono svolte operazioni di trasformazione per poi recuperare i dati
modificati
2.1.3: Spark SQL
Spark SQL permette di fare interrogazioni su dati strutturati e semi
strutturati usando HiveQL, una variante del linguaggio SQL. Si possono
interrogare file JSON, file di testo e qualunque altro formato supportato
da Hive.
2.1.4: Spark Streaming
Spark Streaming permette di analizzare i flussi di dati in tempo reale come
i log di errori o un flusso di tweet. Un esempio di utilizzo è l’analisi di file
di log generati da server web, come:
• Filtraggio di particolari indirizzi IP in funzione della sicurezza
• L’analisi degli aggiornamenti di stato degli utenti su una
piattaforma social
2.1.5: MLlib
Spark nasce con una libreria che contiene le più comuni funzionalità di
Machine Learning (ML), chiamata MLlib.
Essa offre vari algoritmi di machine learning che includono:
• Classificazione
16
• Regressione
• Clustering
• Collaborative filtering
• Modelli di valutazione e acquisizione dati
Questi algoritmi sono eseguiti sugli RDD.
Gli algoritmi di MLlib vengono eseguiti in parallelo, ed hanno
performance ottime sui cluster. La libreria è valida quando si usano questi
algoritmi su dataset molto grandi.
2.1.6: GraphX
GraphX è una libreria usata per l’analisi dei grafi talmente grandi che non
potrebbero essere analizzati da una singola macchina (ad esempio i grafi
dei social network). Un grafo è una collezione di nodi (o vertici) collegati
da archi. Ad esempio i nodi possono essere le persone e gli archi le
amicizie. La libreria offre algoritmi come PageRank (per misurare
l’importanza di ogni nodo di un grafo), il calcolo delle componenti
connesse, il calcolo dei triangoli e molto altro.
2.1.6.1 Partition Strategy
Per elaborare il grafo in uno stile distribuito, il grafo ha bisogno di essere
rappresentato in uno schema distribuito. Normalmente, ci sono due tipi
17
di grafi partizionati, l’approccio vertex-cut e l’approccio edge-cut.
Spark Graphx adotta un vertex-cut per partizionare dei grafi distribuiti.
La strategia è programmata nel PartitionStrategy.scala. Guardiamo questo
file:
1. case object RandomVertexCut extends PartitionStrategy{
2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part
itionID): PartitionID = {
3. math.abs((src, dst).hashCode()) % numParts
}
RandomVertexCut calcola il valore hash degli ID Vertex di origine e
destinazione, usando il modulo (da numberOfParts) come IP partizione
del bordo. I bordi partizionati nella stessa partizione di due vertici hanno
la stessa direzione.
CanonicalRandomVertexCut partiziona i bordi indipendentemente dalla
direzione
1. case object CanonicalRandomVertexCut extends PartitionStrategy {
18
2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part
itionID): PartitionID = {
3. if (src < dst) {
4. math.abs((src, dst).hashCode()) % numParts
5. } else {
6. math.abs((dst, src).hashCode()) % numParts
7. }
8. }
9. }
Altri due schemi di partizionamento sono EdgePartition1D e
EdgePartition2D. In EdgePartition1D, i bordi vengono assegnati alle
partizioni solo in base ai loro vertici di origine.
1. case object EdgePartition1D extends PartitionStrategy {
2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part
itionID): PartitionID = {
3. val mixingPrime: VertexId = 1125899906842597L
4. (math.abs(src * mixingPrime) % numParts).toInt
5. }
6. }
Un principio molto grande (mixingPrime) viene usato per bilanciare le
partizioni. Ma questa operazione non elimina completamente il problema.
EdgePartition2D è un po’ più complesso. Usa sia il vertice di origine che
quello di destinazione per calcolare la partizione. È basato sulla matrice di
adiacenza del bordo sparso. Ecco un esempio estratto dal codice sorgente.
19
Supponiamo di avere un grafo con 12 vertici che vogliamo dividere su 9
macchine. Possiamo usare la seguente rappresentazione di matrice sparsa:
Come si vede, E<v11, v1> è partizionato nel P6. Ma è anche chiaro che P0
contiene troppi bordi (molti di più di altre partizioni) che provocano uno
squilibrio di partizionamento. Così mixingPrime viene usato anche in
EdgePartition2D.
1. case object EdgePartition2D extends PartitionStrategy {
2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part
itionID): PartitionID = {
3. val ceilSqrtNumParts: PartitionID = math.ceil(math.sqrt(numParts))
.toInt
4. val mixingPrime: VertexId = 1125899906842597L
5. val col: PartitionID = (math.abs(src * mixingPrime) % ceilSqrtNumP
arts).toInt
20
6. val row: PartitionID = (math.abs(dst * mixingPrime) % ceilSqrtNum
Parts).toInt
7. (col * ceilSqrtNumParts + row) % numParts
8. }
9. }
Guarda questo grafico realistico.
I vertici A, B e C sono in una partizione; D, E, F sono nell’altra. I bordi sono
suddivisi in due partizioni come nel Edge Table. La tabella di
instradamento è molto utile perché registra lo stato di taglio dei vertici.
21
2.4: Scala
Scala è un linguaggio di programmazione funzionale orientato ad oggetti.
Compilando un sorgente scala viene generato un bytecode eseguibile in
una Java Virtual Machine. Esso fornisce un linguaggio diretto per definire
funzioni anonime (dichiarate senza essere legate ad un nome), supporta
funzioni di ordine superiore e permette alle funzioni di essere annidate e
supporta funzioni parziali. Scala ha supporto nativo per il pattern
matching, permette di potenziale l’elaborazione di dati XML con il
supporto di espressioni regolari. Il suo nome deriva da “Scalable
Language” ovvero linguaggio scalabile. Ciò che rende Scala un linguaggio
scalabile è la fusione tra programmazione funzionale e programmazione
ad oggetti.
22
Capitolo 3: Realizzazione e
implementazione del progetto
Per svolgere il progetto sono passato per diverse fasi:
1. Acquisizione del file di testo da utilizzare come grafo
2. Creazione del grafo
3. Implementazione algoritmo
a. Caso 1: Vertici non assegnati alle partizioni
b. Caso 2: Solo un vertice assegnato
c. Caso 3: Vertici assegnati, partizione comune
d. Caso 4: Vertici assegnati a due partizioni diverse
3.1: Acquisizione del file di testo da utilizzare come grafo
Il file di testo è un file scaricato da
http://files.grouplens.org/datasets/movielens/ml-10m.zip. Il file zip al suo
interno contiene vari file, tra cui ratings.dat che è il file che ho usato per
formare il grafico, questo file al suo interno contiene:
10 milioni di valutazioni fatte da 72000 utenti su 10000 film presi dal
famosissimo servizio MovieLens. I dati sono ordinati casualmente.
3.2: Creazione del grafo
Per la creazione del grafo ci sono stati vari problemi sulla formattazione
del file, inizialmente avevamo pensato di usare la classe di GraphX
GraphLoader con al suo interno il metodo:
23
1. edgeListFile(SparkContext sc, String path, boolean canon
icalOrientation, int numEdgePartitions, StorageLevel edg
eStorageLevel, StorageLevel vertexStorageLevel)
ma non avrebbe funzionato, perché essa carica un grafico da un file
formattato in un elenco di bordi in cui ogni riga contiene due numeri: un
ID sorgente e un ID destinazione, noi avevamo un file formattato in una
maniera diversa.
Il file nostro era formattato in questa maniera: un ID utente, un ID film, il
voto che l’utente ha dato al film e un numero chiamato Timestamp che a
noi non serviva, tutto diviso da un t. Il modo più facile per formattare il
file in modo da creare dei bordi automaticamente era questo:
1. val edges:RDD[Edge[String]]=
2. sc.textFile("data/u1.base").map{ line =>
3. val fields= line.split("t")
4. Edge(fields(0).toLong,fields(1).toLong,fields(2)
)
5. }
Ora una volta creati i bordi il lavoro era all’80% dell’opera, mancava solo
creare il grafo.
Per creare il grafo abbiamo usato la classe Graph con al suo interno il
metodo:
1. fromEdges[VD, ED](edges: RDD[Edge[ED]],defaultValue: VD)
: Graph[VD, ED]
in questo modo:
24
1. val graph: Graph[Any,String] =Graph.fromEdges(edges,"def
aultProperty")
Il grafico era creato, per controllare che bordi e vertici erano stati creati
abbiamo usato graph.numEdges e graph.numVertices in modo da capire
anche quanti erano sia i bordi che i vertici.
3.3 Implementazione algoritmo
Per l’implementazione dell’algoritmo ho usato la classe PartitionStrategy
di GraphX e l’ho modificata. Nel Main ho creato il valore partition che è
un PartitionID ed ho usato partitionBy in questo modo:
1. val partition= graph.partitionBy(HDRF,numPartition)
Mentre in HDRF.scala sono andato ad aggiungere l’estensione
PartitionStrategy, qui dentro ho implementato tutto l’algoritmo.
L’implementazione dell’algoritmo ha seguito questi 4 casi:
a) Caso 1: Se nessuno dei due vertici di e è stato mai inserito in qualche
partizione
Scelgo la partizione più scarica e li assegno a quella
b) Caso 2: Se uno dei due vertici è già presente in almeno una partizione
25
Scelgo la partizione più scarica tra quelle in cui è presente almeno un
vertice e ci replico l’altro vertice
c)Caso 3: Entrambi i vertici sono presenti in diverse partizioni ed esiste
un’intersezione dei set non nulla (cioè esiste almeno una partizione che li
contiene entrambi)
Scelto nell’intersezione dei set la partizione più scarica
d) Caso 4: Entrambi i vertici sono presenti in diverse partizioni ma
l’intersezione dei set è nulla (cioè non esiste alcuna partizione che li
contiene entrambi)
Scelgo tra le partizioni a cui è assegnato uno dei due, in quella più scarica
ci copio l’altro vertice
26
Come ultima cosa, una volta partizionato il grafico ho calcolato il fattore
di replicazione medio e la deviazione standard relativa.
Questa è la porzione di codice di come ho implementato il tutto:
1. package app
2.
3.
4. import org.apache.spark.graphx._
5.
6. import scala.collection.concurrent.TrieMap
7.
8. object HDRF extends PartitionStrategy{
9. private var init=0; //lo puoi usare per controllare una fase di inizializzaz
ione che viene eseguita solo la prima volta
10.
11. private var partitionsLoad:Array[Long] = Array.empty[Long] //carico (n
umero di archi) di ogni partizione
12. private val vertexIdListPartitions: TrieMap[Long, List[AnyVal]] = Trie
Map.empty[Long,List[AnyVal]] //lista di partizioni associate a ogni verti
ce
13. private val vertexIdEdges: TrieMap[Long, Long] = TrieMap() //grado di
ogni vertice
14.
15. private var edges = 0
16.
17. override def getPartition(src:VertexId, dst:VertexId, numParts:Int): Parti
tionID ={
18. var valoreMax:Long =Int.MaxValue
19. var partScarica:Int = -1
20. if(init==0){
21. init=1
22. partitionsLoad=Array.fill[Long](numParts)(0)
23.
27
24. }
25.
26.
27. //AGGIORNA IL GRADO CONOSCIUTO DEI VERTICI src E dst NEL
LA VARIABILE vertexIdEdges
28.
29. vertexIdEdges.update(src,+1)
30. vertexIdEdges.update(dst,+1)
31.
32. //PARTIZIONA IL GRAFO
33. if((!vertexIdListPartitions.contains(src))&&(!vertexIdListPartitions.con
tains(dst))){
34. //NESSUNO DEI DUE VERTICI E' STATO MAI INSERITO IN QUAL
CHE PARTIZIONE
35. //SCELGO LA PARTZIIONE PIU' SCARICA E LI ASSEGNO A QUEL
LA
36. for(c<-0 until numParts){
37. if(partitionsLoad(c)<valoreMax){
38. valoreMax=partitionsLoad(c)
39. partScarica=c
40. }
41. }
42. if(partScarica != -1) {
43. partitionsLoad(partScarica)=partitionsLoad(partScarica)+1
44. vertexIdListPartitions.put(src, List(partScarica))
45. vertexIdListPartitions.put(dst, List(partScarica))
46. //vertexIdListPartitions.update(src, vertexIdListPartitions.getOrElse(
src, List()) ++ List(partScarica))
47. //vertexIdListPartitions.update(dst, vertexIdListPartitions.getOrElse(
dst, List()) ++ List(partScarica))
48. }
49.
50. }else if((vertexIdListPartitions.contains(src) &&(!vertexIdListPartitions
.contains(dst)))||((!vertexIdListPartitions.contains(src))&& vertexIdListP
artitions.contains(dst))){
51. //UNO SOLO DEI DUE VERTICI E' GIA' PRESENTE IN ALMENO U
NA PARTIZIONE
52. if(vertexIdListPartitions.contains(src) &&(!vertexIdListPartitions.cont
ains(dst))){
53. //SI TRATTA DI src
28
54. //SCELGO LA PARTIZIONE PIU' SCARICA TRA QUELLE IN CUI
E' PRESENTE src E CI REPLICO dst
55. for(c<- 0 until numParts){
56. if(partitionsLoad(c)<valoreMax){
57. if(vertexIdListPartitions(src).contains(c)) {
58. valoreMax = partitionsLoad(c)
59. partScarica = c
60. }
61. }
62. }
63. if(partScarica != -1) {
64. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1
65. vertexIdListPartitions.put(dst, List(partScarica))
66. //vertexIdListPartitions.update(dst, vertexIdListPartitions.getOrEls
e(dst, List()) ++ List(partScarica))
67. }
68.
69. }else{
70. //SI TRATTA DI dst
71. //SCELGO LA PARTZIIONE PIU' SCARICA TRA QUELLE IN CUI
E' PRESENTE dst E CI REPLICO src
72.
73. for(c<- 0 until numParts){
74. if(partitionsLoad(c)<valoreMax){
75. if(vertexIdListPartitions(dst).contains(c)) {
76. valoreMax = partitionsLoad(c)
77. partScarica = c
78. }
79. }
80. }
81. if(partScarica != -1) {
82. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1
83. vertexIdListPartitions.put(src, List(partScarica))
84. //vertexIdListPartitions.update(src, vertexIdListPartitions.getOrEls
e(src, List()) ++ List(partScarica))
85. }
86.
87. }
88. }else if(vertexIdListPartitions(src).intersect(vertexIdListPartitions(dst))
.nonEmpty){
29
89. //ENTRAMBI I VERTICI SONO PRESENTI IN DIVERSE PARTIZION
I ED ESISTE UNA INTERSEZIONE DEI SET NON NULLA (CIOE' ESIST
E ALMENO UNA PARTIZIONE CHE LI CONTIENE ENTRAMBI)
90. //SCELGO NELL'INTERSEZIONE DEI SET LA PARTIZIONE PIU' S
CARICA
91. for(c<- 0 until numParts){
92. if (partitionsLoad(c) < valoreMax) {
93. if (vertexIdListPartitions(src).contains(c) && vertexIdListPartitions
(dst).contains(c)) {
94. valoreMax = partitionsLoad(c)
95. partScarica = c
96. }
97. }
98. }
99. if(partScarica != -1) {
100. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1
101. }
102.
103. }else {
104. //ENTRAMBI I VERTICI SONO PRESENTI IN DIVERSE PART
IZIONI MA L'INTERSEZIONE DEI SET E' NULLA (CIOE' NON ESISTE
ALCUNA PARTIZIONE CHE LI CONTIENE ENTRAMBI)
105. if(vertexIdEdges(src) >= vertexIdEdges(dst)){
106. //SCELGO TRA LE PARTIZIONI A CUI E' ASSEGNATO dst
QUELLA PIU' SCARICA E CI COPIO src
107.
108. for(c<-0 until numParts){
109. if(partitionsLoad(c)<valoreMax){
110. if(vertexIdListPartitions(dst).contains(c)) {
111. valoreMax = partitionsLoad(c)
112. partScarica = c
113. }
114. }
115. }
116. if(partScarica != -1) {
117. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1
118. vertexIdListPartitions.update(src, vertexIdListPartitions(src).
union(List(partScarica)))
119. //vertexIdListPartitions.update(src, vertexIdListPartitions.get
OrElse(src, List()) ++ List(partScarica))
30
120.
121. }
122.
123. }else{
124. //SCELGO TRA LE PARTIZIONI A CUI E' ASSEGNATO src
QUELLA PIU' SCARICA E CI COPIO dst
125.
126. for(c<-0 until numParts){
127. if(partitionsLoad(c)<valoreMax){
128. if(vertexIdListPartitions(src).contains(c)) {
129. valoreMax = partitionsLoad(c)
130. partScarica = c
131. }
132. }
133. }
134. if(partScarica != -1) {
135. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1
136. vertexIdListPartitions.update(dst, vertexIdListPartitions(dst)
.union(List(partScarica)))
137. //vertexIdListPartitions.update(dst, vertexIdListPartitions.ge
tOrElse(dst, List()) ++ List(partScarica))
138. }
139. }
140.
141. }
142.
143. edges=edges+1
144. if(edges==10000054){
145. //Calcolo il fattore di replicazione medio dei vertici
146. //vertexIdListPartitions.foreach(println)
147. var fattore_replicazione:Double = 0
148. for ((_,v) <- vertexIdListPartitions) fattore_replicazione += v.siz
e
149. fattore_replicazione /= vertexIdListPartitions.size
150. //Calcolo il carico medio delle partizioni
151. var carico_medio:Double = 0
152. for (l <- partitionsLoad) carico_medio += l
153. //Calcolo la relativa devizaione standard
154. var tmp:Double = 0
31
155. for (l <- partitionsLoad) tmp += scala.math.pow(l - carico_medi
o, 2)
156. tmp /= numParts-1
157. val stdev = scala.math.sqrt(tmp)
158. //Calcolo la devizione standard relativa
159. val rsd = 100 * stdev / carico_medio
160. println("n ---* RISULTATI DEL PARTIZIONAMENTO *---
n")
161. println("tFattore di replicazione medio: " + fattore_replicazion
e)
162. println("tDeviazione standard relativa: " + rsd)
163. }
164. partScarica
165. }
166. }
Di seguito c’è anche il Main:
1. package app
2.
3. import org.apache.spark.graphx._
4. import org.apache.spark._
5. import org.apache.spark.rdd.RDD
6.
7. object Main{
8.
9.
10. def main(args: Array[String]) {
11.
12. val sc = new SparkContext(new SparkConf().setMaster("local").setApp
Name("HDRF"))
13.
14. // mostra solo i log in caso di errore
32
15. sc.setLogLevel("ERROR")
16.
17. //modifico il file di testo preso in ingresso
18. val edges:RDD[Edge[String]]=
19. sc.textFile("data/ratings.dat").map{ line =>
20. val fields= line.split("::")
21. Edge(fields(0).toLong,fields(1).toLong,fields(2))
22. }
23.
24. val graph: Graph[Any,String] =Graph.fromEdges(edges,"defaultPrope
rty")
25. println("Benvenuto nel partizionatore HDRF:")
26. println("Il file scelto è: ratings.dat")
27. println("Esso contiene:")
28. println("Un numero di vertici pari a: "+ graph.numVertices)
29. println("e un numero di nodi pari a: "+ graph.numEdges)
30. println()
31. println("Partizionamento in corso...")
32. println("...")
33. println("...")
34. val result=graph.partitionBy(HDRF,4)
35.
36. result.edges.count()
37. }
38. }
33
Capitolo 4 – Validazione e Test
Per i test abbiamo usato IntelliJIDEA sul mio PC con Linux Lubuntu e il
risultato è il seguente:
1. Benvenuto nel partizionatore HDRF:
2. Il file scelto è : ratings.dat
3. Esso contiene:
4. Un numero di vertici pari a: 70155
5. e un numero di nodi pari a: 10000054
6.
7. Partizionamento in corso...
8. ...
9. ...
10.
11. ---* RISULTATI DEL PARTIZIONAMENTO *---
12.
13. Fattore di replicazione medio: 2.6129855320362054
14. Deviazione standard relativa: 88.19171036899517
34
Capitolo 5: Conclusioni
• HDRF è un algoritmo di partizionamento grafico dei vertici a
passaggio singolo.
• Mi sono basato su un approccio vertex-cut greedy che sfrutta
informazioni sui gradi dei vertici
• In questa tesi forniamo un’analisi teorica e pratica dell’algoritmo
HDRF con un limite superiore del caso medio per il fattore di
replicazione dei vertici.
• Gli esperimenti studiati mostrano che:
o HDRF fornisce il fattore di replica più piccola e vicina al
bilanciamento di carico ottimale
o HDRF riduce in modo significativo il tempo necessario per
eseguire calcolo su grafici
35
Bibliografia
[1] Apache Spark
https://spark.apache.org/docs/latest/
[2] Yuhc
Partition Strategy in GraphX – Simple Note
http://note.yuhc.me/2015/03/graphx-partition-strategy/
[3] Gianluca Tartaggia
Linguaggio di programmazione Scala
http://tesi.cab.unipd.it/40851/1/Tesina.pdf
[4] Camil Demetrescu
Dispensa programmazione funzionale
https://docs.google.com/document/d/1M1EtsCXsbIdqXLAF4dxy2ppLIBeX5Y3gdany-
MYx_9A/edit?pref=2&pli=1#
[5] Tutorialspoint
Apache Spark installation and Core Programming
https://www.tutorialspoint.com/apache_spark/apache_spark_installation.htm
https://www.tutorialspoint.com/apache_spark/apache_spark_core_programming.htm
[6] Mapr
How to get started using Apache Spark Graphx Scala
https://mapr.com/blog/how-get-started-using-apache-spark-graphx-scala/
[7] Ampcamp
Graph Analytics with Graphx
36
http://ampcamp.berkeley.edu/big-data-mini-course/graph-analytics-with-
graphx.html
[8] Srini Penchikala – InfoQ
Graph Data Analytics with Apache Spark
https://www.infoq.com/articles/apache-spark-graphx
[9] Lorenzo Gatto
Analisi e valutazione della piattaforma Spark
http://amslaurea.unibo.it/8876/1/gatto_lorenzo_tesi.pdf

More Related Content

Similar to Tesi andrea cingolani

Build a LINQ-enabled Repository
Build a LINQ-enabled RepositoryBuild a LINQ-enabled Repository
Build a LINQ-enabled Repository
Andrea Saltarello
 
La metodologia statistica nel data mining
La metodologia statistica nel data miningLa metodologia statistica nel data mining
La metodologia statistica nel data mining
Francesco Tamburini
 
Extended summary of code building genetic programming
Extended summary of code building genetic programmingExtended summary of code building genetic programming
Extended summary of code building genetic programming
MartinaMaione1
 
Algoritmi di clustering
Algoritmi di clusteringAlgoritmi di clustering
Algoritmi di clustering
Rosario Turco
 
Mobile price classification
Mobile price classificationMobile price classification
Mobile price classification
MircoBarbero
 
Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010
Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010
Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010
Stefano Bragaglia
 
PresentazioneLaureaMatteoVettosi
PresentazioneLaureaMatteoVettosiPresentazioneLaureaMatteoVettosi
PresentazioneLaureaMatteoVettosiMatteo Vettosi
 
Compressione di insiemi di espressioni regolari tramite programmazione geneti...
Compressione di insiemi di espressioni regolari tramite programmazione geneti...Compressione di insiemi di espressioni regolari tramite programmazione geneti...
Compressione di insiemi di espressioni regolari tramite programmazione geneti...
Simone Cumar
 
COUGAR: Clustering Of Unknown malware using Genetic Algorithm Routines
COUGAR: Clustering Of Unknown malware using Genetic Algorithm RoutinesCOUGAR: Clustering Of Unknown malware using Genetic Algorithm Routines
COUGAR: Clustering Of Unknown malware using Genetic Algorithm Routines
DavidePanarella
 
Data Exchange
Data ExchangeData Exchange
Data Exchange
Enrico
 
Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...
Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...
Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...
Andrea Cannella
 
Lezioni 2009
Lezioni 2009Lezioni 2009
Lezioni 2009
Giuseppe Levi
 
Gestione Schematico2009
Gestione Schematico2009Gestione Schematico2009
Gestione Schematico2009Diego Faro
 
Gestione Schematico2009
Gestione Schematico2009Gestione Schematico2009
Gestione Schematico2009guest15b813
 
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
Francesco Andreuzzi
 
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
Francesco Andreuzzi
 
Hadoop [software architecture recovery]
Hadoop [software architecture recovery]Hadoop [software architecture recovery]
Hadoop [software architecture recovery]
gioacchinolonardo
 
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Filippo Muscolino
 
Presentazione understand
Presentazione understandPresentazione understand
Presentazione understand
Luigi La Torre
 
Metodi matematici per l’analisi di sistemi complessi
Metodi matematici per l’analisi di sistemi complessiMetodi matematici per l’analisi di sistemi complessi
Metodi matematici per l’analisi di sistemi complessi
Lino Possamai
 

Similar to Tesi andrea cingolani (20)

Build a LINQ-enabled Repository
Build a LINQ-enabled RepositoryBuild a LINQ-enabled Repository
Build a LINQ-enabled Repository
 
La metodologia statistica nel data mining
La metodologia statistica nel data miningLa metodologia statistica nel data mining
La metodologia statistica nel data mining
 
Extended summary of code building genetic programming
Extended summary of code building genetic programmingExtended summary of code building genetic programming
Extended summary of code building genetic programming
 
Algoritmi di clustering
Algoritmi di clusteringAlgoritmi di clustering
Algoritmi di clustering
 
Mobile price classification
Mobile price classificationMobile price classification
Mobile price classification
 
Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010
Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010
Stefano Bragaglia MSc Thesis, awarded as Best Italian thesis in AI 2009/2010
 
PresentazioneLaureaMatteoVettosi
PresentazioneLaureaMatteoVettosiPresentazioneLaureaMatteoVettosi
PresentazioneLaureaMatteoVettosi
 
Compressione di insiemi di espressioni regolari tramite programmazione geneti...
Compressione di insiemi di espressioni regolari tramite programmazione geneti...Compressione di insiemi di espressioni regolari tramite programmazione geneti...
Compressione di insiemi di espressioni regolari tramite programmazione geneti...
 
COUGAR: Clustering Of Unknown malware using Genetic Algorithm Routines
COUGAR: Clustering Of Unknown malware using Genetic Algorithm RoutinesCOUGAR: Clustering Of Unknown malware using Genetic Algorithm Routines
COUGAR: Clustering Of Unknown malware using Genetic Algorithm Routines
 
Data Exchange
Data ExchangeData Exchange
Data Exchange
 
Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...
Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...
Seminario Basi di Dati - Architetture Distribuite - Università degli Studi di...
 
Lezioni 2009
Lezioni 2009Lezioni 2009
Lezioni 2009
 
Gestione Schematico2009
Gestione Schematico2009Gestione Schematico2009
Gestione Schematico2009
 
Gestione Schematico2009
Gestione Schematico2009Gestione Schematico2009
Gestione Schematico2009
 
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
 
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
BisPy: un pacchetto Python per il calcolo della massima bisimulazione di graf...
 
Hadoop [software architecture recovery]
Hadoop [software architecture recovery]Hadoop [software architecture recovery]
Hadoop [software architecture recovery]
 
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
 
Presentazione understand
Presentazione understandPresentazione understand
Presentazione understand
 
Metodi matematici per l’analisi di sistemi complessi
Metodi matematici per l’analisi di sistemi complessiMetodi matematici per l’analisi di sistemi complessi
Metodi matematici per l’analisi di sistemi complessi
 

Tesi andrea cingolani

  • 1. Facoltà di Ingegneria Informatica Automatica e Gestionale Corso di Laurea Triennale in Ingegneria Dei Sistemi Informatici Tesi di Laurea Implementazione e valutazione di un algoritmo per il partizionamento di grafi su piattaforma SPARK Relatore: Laureando: Prof. Leonardo Querzoni Andrea Cingolani Matricola: 1477231 Anno accademico 2016/2017
  • 2.
  • 3.
  • 4. 4 Indice Introduzione................................................................................................................................ 5 Capitolo 1: L’algoritmo HDRF ............................................................................................... 10 Capitolo 2: Strumenti usati per l’analisi................................................................................ 13 2.1: Apache Spark................................................................................................................. 13 2.1.1: Caratteristiche di Apache Spark........................................................................... 13 2.1.2: Spark Core............................................................................................................... 14 2.1.3: Spark SQL................................................................................................................ 15 2.1.4: Spark Streaming ..................................................................................................... 15 2.1.5: MLlib........................................................................................................................ 15 2.1.6: GraphX..................................................................................................................... 16 2.4: Scala................................................................................................................................. 21 Capitolo 3: Realizzazione e implementazione del progetto............................................... 22 3.1: Acquisizione del file di testo da utilizzare come grafo............................................ 22 3.2: Creazione del grafo....................................................................................................... 22 3.3 Implementazione algoritmo.......................................................................................... 24 Capitolo 4 – Validazione e Test.............................................................................................. 33 Capitolo 5: Conclusioni ........................................................................................................... 34 Bibliografia................................................................................................................................ 35
  • 5. 5 Introduzione Negli ultimi anni abbiamo assistito a un'enorme crescita di produzioni di informazioni. Alcune società come IBM stimano che "ogni giorno vengono creati 2,5 quintilioni di byte di dati", pari al 90% dei dati nel mondo creati negli ultimi due anni. Di fronte a questa crescita, i ricercatori del mondo accademico e industriale si sono concentrati per la progettazione di nuovi, efficienti, approcci per analisi parallela dei dati in grado di resistere al diluvio di dati atteso nei prossimi anni. Data la proliferazione di dati che possono essere rappresentati come grafici di vertici interconnessi, un paradigma di calcolo basato sul grafico fornisce un'astrazione piacevole, adatta, per eseguire calcoli su di esso. Grandi quantità di dati, in particolare grafici a invarianza di scala o grafici power-law rientrano in questo paradigma. Inoltre, il calcolo basato sui grafici trova applicazione in molti campi diversi e importanti come i social network, la biologia, la chimica e la sicurezza informatica. Un problema chiave nel calcolo dei grafici è che è spesso difficile scalare con l’aumento delle dimensioni dei dati in input poiché i grafici non sono facilmente partizionabili in sotto grafi indipendenti che possono essere calcolati in parallelo. Per poter lavorare su dataset di grandi dimensioni, framework Graphcomputing (DGC) distribuiti (come GraphLab o Pregel) separa forzatamente il grafo di input posizionando i suoi elementi costituenti, siano essi vertici o bordi, in partizioni distinte, una per ciascuna risorsa di
  • 6. 6 calcolo disponibile. Durante la fase di partizionamento, gli elementi di dati che condividono le connessioni con altri elementi già posizionati in altre partizioni risultano avere connessioni remote tra di loro. Poiché queste partizioni vengono solitamente posizionate su macchine diverse, ciò può comportare una rete non necessaria o eccessiva e costi di calcolo. Per risolvere questo problema, frequentemente la tecnica utilizzata è quella di creare e posizionare localmente delle repliche di dati connessi in remoto tra queste partizioni. Mentre questo riduce il costo di accesso, gli elementi di dati replicati devono essere sincronizzati durante il calcolo in modo da evitare che gli stati di replica divergano e generino risultati di calcolo privi di significato. Questa sincronizzazione può ostacolare in modo significativo le prestazioni poiché costringe le repliche a coordinare e scambiare dati più volte durante il calcolo. Il modo in cui il set di dati di input è partizionato ha un grande impatto sulle prestazioni del calcolo del grafico. Una strategia di partizionamento ingenua può finire per replicare una grande parte degli elementi di input su diverse partizioni, intralciando gravemente le prestazioni inducendo un over head di sincronizzazione delle repliche di grandi dimensioni durante la fase di calcolo. Inoltre, la fase di partizionamento dovrebbe produrre partizioni bilanciate in modo uniforme (cioè partizioni con dimensioni simili) per evitare possibili distorsioni del carico in un cluster di macchine su cui i dati sono partizionati. Diversi approcci recenti hanno esaminato questo problema. Qui focalizziamo la nostra attenzione su algoritmi di partizionamento grafico, ovvero gli algoritmi che partizionano gli elementi in entrata uno alla volta sulla base delle sole proprietà dell'elemento corrente e su assegnazioni precedenti alle
  • 7. 7 partizioni (nessuna conoscenza globale sul grafico di input). Inoltre, questi algoritmi sono solitamente one-pass, cioè si astengono dal modificare l'assegnazione di un elemento di dati ad una partizione una volta fatto questo. Questi algoritmi sono i candidati ideali nelle impostazioni in cui le dimensioni dei dati di input e i vincoli sulle risorse disponibili limitano il tipo di soluzioni che può essere impiegato. Altre caratteristiche dei dati di input svolgono anche un ruolo importante nel partizionamento. È stato dimostrato che gli algoritmi vertex-cut sono l'approccio migliore per gestire grafici di input caratterizzati da distribuzioni di gradi di power-law. Questo lavoro precedente ha anche delineato chiaramente l'importante ruolo svolto dai nodi ad alta gradazione dal punto di vista della qualità del partizionamento. Tuttavia, pochi algoritmi tengono conto di questo aspetto. Comprensibilmente, questo è un problema complesso da risolvere per gli approcci basati sul flusso a causa della loro natura a un solo passaggio. In questo progetto, facciamo leva sull'idea che un algoritmo di partizionamento dovrebbe fare del suo meglio per tagliare, cioè replicare, vertici con grado elevato. In particolare, introduciamo High Degree (are) Replicated First (HDRF), un algoritmo di partizionamento grafico basato su un avido vertex-cut che sfrutta le informazioni sui gradi dei vertici. HDRF è caratterizzato dalle seguenti proprietà desiderabili:
  • 8. 8 Emette partizioni con il più piccolo fattore di replicazione medio tra tutte le soluzioni concorrenti quando vengono applicate grafici power-law mentre fornisce un bilanciamento del carico quasi perfetto. Il primo è ottenuto replicando avidamente i vertici con gradi maggiori, mentre quest'ultimo è fornito da un termine di bilanciamento parametrizzabile il cui impatto può essere regolato per adattare il comportamento dell'algoritmo a qualsiasi ordine di input dei dati. Da un lato, ridurre il fattore di replicazione medio è importante per ridurre il costo della larghezza di banda della rete, l'utilizzo della memoria e il sovraccarico della sincronizzazione delle repliche a tempo di calcolo. Un'equa distribuzione del carico sulle partizioni, d'altra parte, consente un utilizzo più efficiente delle risorse di calcolo disponibili. HDRF tiene conto
  • 9. 9 di entrambi questi aspetti in modo integrato, riducendo significativamente il tempo necessario per eseguire calcoli su grafici su larga scala. Nei capitoli successivi spiegheremo cosa è più nel dettaglio l’algoritmo HDRF, cosa abbiamo usato per implementarlo, e infine tutto il procedimento svolto.
  • 10. 10 Capitolo 1: L’algoritmo HDRF L’algoritmo HDRF è un algoritmo adatto ai grafi power-law. Due informatici hanno dimostrato che solo alcuni vertici di grado alto vengono rimossi da un grafo power-law, e vengono trasformati in un gruppo di cluster isolati. Inoltre nei grafici power-law la distribuzione dei coefficienti di cluster diminuisce con l’aumento del grado dei vertici. Ciò implica che i vertici a basso grado appartengono ai sottogruppi molto densi e quei sotto-grafi sono collegati tra loro attraverso vertici di grado alto. Questo schema di partizionamento sfrutta queste proprietà concentrandosi sulle località dei vertici di basso grado. In particolare, cerca di posizionare ciascun elemento fortemente connesso con i vertici di basso grado in una singola partizione, tagliando i vertici di alto grado e replicandoli su un gran numero di partizioni. Poiché il numero dei vertici di alto grado nei grafici power-law è molto basso, incoraggiare la replica per questi vertici porta ad una riduzione complessiva del fattore di replica. In concreto, quando HDRF crea una replica, lo fa per il vertice con massimo grado. Tuttavia, ottenere gradi di vertice per un grafico che viene consumato in streaming non è banale. Per evitare il sovraccarico di un passo di pre- elaborazione (dove il grafo di input viene scandito per calcolare i gradi esatti del vertice) è possibile mantenere una tabella con gradi parziali dei vertici che viene aggiornata mentre l’input viene analizzato. Poiché ogni nuovo bordo viene considerato nell’input, i valori di gradazione dei vertici corrispondenti vengono aggiornati nella tabella. I valori di grado parziale raccolti in fare di run-time sono di solito un buon indicatore per il grado
  • 11. 11 di un vertice poiché è più probabile che un bordo osservato appartiene ad un vertice di alto grado, piuttosto che a uno di basso grado. Matematicamente quando elaborano i vertici vi e vj di bordo appartenente ad E (bordi), l’algoritmo HDRF recupera i loro gradi parziali e li incrementa di uno. Sia (vi) il grado parziali di vi e (vj) quelli di vj. I valori di grado vengono normalizzati in modo da arrivare ad uno: Per quanto riguarda l’elogio euristico, l’algoritmo HDRF calcola un punteggio CHDRF (vi, vj, p) per tutte le partizioni p appartenenti a P e poi assegna e alla partizione p* che massimizza CHDRF. Il punteggio per ogni partizione p appartenente a P è definito come segue: Il parametro λ consente di controllare l’entità dello squilibrio di dimensione delle partizioni nel calcolo dei punteggi. È stato introdotto perché equilibra partizioni particolarmente squilibrate. Per vedere questo problema nota che CBAL greedy (p) è sempre < 1 mentre CREP greedy e CREP HDRF sono sempre o 0 o > 1. Per questa ragione, il termine di equilibrio CBAL va messo o nell’algoritmo greedy o quando 0 < λ <= 1 è usato solo per scegliere tra partizione che presentano lo stesso valore per il termine di replica CREP,
  • 12. 12 rompendo così la simmetria. Tuttavia, questo potrebbe non essere sufficiente per garantire il saldo del carico. Ad esempio, se il flusso di bordi è ordinato secondo un ordine di visita sul grafo (ad esempio la prima ricerca di larghezza e profondità), quando si elaborano i vertici vi e vj di bordo e appartenente ad E, c’è sempre una singola partizione p* con CREP Greedy (vi, vj, p*) >= 1 (o CHDRF REP(vi,vj,p) > 1) e tutte le altre partizioni p appartenente a P se p =! p* hanno CREP Greedy (vi,vj,p*) = 0 (o CHDRF REP(vi,vj,p) = 0). In questo caso il termine di equilibrio è inutile in quanto non esiste una simmetria per rompere l’euristica, finisce per mettere tutti i bordi in una singola partizione p*. Questo problema può essere risolto impostando un valore λ > 1. In questa valutazione si è studiato l’andamento del fattore di replicazione e il bilanciamento del carico variando λ. Inoltre, da notare quando λ -> ∞ l’algoritmo somiglia a un euristico casuale, dove le osservazioni passate sono ignorate e contano solo partizioni di uguale dimensione. HDRF può essere eseguito come un singolo processo o in istanze parallele per aumentare la velocità della fase di partizionamento. Come per i greedy, HDRF ha anche bisogno di uno stato da condividere tra istanze parallele durante la partizione.
  • 13. 13 Capitolo 2: Strumenti usati per l’analisi Il progetto è stato realizzato con il framework Apache Spark su sistema Linux Lubuntu 17.04 programmato in Scala. Nei prossimi paragrafi viene spiegato il funzionamento sia di Scala che di Spark, descrivendo alcune delle librerie da noi usate di Spark. 2.1: Apache Spark Apache Spark è una piattaforma di calcolo a cluster costruita per essere veloce e multiuso. Il suo obiettivo è di eseguire più attività possibile su un unico sistema dove prima questo non era possibile. Alcune di esse sono: • Streaming di dati • Query interattive • Algoritmi iterativi • Applicazioni di batch Supportando tutti questi carichi di lavoro su un'unica macchina, Spark rende semplice e poco costoso la combinazione di più processi. 2.1.1: Caratteristiche di Apache Spark Nel suo core, Spark offre un motore di computazione che viene sfruttato da tutti gli altri suoi componenti. Questo significa che un’ottimizzazione al core di Spark si manifesta su tutte le applicazioni che usato questo framework. Il core di Spark si occupa di: • Scheduling
  • 14. 14 • Distribuzione • Controllo dell’esecuzione dell’applicazione dell’utente A sfruttare il core di ci sono vari tools: • Spark SQL • Spark Streaming • MLlib • GraphX Essi si integrano tra di loro, facendo sì che un’applicazione possa usare tutti i componenti di Spark contemporaneamente. Tutti i moduli di Spark sono di seguito descritti. 2.1.2: Spark Core Lo Spark Core contiene le funzionalità base di Spark, includendo: • Componenti della pianificazione • Management di memoria • Recupero dei guasti • Interazione con i sistemi di memorizzazione
  • 15. 15 Lo Spark Core include le API che definiscono gli RDD (Resilient Distributed Datasets) che rappresentano dati distribuiti sul cluster e su cui vengono svolte operazioni di trasformazione per poi recuperare i dati modificati 2.1.3: Spark SQL Spark SQL permette di fare interrogazioni su dati strutturati e semi strutturati usando HiveQL, una variante del linguaggio SQL. Si possono interrogare file JSON, file di testo e qualunque altro formato supportato da Hive. 2.1.4: Spark Streaming Spark Streaming permette di analizzare i flussi di dati in tempo reale come i log di errori o un flusso di tweet. Un esempio di utilizzo è l’analisi di file di log generati da server web, come: • Filtraggio di particolari indirizzi IP in funzione della sicurezza • L’analisi degli aggiornamenti di stato degli utenti su una piattaforma social 2.1.5: MLlib Spark nasce con una libreria che contiene le più comuni funzionalità di Machine Learning (ML), chiamata MLlib. Essa offre vari algoritmi di machine learning che includono: • Classificazione
  • 16. 16 • Regressione • Clustering • Collaborative filtering • Modelli di valutazione e acquisizione dati Questi algoritmi sono eseguiti sugli RDD. Gli algoritmi di MLlib vengono eseguiti in parallelo, ed hanno performance ottime sui cluster. La libreria è valida quando si usano questi algoritmi su dataset molto grandi. 2.1.6: GraphX GraphX è una libreria usata per l’analisi dei grafi talmente grandi che non potrebbero essere analizzati da una singola macchina (ad esempio i grafi dei social network). Un grafo è una collezione di nodi (o vertici) collegati da archi. Ad esempio i nodi possono essere le persone e gli archi le amicizie. La libreria offre algoritmi come PageRank (per misurare l’importanza di ogni nodo di un grafo), il calcolo delle componenti connesse, il calcolo dei triangoli e molto altro. 2.1.6.1 Partition Strategy Per elaborare il grafo in uno stile distribuito, il grafo ha bisogno di essere rappresentato in uno schema distribuito. Normalmente, ci sono due tipi
  • 17. 17 di grafi partizionati, l’approccio vertex-cut e l’approccio edge-cut. Spark Graphx adotta un vertex-cut per partizionare dei grafi distribuiti. La strategia è programmata nel PartitionStrategy.scala. Guardiamo questo file: 1. case object RandomVertexCut extends PartitionStrategy{ 2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part itionID): PartitionID = { 3. math.abs((src, dst).hashCode()) % numParts } RandomVertexCut calcola il valore hash degli ID Vertex di origine e destinazione, usando il modulo (da numberOfParts) come IP partizione del bordo. I bordi partizionati nella stessa partizione di due vertici hanno la stessa direzione. CanonicalRandomVertexCut partiziona i bordi indipendentemente dalla direzione 1. case object CanonicalRandomVertexCut extends PartitionStrategy {
  • 18. 18 2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part itionID): PartitionID = { 3. if (src < dst) { 4. math.abs((src, dst).hashCode()) % numParts 5. } else { 6. math.abs((dst, src).hashCode()) % numParts 7. } 8. } 9. } Altri due schemi di partizionamento sono EdgePartition1D e EdgePartition2D. In EdgePartition1D, i bordi vengono assegnati alle partizioni solo in base ai loro vertici di origine. 1. case object EdgePartition1D extends PartitionStrategy { 2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part itionID): PartitionID = { 3. val mixingPrime: VertexId = 1125899906842597L 4. (math.abs(src * mixingPrime) % numParts).toInt 5. } 6. } Un principio molto grande (mixingPrime) viene usato per bilanciare le partizioni. Ma questa operazione non elimina completamente il problema. EdgePartition2D è un po’ più complesso. Usa sia il vertice di origine che quello di destinazione per calcolare la partizione. È basato sulla matrice di adiacenza del bordo sparso. Ecco un esempio estratto dal codice sorgente.
  • 19. 19 Supponiamo di avere un grafo con 12 vertici che vogliamo dividere su 9 macchine. Possiamo usare la seguente rappresentazione di matrice sparsa: Come si vede, E<v11, v1> è partizionato nel P6. Ma è anche chiaro che P0 contiene troppi bordi (molti di più di altre partizioni) che provocano uno squilibrio di partizionamento. Così mixingPrime viene usato anche in EdgePartition2D. 1. case object EdgePartition2D extends PartitionStrategy { 2. override def getPartition(src: VertexId, dst: VertexId, numParts: Part itionID): PartitionID = { 3. val ceilSqrtNumParts: PartitionID = math.ceil(math.sqrt(numParts)) .toInt 4. val mixingPrime: VertexId = 1125899906842597L 5. val col: PartitionID = (math.abs(src * mixingPrime) % ceilSqrtNumP arts).toInt
  • 20. 20 6. val row: PartitionID = (math.abs(dst * mixingPrime) % ceilSqrtNum Parts).toInt 7. (col * ceilSqrtNumParts + row) % numParts 8. } 9. } Guarda questo grafico realistico. I vertici A, B e C sono in una partizione; D, E, F sono nell’altra. I bordi sono suddivisi in due partizioni come nel Edge Table. La tabella di instradamento è molto utile perché registra lo stato di taglio dei vertici.
  • 21. 21 2.4: Scala Scala è un linguaggio di programmazione funzionale orientato ad oggetti. Compilando un sorgente scala viene generato un bytecode eseguibile in una Java Virtual Machine. Esso fornisce un linguaggio diretto per definire funzioni anonime (dichiarate senza essere legate ad un nome), supporta funzioni di ordine superiore e permette alle funzioni di essere annidate e supporta funzioni parziali. Scala ha supporto nativo per il pattern matching, permette di potenziale l’elaborazione di dati XML con il supporto di espressioni regolari. Il suo nome deriva da “Scalable Language” ovvero linguaggio scalabile. Ciò che rende Scala un linguaggio scalabile è la fusione tra programmazione funzionale e programmazione ad oggetti.
  • 22. 22 Capitolo 3: Realizzazione e implementazione del progetto Per svolgere il progetto sono passato per diverse fasi: 1. Acquisizione del file di testo da utilizzare come grafo 2. Creazione del grafo 3. Implementazione algoritmo a. Caso 1: Vertici non assegnati alle partizioni b. Caso 2: Solo un vertice assegnato c. Caso 3: Vertici assegnati, partizione comune d. Caso 4: Vertici assegnati a due partizioni diverse 3.1: Acquisizione del file di testo da utilizzare come grafo Il file di testo è un file scaricato da http://files.grouplens.org/datasets/movielens/ml-10m.zip. Il file zip al suo interno contiene vari file, tra cui ratings.dat che è il file che ho usato per formare il grafico, questo file al suo interno contiene: 10 milioni di valutazioni fatte da 72000 utenti su 10000 film presi dal famosissimo servizio MovieLens. I dati sono ordinati casualmente. 3.2: Creazione del grafo Per la creazione del grafo ci sono stati vari problemi sulla formattazione del file, inizialmente avevamo pensato di usare la classe di GraphX GraphLoader con al suo interno il metodo:
  • 23. 23 1. edgeListFile(SparkContext sc, String path, boolean canon icalOrientation, int numEdgePartitions, StorageLevel edg eStorageLevel, StorageLevel vertexStorageLevel) ma non avrebbe funzionato, perché essa carica un grafico da un file formattato in un elenco di bordi in cui ogni riga contiene due numeri: un ID sorgente e un ID destinazione, noi avevamo un file formattato in una maniera diversa. Il file nostro era formattato in questa maniera: un ID utente, un ID film, il voto che l’utente ha dato al film e un numero chiamato Timestamp che a noi non serviva, tutto diviso da un t. Il modo più facile per formattare il file in modo da creare dei bordi automaticamente era questo: 1. val edges:RDD[Edge[String]]= 2. sc.textFile("data/u1.base").map{ line => 3. val fields= line.split("t") 4. Edge(fields(0).toLong,fields(1).toLong,fields(2) ) 5. } Ora una volta creati i bordi il lavoro era all’80% dell’opera, mancava solo creare il grafo. Per creare il grafo abbiamo usato la classe Graph con al suo interno il metodo: 1. fromEdges[VD, ED](edges: RDD[Edge[ED]],defaultValue: VD) : Graph[VD, ED] in questo modo:
  • 24. 24 1. val graph: Graph[Any,String] =Graph.fromEdges(edges,"def aultProperty") Il grafico era creato, per controllare che bordi e vertici erano stati creati abbiamo usato graph.numEdges e graph.numVertices in modo da capire anche quanti erano sia i bordi che i vertici. 3.3 Implementazione algoritmo Per l’implementazione dell’algoritmo ho usato la classe PartitionStrategy di GraphX e l’ho modificata. Nel Main ho creato il valore partition che è un PartitionID ed ho usato partitionBy in questo modo: 1. val partition= graph.partitionBy(HDRF,numPartition) Mentre in HDRF.scala sono andato ad aggiungere l’estensione PartitionStrategy, qui dentro ho implementato tutto l’algoritmo. L’implementazione dell’algoritmo ha seguito questi 4 casi: a) Caso 1: Se nessuno dei due vertici di e è stato mai inserito in qualche partizione Scelgo la partizione più scarica e li assegno a quella b) Caso 2: Se uno dei due vertici è già presente in almeno una partizione
  • 25. 25 Scelgo la partizione più scarica tra quelle in cui è presente almeno un vertice e ci replico l’altro vertice c)Caso 3: Entrambi i vertici sono presenti in diverse partizioni ed esiste un’intersezione dei set non nulla (cioè esiste almeno una partizione che li contiene entrambi) Scelto nell’intersezione dei set la partizione più scarica d) Caso 4: Entrambi i vertici sono presenti in diverse partizioni ma l’intersezione dei set è nulla (cioè non esiste alcuna partizione che li contiene entrambi) Scelgo tra le partizioni a cui è assegnato uno dei due, in quella più scarica ci copio l’altro vertice
  • 26. 26 Come ultima cosa, una volta partizionato il grafico ho calcolato il fattore di replicazione medio e la deviazione standard relativa. Questa è la porzione di codice di come ho implementato il tutto: 1. package app 2. 3. 4. import org.apache.spark.graphx._ 5. 6. import scala.collection.concurrent.TrieMap 7. 8. object HDRF extends PartitionStrategy{ 9. private var init=0; //lo puoi usare per controllare una fase di inizializzaz ione che viene eseguita solo la prima volta 10. 11. private var partitionsLoad:Array[Long] = Array.empty[Long] //carico (n umero di archi) di ogni partizione 12. private val vertexIdListPartitions: TrieMap[Long, List[AnyVal]] = Trie Map.empty[Long,List[AnyVal]] //lista di partizioni associate a ogni verti ce 13. private val vertexIdEdges: TrieMap[Long, Long] = TrieMap() //grado di ogni vertice 14. 15. private var edges = 0 16. 17. override def getPartition(src:VertexId, dst:VertexId, numParts:Int): Parti tionID ={ 18. var valoreMax:Long =Int.MaxValue 19. var partScarica:Int = -1 20. if(init==0){ 21. init=1 22. partitionsLoad=Array.fill[Long](numParts)(0) 23.
  • 27. 27 24. } 25. 26. 27. //AGGIORNA IL GRADO CONOSCIUTO DEI VERTICI src E dst NEL LA VARIABILE vertexIdEdges 28. 29. vertexIdEdges.update(src,+1) 30. vertexIdEdges.update(dst,+1) 31. 32. //PARTIZIONA IL GRAFO 33. if((!vertexIdListPartitions.contains(src))&&(!vertexIdListPartitions.con tains(dst))){ 34. //NESSUNO DEI DUE VERTICI E' STATO MAI INSERITO IN QUAL CHE PARTIZIONE 35. //SCELGO LA PARTZIIONE PIU' SCARICA E LI ASSEGNO A QUEL LA 36. for(c<-0 until numParts){ 37. if(partitionsLoad(c)<valoreMax){ 38. valoreMax=partitionsLoad(c) 39. partScarica=c 40. } 41. } 42. if(partScarica != -1) { 43. partitionsLoad(partScarica)=partitionsLoad(partScarica)+1 44. vertexIdListPartitions.put(src, List(partScarica)) 45. vertexIdListPartitions.put(dst, List(partScarica)) 46. //vertexIdListPartitions.update(src, vertexIdListPartitions.getOrElse( src, List()) ++ List(partScarica)) 47. //vertexIdListPartitions.update(dst, vertexIdListPartitions.getOrElse( dst, List()) ++ List(partScarica)) 48. } 49. 50. }else if((vertexIdListPartitions.contains(src) &&(!vertexIdListPartitions .contains(dst)))||((!vertexIdListPartitions.contains(src))&& vertexIdListP artitions.contains(dst))){ 51. //UNO SOLO DEI DUE VERTICI E' GIA' PRESENTE IN ALMENO U NA PARTIZIONE 52. if(vertexIdListPartitions.contains(src) &&(!vertexIdListPartitions.cont ains(dst))){ 53. //SI TRATTA DI src
  • 28. 28 54. //SCELGO LA PARTIZIONE PIU' SCARICA TRA QUELLE IN CUI E' PRESENTE src E CI REPLICO dst 55. for(c<- 0 until numParts){ 56. if(partitionsLoad(c)<valoreMax){ 57. if(vertexIdListPartitions(src).contains(c)) { 58. valoreMax = partitionsLoad(c) 59. partScarica = c 60. } 61. } 62. } 63. if(partScarica != -1) { 64. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1 65. vertexIdListPartitions.put(dst, List(partScarica)) 66. //vertexIdListPartitions.update(dst, vertexIdListPartitions.getOrEls e(dst, List()) ++ List(partScarica)) 67. } 68. 69. }else{ 70. //SI TRATTA DI dst 71. //SCELGO LA PARTZIIONE PIU' SCARICA TRA QUELLE IN CUI E' PRESENTE dst E CI REPLICO src 72. 73. for(c<- 0 until numParts){ 74. if(partitionsLoad(c)<valoreMax){ 75. if(vertexIdListPartitions(dst).contains(c)) { 76. valoreMax = partitionsLoad(c) 77. partScarica = c 78. } 79. } 80. } 81. if(partScarica != -1) { 82. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1 83. vertexIdListPartitions.put(src, List(partScarica)) 84. //vertexIdListPartitions.update(src, vertexIdListPartitions.getOrEls e(src, List()) ++ List(partScarica)) 85. } 86. 87. } 88. }else if(vertexIdListPartitions(src).intersect(vertexIdListPartitions(dst)) .nonEmpty){
  • 29. 29 89. //ENTRAMBI I VERTICI SONO PRESENTI IN DIVERSE PARTIZION I ED ESISTE UNA INTERSEZIONE DEI SET NON NULLA (CIOE' ESIST E ALMENO UNA PARTIZIONE CHE LI CONTIENE ENTRAMBI) 90. //SCELGO NELL'INTERSEZIONE DEI SET LA PARTIZIONE PIU' S CARICA 91. for(c<- 0 until numParts){ 92. if (partitionsLoad(c) < valoreMax) { 93. if (vertexIdListPartitions(src).contains(c) && vertexIdListPartitions (dst).contains(c)) { 94. valoreMax = partitionsLoad(c) 95. partScarica = c 96. } 97. } 98. } 99. if(partScarica != -1) { 100. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1 101. } 102. 103. }else { 104. //ENTRAMBI I VERTICI SONO PRESENTI IN DIVERSE PART IZIONI MA L'INTERSEZIONE DEI SET E' NULLA (CIOE' NON ESISTE ALCUNA PARTIZIONE CHE LI CONTIENE ENTRAMBI) 105. if(vertexIdEdges(src) >= vertexIdEdges(dst)){ 106. //SCELGO TRA LE PARTIZIONI A CUI E' ASSEGNATO dst QUELLA PIU' SCARICA E CI COPIO src 107. 108. for(c<-0 until numParts){ 109. if(partitionsLoad(c)<valoreMax){ 110. if(vertexIdListPartitions(dst).contains(c)) { 111. valoreMax = partitionsLoad(c) 112. partScarica = c 113. } 114. } 115. } 116. if(partScarica != -1) { 117. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1 118. vertexIdListPartitions.update(src, vertexIdListPartitions(src). union(List(partScarica))) 119. //vertexIdListPartitions.update(src, vertexIdListPartitions.get OrElse(src, List()) ++ List(partScarica))
  • 30. 30 120. 121. } 122. 123. }else{ 124. //SCELGO TRA LE PARTIZIONI A CUI E' ASSEGNATO src QUELLA PIU' SCARICA E CI COPIO dst 125. 126. for(c<-0 until numParts){ 127. if(partitionsLoad(c)<valoreMax){ 128. if(vertexIdListPartitions(src).contains(c)) { 129. valoreMax = partitionsLoad(c) 130. partScarica = c 131. } 132. } 133. } 134. if(partScarica != -1) { 135. partitionsLoad(partScarica) = partitionsLoad(partScarica) + 1 136. vertexIdListPartitions.update(dst, vertexIdListPartitions(dst) .union(List(partScarica))) 137. //vertexIdListPartitions.update(dst, vertexIdListPartitions.ge tOrElse(dst, List()) ++ List(partScarica)) 138. } 139. } 140. 141. } 142. 143. edges=edges+1 144. if(edges==10000054){ 145. //Calcolo il fattore di replicazione medio dei vertici 146. //vertexIdListPartitions.foreach(println) 147. var fattore_replicazione:Double = 0 148. for ((_,v) <- vertexIdListPartitions) fattore_replicazione += v.siz e 149. fattore_replicazione /= vertexIdListPartitions.size 150. //Calcolo il carico medio delle partizioni 151. var carico_medio:Double = 0 152. for (l <- partitionsLoad) carico_medio += l 153. //Calcolo la relativa devizaione standard 154. var tmp:Double = 0
  • 31. 31 155. for (l <- partitionsLoad) tmp += scala.math.pow(l - carico_medi o, 2) 156. tmp /= numParts-1 157. val stdev = scala.math.sqrt(tmp) 158. //Calcolo la devizione standard relativa 159. val rsd = 100 * stdev / carico_medio 160. println("n ---* RISULTATI DEL PARTIZIONAMENTO *--- n") 161. println("tFattore di replicazione medio: " + fattore_replicazion e) 162. println("tDeviazione standard relativa: " + rsd) 163. } 164. partScarica 165. } 166. } Di seguito c’è anche il Main: 1. package app 2. 3. import org.apache.spark.graphx._ 4. import org.apache.spark._ 5. import org.apache.spark.rdd.RDD 6. 7. object Main{ 8. 9. 10. def main(args: Array[String]) { 11. 12. val sc = new SparkContext(new SparkConf().setMaster("local").setApp Name("HDRF")) 13. 14. // mostra solo i log in caso di errore
  • 32. 32 15. sc.setLogLevel("ERROR") 16. 17. //modifico il file di testo preso in ingresso 18. val edges:RDD[Edge[String]]= 19. sc.textFile("data/ratings.dat").map{ line => 20. val fields= line.split("::") 21. Edge(fields(0).toLong,fields(1).toLong,fields(2)) 22. } 23. 24. val graph: Graph[Any,String] =Graph.fromEdges(edges,"defaultPrope rty") 25. println("Benvenuto nel partizionatore HDRF:") 26. println("Il file scelto è: ratings.dat") 27. println("Esso contiene:") 28. println("Un numero di vertici pari a: "+ graph.numVertices) 29. println("e un numero di nodi pari a: "+ graph.numEdges) 30. println() 31. println("Partizionamento in corso...") 32. println("...") 33. println("...") 34. val result=graph.partitionBy(HDRF,4) 35. 36. result.edges.count() 37. } 38. }
  • 33. 33 Capitolo 4 – Validazione e Test Per i test abbiamo usato IntelliJIDEA sul mio PC con Linux Lubuntu e il risultato è il seguente: 1. Benvenuto nel partizionatore HDRF: 2. Il file scelto è : ratings.dat 3. Esso contiene: 4. Un numero di vertici pari a: 70155 5. e un numero di nodi pari a: 10000054 6. 7. Partizionamento in corso... 8. ... 9. ... 10. 11. ---* RISULTATI DEL PARTIZIONAMENTO *--- 12. 13. Fattore di replicazione medio: 2.6129855320362054 14. Deviazione standard relativa: 88.19171036899517
  • 34. 34 Capitolo 5: Conclusioni • HDRF è un algoritmo di partizionamento grafico dei vertici a passaggio singolo. • Mi sono basato su un approccio vertex-cut greedy che sfrutta informazioni sui gradi dei vertici • In questa tesi forniamo un’analisi teorica e pratica dell’algoritmo HDRF con un limite superiore del caso medio per il fattore di replicazione dei vertici. • Gli esperimenti studiati mostrano che: o HDRF fornisce il fattore di replica più piccola e vicina al bilanciamento di carico ottimale o HDRF riduce in modo significativo il tempo necessario per eseguire calcolo su grafici
  • 35. 35 Bibliografia [1] Apache Spark https://spark.apache.org/docs/latest/ [2] Yuhc Partition Strategy in GraphX – Simple Note http://note.yuhc.me/2015/03/graphx-partition-strategy/ [3] Gianluca Tartaggia Linguaggio di programmazione Scala http://tesi.cab.unipd.it/40851/1/Tesina.pdf [4] Camil Demetrescu Dispensa programmazione funzionale https://docs.google.com/document/d/1M1EtsCXsbIdqXLAF4dxy2ppLIBeX5Y3gdany- MYx_9A/edit?pref=2&pli=1# [5] Tutorialspoint Apache Spark installation and Core Programming https://www.tutorialspoint.com/apache_spark/apache_spark_installation.htm https://www.tutorialspoint.com/apache_spark/apache_spark_core_programming.htm [6] Mapr How to get started using Apache Spark Graphx Scala https://mapr.com/blog/how-get-started-using-apache-spark-graphx-scala/ [7] Ampcamp Graph Analytics with Graphx
  • 36. 36 http://ampcamp.berkeley.edu/big-data-mini-course/graph-analytics-with- graphx.html [8] Srini Penchikala – InfoQ Graph Data Analytics with Apache Spark https://www.infoq.com/articles/apache-spark-graphx [9] Lorenzo Gatto Analisi e valutazione della piattaforma Spark http://amslaurea.unibo.it/8876/1/gatto_lorenzo_tesi.pdf