Εισαγωγή στον Προγραµµατισµό Πολυεπεξεργαστικών Συστηµάτων Κοινής Μνήµης µε... Λογισµικό & Προγραµµατισµός Συστηµάτων Υψηλής Επίδοσης Εργαστήριο Πληροφοριακών Συστηµάτων Υψηλών Επιδόσεων Τοµέας Λογικού Τµηµα Μηχ. Η/Υ & Πληροφορικής
Παράλληλος Υπολογισµός: Στο Hardware Πάµε Καλά Time 1998 2000 2002 Cluster SMP 100Mb 16 Boxes 32 Boxes 256+ Boxes VIA > 1Gb, Infiniband Περιορίζεται από τις ανάγκες της αγοράς και όχι την τεχνολογία 1-4 CPUs 1-8 CPUs 1-16 CPUs Processor Pentium II Xeon TM PIII, IA-64 64 Merced P4, IA-64 64 McKinley
Παράλληλος υπολογισµός: όµως τι γίνεται µε το software; Οι περισσότεροι συγγραφείς εφαρµογών αγνοούν τον παραλληλισµό (εκτός ίσως από αδρά καταµερισµένο (βαρύ) πολυνηµατισµό γιαgui s και λογισµικό συστήµατος) Γιατί; Οι δυσκολίες συγγραφής παράλληλου software υπερνικούν τα πλεονεκτήµατα Τα πλεονεκτήµατα είναι ξεκάθαρα. Για να αυξηθεί ο όγκος του παράλληλου software πρέπει να ελαχιστοποιηθούν οι δυσκολίες.
Παράδειγµα: Πρόγραµµα π. Η Ακολουθιακή Έκδοση static long num_steps = 100000; double step; void main () { int i; double x, pi, sum = 0.0; step = 1.0/(double) num_steps; for (i=1;i<= num_steps; i++){ x = (i-0.5)*step; sum = sum + 4.0/(1.0+x*x); pi = step * sum;
Πρόγραµµα π: Win32 API #include <windows.h> #define NUM_THREADS 2 HANDLE thread_handles[num_threads]; CRITICAL_SECTION hupdatemutex; static long num_steps = 100000; double step; double global_sum = 0.0; void Pi (void *arg) { int i, start; double x, sum = 0.0; start = *(int *) arg; step = 1.0/(double) num_steps; for (i=start;i<= num_steps; i=i+num_threads){ x = (i-0.5)*step; sum = sum + 4.0/(1.0+x*x); EnterCriticalSection(&hUpdateMutex); global_sum += sum; LeaveCriticalSection(&hUpdateMutex); void main () { double pi; int i; DWORD threadid; int threadarg[num_threads]; for(i=0; i<num_threads; i++) threadarg[i] = i+1; InitializeCriticalSection(&hUpdateMutex); for (i=0; i<num_threads; i++){ thread_handles[i] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) Pi, &threadarg[i], 0, &threadid); WaitForMultipleObjects(NUM_THREADS, thread_handles, TRUE,INFINITE); pi = global_sum * step; printf(" pi is %f \n",pi); ιπλασιάζεται το µέγεθος του κώδικα!
Πρόγραµµα π: H λύση του OpenMP #include <omp.h> static long num_steps = 100000; double step; #define NUM_THREADS 2 void main () { int i; double x, pi, sum = 0.0; step = 1.0/(double) num_steps; omp_set_num_threads(num_threads) #pragma omp parallel for reduction(+:sum) private(x) for (i=1;i<= num_steps; i++){ x = (i-0.5)*step; sum = sum + 4.0/(1.0+x*x); pi = step * sum;
OpenMP: Εισαγωγή C$OMP parallel do shared(a, b, c) C$OMP FLUSH #pragma omp critical OpenMP: API για τη συγγραφή πολυνηµατικών εφαρµογών C$OMP THREADPRIVATE(/ABC/) #pragma omp parallel for private(a, B) CALL OMP_SET_NUM_THREADS(10) call omp_test_lock(jlok) Σύνολο οδηγιών προς τον µεταγλωττιστή και call OMP_INIT_LOCK (ilok) C$OMP MASTER συναρτήσεων βιβλιοθήκης, C$OMP διαθέσιµο ATOMIC στον C$OMP SINGLE προγραµµατιστή PRIVATE(X) παράλληλων setenv OMP_SCHEDULE συστηµάτων dynamic ιευκολύνει τη συγγραφή πολυνηµατικών προγραµµάτων σε Fortran, C and C++ C$OMP PARALLEL DO ORDERED PRIVATE (A, B, C) C$OMP PARALLEL REDUCTION (+: A, B) Πρότυπο που συγκεντρώνει την C$OMP εµπειρία SECTIONS 18 ετών σε προγραµµατισµό πολυεπεξεργαστικών συστηµάτων κοινής µνήµης C$OMP PARALLEL COPYIN(/blk/) C$OMP ORDERED!$OMP BARRIER C$OMP DO lastprivate(xx) Nthrds = OMP_GET_NUM_PROCS() omp_set_lock(lck)
OpenMP: Υποστηρικτές Εταιρείες παραγωγής υλικού Intel, HP, SGI, IBM, SUN, Compaq Εταιρείες παραγωγής λογισµικού KAI (Intel), PGI, PSR, APR, Absoft Εταιρείες παραγωγής εφαρµογών ANSYS, Fluent, Oxford Molecular, NAG, DOE ASCI, Dash, Livermore Software κλπ.
OpenMP: Προγραµµατιστικό Μοντέλο Παραλληλισµός τύπου Fork-Join : To νήµα αρχηγός δηµιουργεί οµάδα νηµάτων σύµφωνα µε τις ανάγκες. Ο παραλληλισµός προστίθεται βαθµιαία: δηλ. το ακολουθιακό πρόγραµµα εξελίσσεται σε παράλληλο πρόγραµµα. Νήµα Αρχηγός Παράλληλα Τµήµατα
OpenMP: Ποια Είναι η Τυπική του Χρήση; Το OpenMP συνήθως χρησιµοποιείται για την παραλληλοποίηση loops: Βρες τα πιο χρονοβόρα loops. Μοίρασε τις επαναλήψεις µεταξύ νηµάτων. ιαίρεσε αυτό το loop µεταξύ πολλαπλών νηµάτων void main() { double Res[1000]; for(int i=0;i<1000;i++) { do_huge_comp(res[i]); Ακολουθιακό πρόγραµµα void main() { double Res[1000]; #pragma omp parallel for for(int i=0;i<1000;i++) { do_huge_comp(res[i]); Παράλληλο πρόγραµµα
OpenMP: Πώς Αλληλεπιδρούν τα Νήµατα; Πλεονέκτηµα : Απόκρυψη «λεπτοµερειών» από τον προγραµµατιστή Μειονέκτηµα : Απόκρυψη πολλών «λεπτοµερειών» από τον προγραµµατιστή µπορεί να βλάψει την επίδοση To OpenMP είναι µοντέλο κοινής µνήµης. Τα νήµατα επικοινωνούν µέσω διαµοιραζόµενων µεταβλητών. Ακούσια διαµοίραση δεδοµένων µπορεί να προκαλέσει races : race : Το αποτέλεσµα του προγράµµατος αλλάζει µε τυχαίο τρόπο αν τα νήµατα εκτελεστούν µε διαφορετική σειρά. Εξάλειψη races : Χρήση συγχρονισµού στα σηµεία που χρειάζεται. Ο συγχρονισµός είναι «ακριβός», άρα : Αλλαγή της δοµής του προγράµµατος και της οργάνωσης των δεδοµένων ώστε να µειωθούν οι απαιτήσεις συγχρονισµού
OpenMP: Μερικές Λεπτοµέρειες Σύνταξης (για Αρχή) Οι περισσότερες «εντολές» OpenMP είναι directives προς τον compiler ή pragmas. Για την C και C++, τα pragmas έχουν τη µορφή: #pragma omp construct [clause [clause] ] Για τη Fortran, τα directives έχουν µία από τις ακόλουθες µορφές: C$OMP construct [clause [clause] ]!$OMP construct [clause [clause] ] *$OMP construct [clause [clause] ] Αφού οι «εντολές» είναι directives και pragmas, ένα πρόγραµµα OpenMP µπορεί να µεταγλωττιστεί από compilers που δεν υποστηρίζουν OpenMP (οι τελευταίοι απλά αγνοούν τα directives / pragmas).
OpenMP: οµηµένα Τµήµατα (Βlocks) Οι περισσότερες «εντολές» OpenMPεφαρµόζονται σε δοµηµένα τµήµατα (blocks) κώδικα. οµηµένο τµήµα: ένα τµήµα κώδικα µε ένα σηµείο εισόδου στην κορυφή και ένα σηµείο εξόδου στο τέλος. Οι µόνες επιτρεπτές διακλαδώσεις είναι εντολές STOP της Fortran και exit() της C/C++. C$OMP PARALLEL 10 wrk(id) = garbage(id) res(id) = wrk(id)**2 if(conv(res(id)) goto 10 C$OMP END PARALLEL print *,id Ένα δοµηµένο τµήµα C$OMP PARALLEL 10 wrk(id) = garbage(id) 30 res(id)=wrk(id)**2 if(conv(res(id))goto 20 go to 10 C$OMP END PARALLEL if(not_done) goto 30 20 print *, id Ένα µη δοµηµένο τµήµα
OpenMP: Παράλληλα Τµήµατα Νήµατα δηµιουργούνται στο OpenMP (στη C/C++) µε το pragma omp parallel. Για παράδειγµα, για να δηµιουργηθεί ένα παράλληλο τµήµα µε 4 νήµατα: Κάθε νήµα εκτελεί για λογαριασµό του τον κώδικα µέσα στο δοµηµένο block του παράλληλου τµήµατος double A[1000]; omp_set_num_threads(4); #pragma omp parallel { int ID = omp_thread_num(); pooh(id,a); Κάθε νήµα καλεί την pooh(id) για ID = 0 έως 3
OpenMP: Παράλληλα Τµήµατα Κάθε νήµα εκτελεί για λογαριασµό του τον ίδιο κώδικα. double A[1000]; omp_set_num_threads(4) double A[1000]; omp_set_num_threads(4); #pragma omp parallel { int ID = omp_thread_num(); pooh(id, A); printf( all done\n ); Ένα µοναδικό αντίγραφο του Α µοιράζεται µεταξύ των νηµάτων. pooh(0,a) printf( all done\n ); pooh(1,a) pooh(2,a) pooh(3,a) Τα νήµατα προχωρούν µόνο όταν έχουν τελειώσει όλα (δηλ. barrier)
Άσκηση 1: Ένα πολυνηµατικό πρόγραµµα Hello world Γράψτε ένα πολυνηµατικό πρόγραµµα στο οποίο κάθε νήµα τυπώνει ένα απλό νήµατα (π.χ. hello world ). Χρησιµοποιήστε 2 ξεχωριστές εντολές printf και το thread ID: int ID = omp_get_thread_num(); printf( hello(%d), ID); printf( world(%d), ID); Τι γίνεται όταν εκτελείται ταυτόχρονα I/O από πολλαπλά νήµατα;
OpenMP: Μερικές Πρώτες Λεπτοµέρειες Dynamic mode (default): Ο αριθµός των νηµάτων που χρησιµοποιούνται για την εκτέλεση παράλληλων τµηµάτων µπορεί να διαφέρει µεταξύ διαφορετικών τµηµάτων. Ο ορισµός του αριθµού των νηµάτων αφορά τον µέγιστο αριθµό νηµάτων ενδεχοµένως η εκτέλεση να γίνει µε λιγότερα νήµατα. Static mode: Ο αριθµός των νηµάτων είναι σταθερός και µάλιστα ακριβώς αυτός που καθορίζεται από τον προγραµµατιστή. Το OpenMP υποστηρίζει εµφωλευµένα παράλληλα τµήµατα, όµως Ένας compiler ενδεχοµένως να επιλέξει να εκτελέσει σειριακά όλα τα επίπεδα µετά το 1ο.
OpenMP: οµές ιαµοίρασης Έργου Το omp for κατανέµει τις επαναλήψεις ενός loop µεταξύ των νηµάτων µιας οµάδας #pragma omp parallel #pragma omp for for (I=0;I<N;I++){ NEAT_STUFF(I); Εξ ορισµού υπονοείται barrier στο τέλος του omp for. Για να αφαιρεθεί το barrier χρησιµοποιούµε την παράµετρο nowait
οµές ιαµοίρασης Έργου: Γιατί Είναι Χρήσιµες; (Παράδειγµα) Ακολουθιακός Κώδικας Παράλληλο Τµήµα OpenMP Παράλληλο τµήµα OpenMP µε omp for για διαµοίραση έργου for(i=0;i<n;i++) { a[i] = a[i] + b[i]; #pragma omp parallel { int id, i, Nthrds, istart, iend; id = omp_get_thread_num(); Nthrds = omp_get_num_threads(); istart = id * N / Nthrds; iend = (id+1) * N / Nthrds; for(i=istart;i<iend;i++) { a[i] = a[i] + b[i]; #pragma omp parallel #pragma omp for schedule(static) for(i=0;i<n;i++) { a[i] = a[i] + b[i];
Εντολή omp for: Η Παράµετρος schedule Η παράµετρος schedule επηρεάζει την κατανοµή των επαναλήψεων στα νήµατα schedule(static [,chunk]) Μοίρασε «κοµµάτια» επαναλήψεων µεγέθους chunk κυκλικά στα νήµατα schedule(dynamic[,chunk]) Κάθε νήµα παίρνει chunk iterations από µια ουρά µέχρι να εξαντληθούν οι επαναλήψεις schedule(guided[,chunk]) Τα νήµατα παίρνουν δυναµικά «κοµµάτια» επαναλήψεων. Το µέγεθος των κοµµατιών είναι αρχικά µεγάλο και µειώνεται σταδιακά σε µέγεθος chunk schedule(runtime) Ο τρόπος scheduling και η παράµετρος chunk λαµβάνονται δυναµικά, κατά το χρόνο εκτέλεσης από τις κατάλληλες µεταβλητές περιβάλλοντος.
OpenMP: οµές ιαµοίρασης Έργου Ηδοµή διαµοίρασης έργου sections αναθέτει ένα διαφορετικό δοµηµένο block σε κάθε νήµα #pragma omp parallel #pragma omp sections { X_calculation(); #pragma omp section y_calculation(); #pragma omp section z_calculation(); Εξ ορισµού υπονοείται barrier στο τέλος του omp sections. Για να αφαιρεθεί το barrier χρησιµοποιούµε την παράµετρο nowait
OpenMP: Συνδυασµός της Εντολής parallel µε οµές ιαµοίρασης Έργου #pragma omp parallel for for (I=0;I<N;I++){ NEAT_STUFF(I); Υπάρχει και parallel sections
Άσκηση 2: Πολυνηµατικό Πρόγραµµα Υπολογισµού του π Στην επόµενη διαφάνεια θα δείτε ένα πρόγραµµα που χρησιµοποιεί αριθµητική ολοκλήρωση για τον υπολογισµό προσέγγισης του π. Παραλληλοποιήστε το πρόγραµµα χρησιµοποιώντας OpenMP. Υπάρχουν εναλλακτικές επιλογές Σαν πρόγραµµα SPMD µε χρήση παράλληλου τµήµατος µόνο. Με χρήση δοµής διαµοίρασης έργου. Πρέπει να διασφαλίσετε ότι το ένα νήµα δεν θα γράφει πάνω στις µεταβλητές του άλλου.
Πρόγραµµα π: H Ακολουθιακή Έκδοση static long num_steps = 100000; double step; void main () { int i; double x, pi, sum = 0.0; step = 1.0/(double) num_steps; for (i=1;i<= num_steps; i++){ x = (i-0.5)*step; sum = sum + 4.0/(1.0+x*x); pi = step * sum;
Πρόγραµµα π: H Έκδοση µε Παράλληλο Τµήµα (SPMD) #include <omp.h> static long num_steps = 100000; double step; #define NUM_THREADS 2 void main () { int i; double x, pi, sum[num_threads]; step = 1.0/(double) num_steps; omp_set_num_threads(num_threads) #pragma omp parallel { double x; int id; id = omp_get_thread_num(); Προγράµ- µατα SPMD Κάθε νήµα εκτελεί τον ίδιο κώδικα. Το ID του νήµατος χρησιµοποιείται για τη διαφοροποίηση της συµπεριφοράς. for (i=id, sum[id]=0.0;i< num_steps; i=i+num_threads){ x = (i+0.5)*step; sum[id] += 4.0/(1.0+x*x); for(i=0, pi=0.0;i<num_threads;i++)pi += sum[i] * step;
Πρόγραµµα π: Η Έκδοση µε οµή ιαµοίρασης Έργου #include <omp.h> static long num_steps = 100000; double step; #define NUM_THREADS 2 void main () { int i; double x, pi, sum[num_threads]; step = 1.0/(double) num_steps; omp_set_num_threads(num_threads) #pragma omp parallel { double x; int id; id = omp_get_thread_num(); sum[id] = 0; #pragma omp for for (i=id;i< num_steps; i++){ x = (i+0.5)*step; sum[id] += 4.0/(1.0+x*x); for(i=0, pi=0.0;i<num_threads;i++)pi += sum[i] * step;
OpenMP: Περισσότερες Λεπτοµέρειες: Scope των Εντολών OpenMP Εντολές OpenMP µπορεί να επεκτείνονται σε πολλά αρχεία. C$OMP PARALLEL call whoami C$OMP END PARALLEL Static ή lexical extent του παράλληλου τµήµατος poo.f Dynamic extent του παράλληλου τµήµατος: περιλαµβάνει το static extent + bar.f subroutine whoami external omp_get_thread_num integer iam, omp_get_thread_num iam = omp_get_thread_num() C$OMP CRITICAL print*, Hello from, iam C$OMP END CRITICAL Orphan directives return εµφανίζονται εκτός end παράλληλου τµήµατος
Περιβάλλον εδοµένων: Default Χαρακτηριστικά Αποθήκευσης εδοµένων Προγραµµατιστικό µοντέλο κοινής µνήµης: Οι περισσότερες µεταβλητές είναι by default κοινές Οι global µεταβλητές είναι κοινές µεταξύ των νηµάτων Fortran: COMMON blocks, SAVE variables, MODULE variables C: File scope variables, static Όµως δεν είναι τα πάντα κοινά... Οι stack variables σε υπο-προγράµµατα που καλούνται από παράλληλα τµήµατα είναι ιδιωτικές Οι automatic variables µέσα σε ένα δοµηµένο block εντολών είναι ιδιωτικές.
Περιβάλλον εδοµένων: Παραδείγµατα Χαρακτηριστικών Αποθήκευσης εδοµένων program sort common /input/ A(10) integer index(10) call input C$OMP PARALLEL call work(index) C$OMP END PARALLEL print*, index(1) Τα A, index και count είναι κοινά µεταξύ των νηµάτων. Το temp είναι ιδιωτικό σε κάθε νήµα A, index, count subroutine work common /input/ A(10) real temp(10) integer count save count temp temp temp A, index, count
Περιβάλλον εδοµένων: Αλλαγή των Χαρακτηριστικών Αποθήκευσης Ο προγραµµατιστής µπορεί να αλλάξει τα χαρακτηριστικά αποθήκευσης των µεταβλητών χρησιµοποιώντας ένα από τα ακόλουθα SHARED PRIVATE FIRSTPRIVATE THREADPRIVATE Η τιµή µιας ιδιωτικής µεταβλητής εντός ενός παράλληλου loop µπορεί να «µεταδοθεί» σαν καθολική τιµή εκτός του loop µε τη: LASTPRIVATE H default συµπεριφορά µπορεί να µεταβληθεί µε τη: DEFAULT (PRIVATE SHARED NONE) Όλες οι εντολές αυτής της διαφάνειας έχουν ισχύ στο lexical extent της εντολής OpenMP. Όλες οι εντολές δεδοµένων εφαρµόζονται σε παράλληλα τµήµατα και δοµές διαµοίρασης έργου εκτός της shared η οποία εφαρµόζεται µόνο σε παράλληλα τµήµατα.
Εντολή private Η private(var) δηµιουργεί ιδιωτικό αντίγραφο της var για κάθε νήµα Η τιµή δεν είναι αρχικοποιηµένη Το ιδιωτικό αντίγραφο δεν συσχετίζεται (όσον αφορά τον χώρο αποθήκευσης) µε το αυθεντικό Παρά την αρχικοποίηση, το IS έχει ακαθόριστη τιµή σε αυτό το σηµείο program wrong IS = 0 C$OMP PARALLEL DO PRIVATE(IS) DO J=1,1000 IS = IS + J 1000 CONTINUE print *, IS Το IS δεν έχει αρχικοποιηθεί
Εντολή firstprivate Firstprivate : ειδική περίπτωση του private. To ιδιωτικό αντίγραφο κάθε νήµατος αρχικοποιείται µε την αντίστοιχη τιµή του νήµατος αρχηγού. program almost_right IS = 0 C$OMP PARALLEL DO FIRSTPRIVATE(IS) DO J=1,1000 IS = IS + J 1000 CONTINUE print *, IS Κάθε νήµα έχει ιδιωτικό αντίγραφο του IS µε αρχική τιµή 0 Παρά την αρχικοποίηση, το IS έχει ακαθόριστη τιµή σε αυτό το σηµείο
Εντολή lastprivate Περνά την τιµή της ιδιωτικής µεταβλητής από την τελευταία επανάληψη που εκτελέστηκε στην καθολική µεταβλητή program closer IS = 0 C$OMP PARALLEL DO FIRSTPRIVATE(IS) C$OMP+ LASTPRIVATE(IS) DO J=1,1000 IS = IS + J 1000 CONTINUE print *, IS Κάθε νήµα έχει ιδιωτικό αντίγραφο του IS µε αρχική τιµή 0 Το IS έχει την τιµή που είχε στην τελευταία επανάληψη (δηλ. για J=1000)
OpenMP: Άλλο ένα Παράδειγµα Σχετικά µε το Περιβάλλον εδοµένων Παράδειγµα µε χρήση των PRIVATE και FIRSTPRIVATE αρχικά οι µεταβλητές A,B, και C = 1 C$OMP PARALLEL PRIVATE(B) C$OMP& FIRSTPRIVATE(C) Μέσα στο παράλληλο τµήµα : Το A είναι κοινό µεταξύ των νηµάτων και ίσο µε 1 Τα B και C είναι ιδιωτικά σε κάθε νήµα. Το B έχει ακαθόριστη αρχική τιµή Το C έχει αρχική τιµή 1 Μετά το παράλληλο τµήµα : Οι τιµές των Β και C είναι ακαθόριστες
OpenMP: Eντολή Default Ηεξ ορισµού τιµή είναι DEFAULT(SHARED) (οπότε αν αυτή είναι η επιθυµητή συµπεριφορά δεν χρειάζεται να κάνουµε τίποτε) Για να αλλάξουµε την εξ ορισµού σηµπεριφορά: DEFAULT(PRIVATE) κάθε µεταβλητή στο static extent του παράλληλου τµήµατος γίνεται ιδιωτική (σαν να είχε καθοριστεί αυτό ρητά µε εντολή private) DEFAULT(NONE): καµία εξ ορισµού συµπεριφορά για τις µεταβλητές στο static extent. Πρέπει να καθοριστεί ρητά η συµπεριφορά για όλες τις µεταβλητές Μόνο το Fortran API υποστηρίζει default(private). Η C/C++ υποστηρίζει µόνο default(shared) ή default(none).
OpenMP: Παράδειγµα Εντολής Default itotal = 1000 C$OMP PARALLEL PRIVATE(np, each) np = omp_get_num_threads() each = itotal/np C$OMP END PARALLEL itotal = 1000 C$OMP PARALLEL DEFAULT(PRIVATE) SHARED(itotal) np = omp_get_num_threads() each = itotal/np C$OMP END PARALLEL Οι δύο κώδικες είναι ισοδύναµοι
Threadprivate Κάνει global δεδοµένα ιδιωτικά σε κάθε νήµα Fortran: COMMON blocks C: File scope και static variables ιαφορετική συµπεριφορά από το PRIVATE Με το PRIVATE οι global µεταβλητές αποκρύπτονται. Το THREADPRIVATE διατηρεί το global scope σε κάθε νήµα Οι µεταβλητές threadprivate µπορούν να αρχικοποιηθούν χρησιµοποιώντας εντολές COPYIN ή DATA.
Παράδειγµα threadprivate Θεωρήστε 2 διαφορετικές συναρτήσεις που καλούνται εντός του ίδιου παράλληλου τµήµατος. subroutine poo parameter (N=1000) common/buf/a(n),b(n) C$OMP THREADPRIVATE(/buf/) do i=1, N B(i)= const* A(i) end do return end subroutine bar parameter (N=1000) common/buf/a(n),b(n) C$OMP THREADPRIVATE(/buf/) do i=1, N A(i) = sqrt(b(i)) end do return end Εξαιτίας της εντολής threadprivate, κάθε νήµα που εκτελεί αυτές τις συναρτήσεις έχει δικό του αντίγραφο του common block /buf/.
OpenMP: Reduction Επηρεάζει στην ουσία τον τρόπο «διαµοίρασης» των µεταβλητών: reduction (op : list) Οι µεταβλητές στο list πρέπει να είναι shared στο παράλληλο τµήµα στου οποίου το scope βρισκόµαστε. Εντός µια δοµής parallel ή διαµοίρασης εργασίας: ηµιουργείται τοπικό αντίγραφο κάθε µεταβλητής της λίστας και αρχικοποιείται (ανάλογα µε την πράξη op π.χ. 0 για + ) Τα τοπικά αντίγραφα συνδυάζονται ώστε να εκφυλιστούν σε ένα µοναδικό global αντίγραφο στο τέλος της δοµής
OpenMP: Παράδειγµα reduction #include <omp.h> #define NUM_THREADS 2 void main () { int i; double ZZ, func(), res=0.0; omp_set_num_threads(num_threads) #pragma omp parallel for reduction(+:res) private(zz) for (i=0; i< 1000; i++){ ZZ = func(i); res = res + ZZ;
Άσκηση 3: Πολυνηµατικό Πρόγραµµα π Επιστρέψτε στο πρόγραµµα υπολογισµού προσέγγισης του πκαι αυτή τη φορά, χρησιµοποιήστε private, reduction και µια δοµή διαµοίρασης έργου για την παραλληλοποίησή του. Πόσο µοιάζει στο αρχικό, ακολουθιακό πρόγραµµα;
OpenMP Πρόγραµµα π : Parallel for µε reduction #include <omp.h> static long num_steps = 100000; double step; #define NUM_THREADS 2 void main () { int i; double x, pi, sum = 0.0; step = 1.0/(double) num_steps; omp_set_num_threads(num_threads) #pragma omp parallel for reduction(+:sum) private(x) for (i=1;i<= num_steps; i++){ x = (i-0.5)*step; sum = sum + 4.0/(1.0+x*x); pi = step * sum; To OpenMP προσθέτει 2-4 γραµµές κώδικα
OpenMP: Συγχρονισµός To OpenMPορίζει τις ακόλουθες εντολές για την υποστήριξη συγχρονισµού: atomic critical section barrier flush ordered single master Στην πραγµατικότητα είναι δοµή διαµοίρασης έργου που περιλαµβάνει συγχρονισµό. Στην ουσία δεν είναι εντολή συγχρονισµού.
OpenMP: Συγχρονισµός Μόνο ένα νήµα µπορεί κάθε στιγµή να µπει στο critical section. C$OMP PARALLEL DO PRIVATE(B) C$OMP& SHARED(RES) DO 100 I=1,NITERS B = DOIT(I) C$OMP CRITICAL CALL CONSUME (B, RES) C$OMP END CRITICAL 100 CONTINUE
OpenMP: Συγχρονισµός To atomicείναι ειδική περίπτωση του critical section που µπορεί να χρησιµοποιηθεί για ορισµένες απλές εντολές. Εφαρµόζεται µόνο για την ανανέωση µιας θέσης µνήµης (την ανανέωση του X στο ακόλουθο παράδειγµα) C$OMP PARALLEL PRIVATE(B) B = DOIT(I) C$OMP ATOMIC X = X + B C$OMP END PARALLEL
OpenMP: Συγχρονισµός Barrier: Κάθε νήµα περιµένει να φτάσουν όλα τα υπόλοιπα νήµατα πριν προχωρήσει. #pragma omp parallel shared (A, B, C) private(id) { id=omp_get_thread_num(); A[id] = big_calc1(id); #pragma omp barrier #pragma omp for for(i=0;i<n;i++){c[i]=big_calc3(i,a); #pragma omp for nowait for(i=0;i<n;i++){ B[i]=big_calc2(C, i); A[id] = big_calc3(id); Υπονοούµενο barrier στο τέλος παράλληλου τµήµατος υπονοούµενο barrier στο τέλος ενός omp for εν υπάρχει υπονοούµενο barrier λόγω nowait
OpenMP: Συγχρονισµός Ηεντολήordered επιβάλει την ακολουθιακή σειρά εκτέλεσης για ένα block. #pragma omp parallel private (tmp) #pragma omp for ordered for (I=0;I<N;I++){ tmp = NEAT_STUFF(I); #pragma ordered res = consum(tmp);
OpenMP: Συγχρονισµός Ηεντολήmaster χαρακτηρίζει ένα δοµηµένο block το οποίο εκτελείται µόνο από το νήµα - αρχηγό. Τα υπόλοιπα νήµατα το αγνοούν (δεν υπάρχουν υπονοούµενα barriers ή flushes). #pragma omp parallel private (tmp) { do_many_things(); #pragma omp master { exchange_boundaries(); #pragma barrier do_many_other_things();
OpenMP: Συγχρονισµός Ηεντολή single χαρακτηρίζει ένα block κώδικα το οποίο εκτελείται από ένα µόνο νήµα. Υπονοούνται barrier και flush στο τέλος του single block. #pragma omp parallel private (tmp) { do_many_things(); #pragma omp single { exchange_boundaries(); do_many_other_things();
OpenMP: Συγχρονισµός Ηεντολήflush δηλώνει ένα σηµείο στο οποίο το νήµα επιχειρεί να δηµιουργήσει συνεπή εικόνα της µνήµης. All memory operations (both reads and writes) defined prior to the sequence point must complete. Όλες οι πράξεις µνήµης (αναγνώσεις και εγγραφές) που ορίζονται πριν από αυτό το σηµείο πρέπει να ολοκληρωθούν. Όλες οι πράξεις µνήµης (αναγνώσεις και εγγραφές) που ορίζονται µετά από αυτό το σηµείο πρέπει να εκτελεστούν µετά το flush. Οι µεταβλητές σε registers ή write buffers πρέπει να εγγραφούν στη µνήµη. Τα ορίσµατα του flush ορίζουν ποιες µεταβλητές θα γίνουν flush. Αν δεν υπάρχουν ορίσµατα όλες οι ορατές στο νήµα µεταβλητές γίνονται flush.
OpenMP: Παράδειγµα flush Το παράδειγµα δείχνει πώς χρησιµοποιείται flush για την υλοποίηση συγχρονισµού point-to-point. integer ISYNC(NUM_THREADS) C$OMP PARALLEL DEFAULT (PRIVATE) SHARED (ISYNC) IAM = OMP_GET_THREAD_NUM() ISYNC(IAM) = 0 C$OMP BARRIER CALL WORK() ISYNC(IAM) = 1! I m all done; signal this to other threads C$OMP FLUSH(ISYNC) DO WHILE (ISYNC(NEIGH).EQ. 0) C$OMP FLUSH(ISYNC) END DO C$OMP END PARALLEL Όλα τα άλλα νήµατα θα δουν την εγγραφή µου Ηανάγνωση επιστρέφει «σωστή» τιµή από τη µνήµη.
OpenMP: Υπονοούµενος Συγχρονισµός Barriers υπονοούνται µετά τις ακόλουθες εντολές OpenMP: end parallel end do (except when nowait is used) end sections (except when nowait is used) end critical end single (except when nowiat is used) Flush υπονοείται µετά τις ακόλουθες εντολές OpenMP: barrier critical, end critical end do end parallel end sections end single ordered, end ordered
OpenMP: Συναρτήσεις Bιβλιοθήκης Συναρτήσεις locks omp_init_lock(), omp_set_lock(), omp_unset_lock(), omp_test_lock() Συναρτήσεις περιβάλλοντος χρόνου εκτέλεσης: Αλλαγή/Έλεγχος του αριθµού των νηµάτων omp_set_num_threads(), omp_get_num_threads(), omp_get_thread_num(), omp_get_max_threads() Ενεργοποίηση/απενεργοποίηση εµφώλευσης και dynamic mode omp_set_nested(), omp_set_dynamic(), omp_get_nested(), omp_get_dynamic() Εκτελούµε σε παράλληλο τµήµα; omp_in_parallel() Πόσοι επεξεργαστές υπάρχουν στο σύστηµα; omp_num_procs()
OpenMP: Συναρτήσεις Βιβλιοθήκης Προστασία πόρων µε locks. omp_lock_t lck; omp_init_lock(&lck); #pragma omp parallel private (tmp) { id = omp_get_thread_num(); tmp = do_lots_of_work(id); omp_set_lock(&lck); printf( %d %d, id, tmp); omp_unset_lock(&lck);
OpenMP: Συναρτήσεις Βιβλιοθήκης Για να ελεγχθεί ο αριθµός των νηµάτων που εκτελούν ένα πρόγραµµα αρχικά απενεργοποιείται το dynamic mode και κατόπιν καθορίζεται ο αριθµός των νηµάτων. #include <omp.h> void main() { omp_set_dynamic(0); omp_set_num_threads(4); #pragma omp parallel { int id=omp_get_thread_num(); do_lots_of_stuff(id);
OpenMP: Μεταβλητές Περιβάλλοντος Έλεγχος scheduling των loops εφόσον έχει καθοριστεί omp for schedule(runtime). OMP_SCHEDULE schedule[, chunk_size] Καθορισµός του default αριθµού νηµάτων. OMP_NUM_THREADS int_literal Ενεργοποίηση/απενεργοποίηση dynamic mode OMP_DYNAMIC TRUE FALSE Ενεργοποίηση/απενεργοποίηση παράλληλης εκτέλεσης εµφωλευµένων παράλληλων τµηµάτων OMP_NESTED TRUE FALSE
Βιβλιογραφία - Links http://www.openmp.org OpenMP consortium\ http://developer.intel.com/software/products/compilers Intel Compilers http://www.kai.com Kuck & Associates http://www.pgroup.com Τhe Portland Group http://pdplab.trc.rwcp.or.jp/pdperf/omni/ The OmniMP Compiler http://www.compunity.org OpenMP community http://www.cepba.upc.es/tools/nanos/nanos.htm The NANOS Compiler & Environment OpenMP C and C++ Application Program Interface Version 2.0 Μάρτιος 2002 OpenMP Fortran Application Program Interface Version 2.0 4 Νοεµβρίου 2000
Ευχαριστίες Η παρουσίαση αυτή έχει βασιστεί στο OpenMP tutorial µε τίτλο OpenMP : An API for writing portable SMP application software που παρουσιάστηκε στο SuperComputing 99 Conference από τους Tim Mattson (Intel Corporation) και Rudolf Eigenmann (Purdue University).