All Pairs Shortest Path χρησιμοποιώντας Κυπριώτη Αικατερίνη 6960 Μόσχογλου Στυλιανός 6978 20 Ιανουαρίου 2012
Περιεχόμενα 1 Πρόλογος 3 2 Ο σειριακός APSP 3 3 Η παραλληλοποίηση με 5 3.1 Το προγραμματιστικό μοντέλο.................. 5 3.2 Το παράλληλο APSP....................... 6 4 Οι μετρήσεις στον Διάδη 7 4.1 Διαγράμματα............................ 8 5 Επίλογος 9 6 Βιβλιογραφία 10 2
1 Πρόλογος Η εργασία αυτή πραγματοποιήθηκε στα πλαίσια του μαθήματος Παράλληλα & Διανεμημένα Συστήματα, στο πολυτεχνικό τμήμα Ηλεκτρολόγων Μηχανικών και Μηχανικών Υπολογιστών του Αριστοτελείου Πανεπιστημίου Θεσσαλονίκης, υπό την επίβλεψη του καθηγητού Πιτσιάνη Νικόλαου. Είναι δακτυλογραφημένη σε L A TEX. Σκοπός της είναι να περιγράψει το κατά δυνατόν καλύτερα την χρήση και παραλληλοποίηση του All Pairs Shortest Path (APSP) των Floyd Warshall. Ο κώδικας που χρησιμοποιήθηκε, μαζί με σχόλια όπου κρίνεται απαραίτητο, βρίσκεται στο αρχείο apsp.cu, επισυναπτόμενο στο τρέχον rar αρχείο. Ακολουθεί ενδελεχής περιγραφή του παράλληλου αλγορίθμου και στατιστικά αποτελέσματα μετρήσεων από τον Διάδη. Τέλος, ευχαριστούμε τον μεταπτυχιακό φοιτητή Σισμάνη Νικόλαο που ήταν πάντα διαθέσιμος να απαντήσει στις απορίες μας κατά την υλοποίηση της εργασίας. Καλή ανάγνωση... 2 Ο σειριακός APSP Καταρχάς, ο αλγόριθμος των Floyd-Warshall αφορά την εύρεση όλων των συντομότερων διαδρομών (paths) μέσα σε ένα κατευθυνόμενο γράφο. Ουσιαστικά, σε ένα γράφο n κόμβων (vertices), τα συνολικά ζευγάρια που δυνητικά μπορούν να εμφανιστούν είναι n 2. Ο συγκεκριμένος αλγόριθμος, μέσω της χρήσης 3 for, δηλαδή πολυπλοκότητας n 3, μας επιστρέφει τις συντομότερες διαδρομές για όλα τα n 2 ζευγάρια. Επομένως, η απόδοσή του O (n 3 ), μπορεί να χαρακτηριστεί ως τουλάχιστον εκπληκτική! Το πώς ακριβώς συντελείται το παραπάνω, το αναλύουμε κατωτέρω, παραθέτοντας τον αλγόριθμο σε μορφή ψευδοκώδικα, αλλά και μέσω ενός σχήματος, ώστε να υπάρχει και η αντίστοιχη οπτικόποιηση του τι συμβαίνει. 1: {Serial APSP algorithm} 2: for all 1 i n do 3: for all 1 j n do 4: if i = j or g ij = + then 5: p ij 1 6: else 7: p ij i 8: end if 9: end for 10: end for 3
11: for all 1 k n do 12: for all 1 i n do 13: for all 1 j n do 14: temp g ik + g kj 15: if temp < g ij then 16: g ij temp 17: p ij p kj 18: end if 19: end for 20: end for 21: end for Πριν ξεκινήσουμε την ανάλυση του αλγορίθμου, θα πρέπει να σημειωθεί ότι ο αρχικός πίνακας g που περιέχει τα βάρη των διαδρομών, έχει αρχικοποιηθεί με τυχαίες τιμές, μικρότερες του n. Σε περίπτωση που μιλάμε για το ίδιο στοιχείο, δηλαδή ταυτίζονται η εκάστοτε τετμημένη και τεταγμένη στον πίνακα, τότε η τιμή του είναι ίση με 0. Αν δεν υπάρχει η διαδρομή, τότε στην αντίστοιχη θέση υπάρχει η τιμή +. Παρατηρώντας τώρα προσεκτικά τον άνωθεν αλγόριθμο, βλέπουμε ότι ο πίνακας g θα έχει στο τέλος της εκτέλεσής του το μικρότερο βάρος που αντιστοιχεί στη βέλτιστη διαδρομή, ενώ ο πίνακας p θα περιέχει το στοιχείο μέσω του οποίου συντελείται αυτή η συντομότερη διαδρομή μεταξύ των κορυφών i και j. Αν δεν υπάρχει αυτή η διαδρομή ή μιλάμε για το ίδιο στοιχείο, τότε στην αντίστοιχη θέση έχει την τιμή 1. Για την καλύτερη οπτικοποίηση, παραθέτουμε και το παρακάτω παράδειγμα γράφου: 3 7 1 2 9 7 5 4 6 3 2 10 4
Επιλέγοντας π.χ. τη διαδρομή 1 2, παρατηρούμε ότι η συντομότερη διαδρομή είναι αυτή, χωρίς να μεσολαβεί ενδιάμεση. Άρα, σε αυτή την περίπτωση, ο πίνακας g στο στοιχείο g[1][2], θα πρέπει να έχει την τιμή 7, ενώ ο πίνακας p στο στοιχείο p[1][2], θα πρέπει να έχει την τιμή 0. Η εύρεση των υπολοίπων επαφίεται στον αναγνώστη... 3 Η παραλληλοποίηση με Πριν αναφέρουμε τον τρόπο με τον οποίο υλοποιήσαμε την παράλληλη εκδοχή του αλγορίθμου, κρίθηκε σκόπιμο από τους συγγραφείς να αναφερθούν μερικά στοιχειώδη, αλλά ταυτόχρονα ουσιώδη χαρακτηριστικά της συγκεκριμένης αρχιτεκτονικής. Η είναι υπολογιστική μηχανή των καρτών γραφικών (GPU) της nvidia και είναι προσβάσιμη από τους προγραμματιστές κυρίως μέσω της γλώσσας C. Χρησιμοποιείται ευρέως, καθώς τα τελευταία χρόνια είναι ραγδαία η αύξηση της ταχύτητας των καρτών γραφικών σε σύγκριση με αυτές των απλών υπολογιστικών επεξεργαστών (CPU s). Αυτό επιτυγχάνεται μέσω της χρήσης πολλών νημάτων (threads) που υποβοηθάει το καθένα στην παράλληλη, άρα και στην τάχιστη, εκτέλεση του προγράμματος. 3.1 Το προγραμματιστικό μοντέλο Για κάθε προγραμματιστή το μοντέλο της είναι μια συλλογή από threads που τρέχουν παράλληλα. Το warp είναι μία συλλογή από threads τα οποία μπορούν να εκτελούνται ταυτόχρονα στον πολυεπεξεργαστή. Το μέγεθος του warp είναι fixed για μια συγκεκριμένη GPU. Ο προγραμματιστής αποφασίζει τον αριθμό των threads που θα εκτελεστούν. Αν ο αριθμός των threads είναι μεγαλύτερος από το μέγεθος του warp, τότε διαμοιράζονται χρονικά στον πολυεπεξεργαστή. Μία συλλογή από threads ονομάζεται block και τρέχει στον πολυεπεξεργαστή σε μια συγκεκριμένη χρονική στιγμή. Πολλά blocks μπορούν να ανατεθούν σε έναν πολυεπεξεργαστή και η εκτέλεσή τους να είναι χρονικά διαμοιραζόμενη. Μια μοναδική εκτέλεση σε μια συσκευή δημιουργεί έναν αριθμό από blocks. Μια συλλογή από όλα τα blocks σε μια μοναδική εκτέλεση ονομάζεται grid. Ολα τα threads από όλα τα blocks που εκτελούνται σε ένα μοναδικό πολυεπεξεργαστή χωρίζουν τους πόρους ισομερώς ανάμεσά τους. Κάθε thread και block έχει μία μοναδική ταυτότητα (id), στο οποίο μπορούμε να έχουμε πρόσβαση κατά τη διάρκεια της εκτέλεσης. Κάθε thread εκτελεί ένα μοναδικό set εντολών που ονομάζεται kernel. Χρησιμοποιώντας τα id s των threads και των blocks, μπορεί κανείς να εφαρμόσει τη διεργασίά του kernel σε διαφορετικό set δεδομένων. Το μοντέλο της παρέχει σχεδόν μια PRAM (parallel access random machine) αν ένας χρησιμοποιεί μόνο τη μνήμη 5
της συσκευής. Παρόλα αυτά, οι πολυεπεξεργαστές ακολουθούν ένα μοντέλο του οποίου η επίδοση μπορεί να βελτιωθεί κάνοντας χρήση της διαμεριζόμενης μνήμης (shared memory) που μπορεί να προσπελαστεί πιο γρήγορα από τη μνήμη της συσκευής (global memory). Η αρχιτεκτονική του υλικού επιτρέπει πολλαπλά set εντολών να εκτελεστούν σε διαφορετικούς πολυεπεξεργαστές. Μια οπτικοποίηση του παραπάνω και πώς διχοτομείται ένας πίνακας σε blocks και threads, παρουσιάζεται στο ακόλουθο σχήμα: first thread visualization first block 3.2 Το παράλληλο APSP Ουσιαστικά εδώ έχουμε να κάνουμε με τον εντελώς αντίστοιχο σειριακό αλγόριθμο που αναλύσαμε ανωτέρω. Μόνο που ουσιαστικά ο αλγόριθμος διαμερίζεται καταλλήλως, ώστε να μοιραστεί η εκτέλεσή του σε καθένα από τα threads της GPU. Το πώς ακριβώς πραγματοποιείται αυτό, αναλύεται παρακάτω. Παρατηρούμε ότι στο σειριακό αλγόριθμο κάθε φορά που εκτελείται η πρώτη εξωτερική επανάληψη του k, ανανεώνονται οι τιμές των πινάκων g και p, οι οποίοι για χάριν ευκολίας έχουν υλοποιηθεί ως μονοδιάστατοι πίνακες και αν η διάστασή τους είναι n n, τότε για να πάρουμε το στοιχείο π.χ. g[i][j], χρησιμοποιούμε την έκφραση g[i n + j]. Η ανανέωση αυτών των τιμών 6
γίνεται με την κλήση της global συνάρτησης p floydwarshall() από πολλά threads, καθένα από τα οποία αναλαμβάνει να ανανεώσει μια διαφορετική θέση του πίνακα. Ουσιαστικά καλείται η συνάρτηση για ένα συγκεκριμένο αριθμό blocks καθένα από τα οποία έχει ένα συγκεκριμένο αριθμό threads, που στη δική μας περίπτωση, στον αριθμό των threads ανά block δώσαμε την default τιμή BLOCKSIZE 256. Οταν το κάθε thread εκτελεί την συνάρτηση p floydwarshall(), υπολογίζεται η θέση του ανάμεσα στα threads χρησιμοποιώντας το id του αλλά και το id του block στο οποίο ανήκει (blockidx.x blockdim.x + threadidx.x). Επειτα υπολογίζεται η τετμημένη ix / x και η τεταγμένη του πίνακα ix - i x και ανανεώνεται η τιμή στην αντίστοιχη θέση (i x + j) του πίνακα. Στο τέλος όλων των επαναλήψεων του k, οι πίνακες g και p θα περιέχουν τις ζητούμενες τιμές. 4 Οι μετρήσεις στον Διάδη Παρακάτω παραθέτουμε αρχικά τους πίνακες με τα χρονικά αποτελέσματα α- πό τον Διάδη για N {256, 512, 1024, 2048, 4096}. Το With DT αναφέρεται στον χρόνο συμπεριλαμβάνοντας τη μεταφορά των δεδομένων από και προς την CPU, ενώ το Without DT χωρίς αυτόν. Προσπαθήσαμε οποτεδήποτε τρέχαμε το πρόγραμμα να είμαστε οι μόνοι που το εκτελούν, για να μην υπάρχουν καθυστερήσεις άνευ λόγου και αιτίας. Τα αποτελέσματα που λάβαμε είναι πραγματικά εκπληκτικά και παρουσιάζουν τη μεγάλη δύναμη που έχει μια GPU και πόσο πολύ μπορεί να βοηθήσει στην εξοικονόμηση χρόνου. Θα πρέπει να σημειωθεί ότι κάθε σειρά πειραμάτων για συγκεκριμένο και fixed N, δηλαδή κάθε testcase, εκτελέστηκε δέκα φορές και από όλες αυτές τις περιπτώσεις κρατήσαμε το μέσο χρόνο εκτέλεσης. Τέλος, να επισημανθεί πως ο αριθμός του blocksize δεν είναι fixed, αλλά μεταβάλλεται ανάλογα με την είσοδο των σημείων που δίνει ο χρήστης. Συγκεκριμένα, στην μέγιστη τιμή της εισόδου, δηλαδή για 4096, το blocksize μεταβάλλεται και γίνεται ίσο με 512, ώστε να υπολογιστούν επαρκώς όλα τα σημεία του πίνακα. Σε όλες τις υπόλοιπες περιπτώσεις παραμένει ίσο με 256. Υστερα από αρκετές εκτελέσεις, παρατηρήσαμε ότι αυτή είναι η τιμή που δίνει τους καλύτερους χρόνους. Ν Serial With DT Without DT 256 0.155806 0.002746 0.002169 512 1.215777 0.016744 0.014368 1024 9.850609 0.113203 0.104249 2048 79.969685 0.829737 0.797399 4096 629.537616 6.682981 6.560377 7
4.1 Διαγράμματα Εξαιτίας του γεγονότος των δύο χρόνων (του With DT και Without DT) που είναι πολύ κοντά μεταξύ τους και ταυτόχρονα πολύ κοντά στο 0, τα διαγράμματα προσφέρονται απλώς ως κάτι ενδεικτικό κι όχι απαραίτητα ακριβές. Για την απόλυτη ακρίβεια σε χρόνους για όλες τις πιθανείς τιμές, παρακαλώ να ανατρέξετε στον ειδικό πίνακα που έχουμε δημιουργήσει στο τέλος της προηγούμενης υποενότητας. Συγκεντρωτικό διάγραμμα Στο συγκεκριμένο διάγραμμα, παρουσιάζονται οι χρόνοι και για τις τρεις συνολικά περιπτώσεις. Ωστόσο, όπως θα έχετε παρατηρήσει και από τον προηγούμενο πίνακα, επειδή η ταχύτητα του σειριακού αυξάνεται εκθετικά ενώ του παράλληλου παραμένει σε χαμηλά επίπεδα, δεν είναι ευκρινή τα διαγράμματα των παράλληλων εκδοχών, μιας και τείνουν σχεδόν στο μηδέν. Αναλυτικά και μόνα τους τα δύο παράλληλα παραδείγματα παρουσιάζονται στο επόμενο διάγραμμα. 700 Experimental Results 600 500 t(sec) 400 300 200 100 0 0 500 1000 1500 2000 2500 3000 3500 4000 4500 Vertices(N) 8
Par allhla & Dianemhm ena Διάγραμμα παραλλήλων Οπως αναφέρθηκε, για να παρουσιαστούν καλύτερα διαγραμματικά οι επιδόσεις των παράλληλων για τις διάφορες τιμές του N, απομονώθηκαν και παρουσιάζονται εδώ μόνα τους. Προφανώς, το πιο γρήγορο εκτελέσιμο είναι αυτό στο οποίο δεν προσμετράται ο χρόνος αποστολής και λήψης δεδομένων από και προς την CPU αντίστοιχα. 7 Experimental Results 6 5 t(sec) 4 3 2 1 0 0 500 1000 1500 2000 2500 3000 3500 4000 4500 Vertices(N) 5 Επίλογος Φτάνοντας στο τέλος, ελπίζουμε να καλύψαμε τις βασικές πτυχές της παραλληλοποίησης και να έγινε κατανοητό μέσα από τα γραφόμενά μας τόσο το multithreading, όσο και η βαθύτερη κατανόηση του All Pairs Shortest Path Algorithm. Ολα τα σχήματα έγιναν εξ ολοκλήρου στο GIMP, ενώ τα διαγράμματα στο matlab. Η υλοποίηση του κώδικα έγινε ύστερα από στενή συνεργασία και έπειτα από αρκετές συναντήσεις, προβληματισμούς και ανταλλαγή απόψεων μεταξύ των δύο φοιτητών. Ευχαριστούμε πολύ για την ανάγνωση... Κυπριώτη Αικατερίνη Μόσχογλου Στυλιανός 9
6 Βιβλιογραφία [1] Rob Farber. Application Design and Development. Elsevier, 2011. [2] Jason Sanders and Edward Kandrot. by example. Addison-Wesley, 2010. [3] Wei-Lung Dustin Tseng. All-pairs shortest path algorithms. Cornell University, CS Department, 2006. 10