Κατανεμημένος και Παράλληλος Προγραμματισμός Ηλίας Κ. Σάββας Καθηγητής Τμήμα Μηχανικών Πληροφορικής ΤΕ, ΤΕΙ Θεσσαλίας Email: savvas@teilar.gr Παράλληλος προγραμματισμός OpenMP (2) Παραλληλοποίηση των βρόγχων (for loops) Παραδείγματα Χρονομέτρηση Καλές πρακτικές Ταξινόμηση άρτιου περιττού Μέτρηση λέξεων σε αρχείο κειμένου ΕνόητηταI #8 - OpenMP τεχνικές 2 Ο βρόγχος for H OMP διαχειρίζεται χωρίς την επέμβαση του προγραμματιστή τους βρόγχους του for (δεν μπορούν να παραλληλισθούν βρόγχοι while ή do..while): # pragma omp parallel { --- # pragma omp for ή # pragma omp parallell for { --- ΕνόητηταI #8 - OpenMP τεχνικές 3 1
Παραδείγματα (1 από 6) # pragma omp parallel { # pragma omp for printf("\ndiergasia %d - - ektelei to %d tou for loop...", omp_get_thread_num(), i); N = 10 Diergasia 3 -- ektelei to 8 tou for loop... Diergasia 3 -- ektelei to 9 tou for loop... Diergasia 2 -- ektelei to 6 tou for loop... Diergasia 2 -- ektelei to 7 tou for loop... Diergasia 1 -- ektelei to 3 tou for loop... Diergasia 1 -- ektelei to 4 tou for loop... Diergasia 1 -- ektelei to 5 tou for loop... Diergasia 0 -- ektelei to 0 tou for loop... Diergasia 0 -- ektelei to 1 tou for loop... Diergasia 0 -- ektelei to 2 tou for loop... ΕνόητηταI #8 - OpenMP τεχνικές 4 Παραδείγματα (2 από 6) # pragma omp parallel shared(n) private(i) { # pragma omp for printf("\ndiergasia %d - - ektelei to %d tou for loop...", omp_get_thread_num(), i); N = 10 Diergasia 3 -- ektelei to 8 tou for loop... Diergasia 3 -- ektelei to 9 tou for loop... Diergasia 2 -- ektelei to 6 tou for loop... Diergasia 2 -- ektelei to 7 tou for loop... Diergasia 1 -- ektelei to 3 tou for loop... Diergasia 1 -- ektelei to 4 tou for loop... Diergasia 1 -- ektelei to 5 tou for loop... Diergasia 0 -- ektelei to 0 tou for loop... Diergasia 0 -- ektelei to 1 tou for loop... Diergasia 0 -- ektelei to 2 tou for loop... ΕνόητηταI #8 - OpenMP τεχνικές 5 Παραδείγματα (3 από 6) reduction(+:sum) sum += A[i]; N = 10 Athroisma = 47 ΕνόητηταI #8 - OpenMP τεχνικές 6 2
Παραδείγματα (4 από 6) private (i) shared(n) reduction(+:sum) { sum += A[i]; printf("\ndiergasia %d, i=%d, sum=%d", omp_get_thread_num(), i, sum); ΕνόητηταI #8 - OpenMP τεχνικές 7 Παραδείγματα (5 από 6) private (i) shared(n) reduction(+:sum) nowait sum += A[i]; Ότι και στο προηγούμενο παράδειγμα με την διαφορά ότι εάν κάποια διεργασία (πυρήνας) τερματίσει νωρίτερα από τους υπόλοιπους μπορεί να του ανατεθεί επόμενη εργασία. Προσοχή: πρέπει να χρησιμοποιείται μόνο αν είμαστε σίγουροι ότι δεν υπάρχει εξάρτηση διεργασιών. ΕνόητηταI #8 - OpenMP τεχνικές 8 Παραδείγματα (6 από 6) private (i) shared(n) reduction(+:sum) { sum += A[i]; printf("\ndiergasia %d, i=%d, sum=%d", omp_get_thread_num(), i, sum); printf("\n\nathroisma = %d\n\n", sum); N = 10: 0=3, 1=6, 2=7, 3=5, 4=3, 5=5, 6=6, 7=2, 8=9, 9=1 Diergasia 2, i=6, sum=6 Diergasia 2, i=7, sum=8 Diergasia 3, i=8, sum=9 Diergasia 3, i=9, sum=10 Diergasia 1, i=3, sum=5 Diergasia 1, i=4, sum=8 Diergasia 1, i=5, sum=13 Diergasia 0, i=0, sum=3 Diergasia 0, i=1, sum=9 Diergasia 0, i=2, sum=16 Athroisma = 47 ΕνόητηταI #8 - OpenMP τεχνικές 9 3
Βρόγχοι: static vs. dynamic (1 από 4) Schedule static: o βρόγχος διαιρείται σε συνεχή τμήματα ανάλογα με τους πυρήνες/διεργασίες. Schedule dynamic: βρόγχος διαιρείται σε τμήματα μεγέθους 1 και εάν κάποια διεργασία/πυρήνας τελειώνει τότε του ανατίθεται νέο τμήμα (πάντα μεγέθους 1 εκτός και εάν ορισθούν διαφορετικά πχ schedule(dynamic, 2) ) ΕνόητηταI #8 - OpenMP τεχνικές 10 Βρόγχοι: static vs. dynamic (2 από 4) #pragma omp parallel for schedule(static) for(i=0; i<10; i++) printf("\n Diergasia = %d, i = %d", omp_get_thread_nu m(),i); Κάθε διεργασία παίρνει από την αρχή ένα μέρος του βρόγχου (σταθερό) Diergasia = 0, i = 0 Diergasia = 0, i = 1 Diergasia = 0, i = 2 Diergasia = 3, i = 8 Diergasia = 3, i = 9 Diergasia = 1, i = 3 Diergasia = 1, i = 4 Diergasia = 1, i = 5 Diergasia = 2, i = 6 Diergasia = 2, i = 7 ΕνόητηταI #8 - OpenMP τεχνικές 11 Βρόγχοι: static vs. dynamic (3 από 4) #pragma omp for schedule(dynamic) for(i=0; i<10; i++) printf("\n Diergasia = %d, i = %d", omp_get_thread_n um(),i); Κάθε διεργασία παίρνει ένα μέρος του βρόγχου και μόλις τελειώσει παίρνει επόμενο, κλπ Diergasia = 0, i = 2 Diergasia = 0, i = 4 Diergasia = 3, i = 3 Diergasia = 3, i = 6 Diergasia = 3, i = 7 Diergasia = 3, i = 8 Diergasia = 3, i = 9 Diergasia = 2, i = 1 Diergasia = 1, i = 0 Diergasia = 0, i = 5 ΕνόητηταI #8 - OpenMP τεχνικές 12 4
Βρόγχοι: static vs. dynamic (4 από 4) #pragma omp for schedule(dynamic, 3) for(i=0; i<10; i++) printf("\n Diergasia = %d, i = %d", omp_get_thread_ num(),i); Ότι και η προηγούμενη μόνο που η κάθε διεργασία παίρνει αρχικά από 3 μέρη του βρόγχου Diergasia = 0, i = 0 Diergasia = 0, i = 1 Diergasia = 0, i = 2 Diergasia = 1, i = 3 Diergasia = 1, i = 4 Diergasia = 1, i = 5 Diergasia = 2, i = 9 Diergasia = 3, i = 6 Diergasia = 3, i = 7 Diergasia = 3, i = 8 ΕνόητηταI #8 - OpenMP τεχνικές 13 Reduction Βασικοί τελεστές + με αρχική τιμή στην μεταβλητή 0 * με αρχική τιμή στην μεταβλητή 1 - με αρχική τιμή στην μεταβλητή 0 Παράδειγμα: reduction(*: x) X *= A[i] ΕνόητηταI #8 - OpenMP τεχνικές 14 Χρονομέτρηση $/bin/time./εκτελέσιμο πρόγραμμα Real : x User: y Sys : z X: ο συνολικός χρόνος εκτέλεσης του προγράμματος από την αρχή μέχρι το τέλος Y: ο απαιτούμενος χρόνος του προγράμματος χωρίς τον χρόνο που απαιτήθηκε για υπηρεσίες του λειτουργικού συστήματος Z: αποκλειστικά ο χρόνος που χρειάστηκε για υπηρεσίες του Λ.Σ. (πχ input/output κλπ) ΕνόητηταI #8 - OpenMP τεχνικές 15 5
Γρήγορη ερώτηση: τι θα εκτυπωθεί for (i=0; i<10; i++) printf( %d, I printf( \n\n ); private(i) for (i=0; i<10; i++) printf( %d, I printf( \n\n ); Δεν υπάρχει καμία διαφορά γιατί έτσι και αλλιώς η OMP μοιράζει την for σε διεργασίες με τον αντίστοιχο δείκτη ΕνόητηταI #8 - OpenMP τεχνικές 16 Πρακτικές βελτιστοποίησης Βελτιστοποίηση (ελαχιστοποίηση) των barriers Αποφυγή σειριακών τμημάτων Αποφυγή μεγάλων critical τμημάτων του προγράμματος Μεγιστοποίηση πλήθους παράλληλων τμημάτων Αποφυγή παράλληλων τμημάτων σε εσωτερικούς βρόγχους Αντίληψη μικρής εξισορρόπησης φορτίου εργασίας ΕνόητηταI #8 - OpenMP τεχνικές 17 Βελτιστοποίηση των barriers (1 από 3) # pragma omp parallel {. # pragma omp for ------- # pragma omp for nowait ------- Ένα barrier λιγότερο. Η οδηγία nowait έχει σαν αποτέλεσμα να άρει το barrier μετά τον δεύτερο βρόγχο. Έτσι όποιος πυρήνας τελειώσει νωρίτερα να μπορεί να αναλάβει επόμενη εργασία. ΕνόητηταI #8 - OpenMP τεχνικές 18 6
Βελτιστοποίηση των barriers (2 από 3) # pragma omp parallel default(none) shared(n, A, B, C, D, sum) private(i) { --- # pragma omp for nowait A[i] = 10 + B[i]; # pragma omp for nowait C[i] = 10 * D[i]; # pragma omp barrier # pragma omp for nowait reduction(+:sum) sum += A[i] + C[i]; --- ΕνόητηταI #8 - OpenMP τεχνικές 19 Βελτιστοποίηση των barriers (3 από 3) Οι δύο πρώτοι βρόγχοι θα εκτελεστούν χωρίς να περιμένει ο ένας το άλλο Για να εκτελεστεί όμως ο τρίτος βρόγχος, οι δύο πρώτοι πρέπει οπωσδήποτε να έχουν ολοκληρωθεί αφού υπάρχει σαφής εξάρτηση (dependency) των δεδομένων. ΠΡΟΣΟΧΗ: πρέπει να είμαστε βέβαιοι ότι δεν υπάρχει εξάρτηση δεδομένων όταν εφαρμόζουμε τακτικές άρσης των barriers ΕνόητηταI #8 - OpenMP τεχνικές 20 Αποφυγή παράλληλων τμημάτων σε εσωτερικούς βρόγχους (1 από 2) for (j=0; j<n; j++) for (k=0; k<n; k++) {.. Το κόστος της αίτησης παραλληλίας ανέρχεται σε N 2. ΕνόητηταI #8 - OpenMP τεχνικές 21 7
Αποφυγή παράλληλων τμημάτων σε εσωτερικούς βρόγχους (2 από 2) # pragma omp parallel for (j=0; j<n; j++) # pragma omp for for (k=0; k<n; k++) {.. To κόστος της παραλληλίας μειώνεται σημαντικά! ΕνόητηταI #8 - OpenMP τεχνικές 22 Αποφυγή σειριακών τμημάτων Είναι προφανές ότι όσο πιο μικρά και λίγα είναι τα σειριακά τμήματα (δηλαδή αυτά που δεν μπορούν να εκτελεσθούν παράλληλα) ενός προγράμματος τόσο μειώνει και ο χρόνος εκτέλεσης του παράλληλου προγράμματος (Νόμος Amdahl) Αντίστοιχα η μεγιστοποίηση του πλήθος των παράλληλων τμημάτων ελαχιστοποιεί πάλι τον χρόνο εκτέλεσης του προγράμματος ΕνόητηταI #8 - OpenMP τεχνικές 23 Αποφυγή μεγάλων critical τμημάτων του προγράμματος Critical: εάν απαιτείται κάποιο μέρος του παράλληλου τμήματος να εκτελεσθεί αποκλειστικά από μία διεργασία/πυρήνα: #pragma omp parallel shared(x) { ---- #pragma omp critical x = x + 1; Αν και όλες οι διεργασίες θα προσπαθήσουν (ανταγωνισμός) να εκτελέσουν την αύξηση του x αποκλειστικά μόνο μία θα το εκτελέσει ακριβώς επειδή είναι critical ΕνόητηταI #8 - OpenMP τεχνικές 24 8
Αντίληψη μικρής εξισορρόπησης φορτίου εργασίας Πολλές (αν όχι όλες) φορές κάποιες διεργασίες / πυρήνες αναλαμβάνουν μεγαλύτερο φόρτο εργασίας από άλλες Παράδειγμα: εργασίες σε τριγωνικούς πίνακες θα επιφέρει μεγαλύτερο φόρτο στις διεργασίες που θα διαχειριστούν γραμμές με τα περισσότερα σε πλήθος στοιχεία ΕνόητηταI #8 - OpenMP τεχνικές 25 Εξισορρόπηση φόρτου (1 από 2) Read_from_file_chunk(i) For (j=0; j<p; j++) Εργασίες σε δεδομένα Write_results_to_file_chunk(i) O παραπάνω κώδικας διαβάζει ένα αρχείο τμηματικά (chunks), η κάθε διεργασία/πυρήνας επεξεργάζεται αυτό το τμήμα και στην συνέχεια γράφονται τα αποτελέσματα σε αυτό το τμήμα του αρχείου. Πως βελτιστοποιείται??? ΕνόητηταI #8 - OpenMP τεχνικές 26 Εξισορρόπηση φόρτου (2 από 2) # pragma omp parallel Παραλληλοποίηση { # pragma omp single των I/O { Read_from_file_chunk(0) διαδικασιών με for (i=1; i<n; i++) τους υπολογισμούς # pragma omp single nowait { Read_from_file_chunk(i) schedule(dynamic) Εργασίες σε δεδομένα # pragma omp single nowait Write_results_to_file_chunk(i) ΕνόητηταI #8 - OpenMP τεχνικές 27 9
Ταξινόμηση άρτιου/περιττού (1 από 2) Μοιάζει με την μέθοδο φυσαλίδας (bubble sort) Διαχωρίζει τις συγκρίσεις σε δύο φάσεις Άρτια φάση: (Α[0],Α[1]), (Α[2],Α[3]), (Α[4],Α[5]), Περιττή φάση: (Α[1],Α[2]), (Α[3],Α[4]), (Α[5],Α[6]), ΕνόητηταI #8 - OpenMP τεχνικές 28 Ταξινόμηση άρτιου/περιττού (2 από 2) for (fasi=0; fasi<n; fasi++) if (fasi % 2 == 0 ) // άρτια φάση for (i=1; i<n; i+=2) if (A[i-1] > A[i]) { temp = A[i]; A[i] = A[i-1]; A[i-1] = temp; else // περιττή φάση for (i=1; i<n-1; i+=2) if (A[i] > A[i+1]) { temp = A[i]; A[i] = A[i+1]; A[i+1] = temp; ΕνόητηταI #8 - OpenMP τεχνικές 29 Ταξιν. άρτιου/περιττού, παράλληλη έκδοση 1 for (fasi=0; fasi<n; fasi++) if (fasi % 2 == 0 ) //άρτια default(none) shared(a, N) private(temp, i) num_threads(2) for (i=1; i<n; i+=2) if (A[i-1] > A[i]) { temp = A[i]; A[i] = A[i-1]; A[i-1] = temp; Else // περιττή φάση default(none) shared(a, N) private(temp, i) num_threads(2) for (i=1; i<n-1; i+=2) if (A[i] > A[i+1]) { temp = A[i]; A[i] = A[i+1]; A[i+1] = temp; ΕνόητηταI #8 - OpenMP τεχνικές 30 10
Ταξιν. άρτιου/περιττού, παράλληλη έκδοση 2 # pragma omp parallel num_threads(3) default(none) shared(a, N) private(temp, i) for (fasi=0; fasi<n; fasi++) { if (fasi % 2 == 0 ) // άρτια # pragma omp for for (i=1; i<n; i+=2) { if (A[i-1] > A[i]) { temp = A[i]; A[i] = A[i-1]; A[i-1] = temp; else //περιττή # pragma omp for for (i=1; i<n-1; i+=2) { if (A[i] > A[i+1]) { temp = A[i]; A[i] = A[i+1]; A[i+1] = temp; ΕνόητηταI #8 - OpenMP τεχνικές 31 Σύγκριση των δύο εκδόσεων Διεργασίες/πυρήνες 1 2 3 4 Δύο οδηγίες parallel for 0,770 0,453 0,358 0,305 Δύο οδηγίες for 0,732 0,376 0,294 0,239 0,9 0,8 0,7 0,6 2 parallel for 2 οδηγίες for 0,5 0,4 0,3 0,2 0,1 0 1 2 3 4 ΕνόητηταI #8 - OpenMP τεχνικές 32 Μέτρηση λέξεων (από αρχείο) while (! feof(f) ) { c = fgetc(f); if ( (c>='a' && c<='z') (c>='a' && c<='z') (c>='0' && c<='9')) { if (mesa_se_leksi == 0) { lekseis++; mesa_se_leksi = 1; else mesa_se_leksi = 0; Η μεταβλητή mesa_se_leksi λειτουργεί σαν flag. Όταν διαβασθεί ένας χαρακτήρας, εάν είναι γράμμα ή αριθμός τότε εάν δεν βρισκόμαστε μέσα σε λέξη αυξάνει τον μετρητή των λέξεων (lekseis) αλλιώς δεν πρέπει να μετρηθεί λέξη ΕνόητηταI #8 - OpenMP τεχνικές 33 11
Παράλληλη έκδοση (μέτρηση λέξεων) Τεμαχισμός του αρχείου σε τμήματα (chunks) Απόδοση του κάθε τμήματος σε διαφορετική διεργασία / πυρήνα Ο κάθε πυρήνας διαβάζει το τμήμα που του αντιστοιχεί Μέτρηση του πλήθους των λέξεων (τοπικά) Άθροιση όλων των επιμέρους μετρητών (reduce) ΕνόητηταI #8 - OpenMP τεχνικές 34 Ερωτήσεις; Τέλος της # 07 ενότητας Χρησιμοποιείστε το email για όποιες επιπλέον απορίες / ερωτήσεις: savvas@teilar.gr Σημειώσεις μαθήματος (και όχι μόνο): https://e-class.teilar.gr/courses/cs386/ ΕνόητηταI #8 - OpenMP τεχνικές 35 12