Υλοποίηση μεταφραστή C με επεκτάσεις OpenMP Λεοντιάδης Ηλίας Τζούμας Γεώργιος Πτυχιακή εργασία Τελική παρουσίαση Υπεύθυνος καθηγητής Β. Β. Δημακόπουλος
Παράλληλη επεξεργασία Λύση σε προβλήματα που είναι πολύ μεγάλα για έναν μόνο επεξεργαστή Χωρισμός του προβλήματος σε μικρότερα κομμάτια Ανάθεση του κάθε κομματιού σε έναν επεξεργαστή Σύνθεση του τελικού αποτελέσματος
Μοντέλα παράλληλου προγραμματισμού Μοντέλο κοινής μνήμης Κοινή μνήμη CPU RAM Message Μοντέλο μεταβίβασης μηνυμάτων
Κλασσικός παράλληλος προγραμματισμός Ο προγραμματιστής είναι υπεύθυνος για τον διαχωρισμό του προγράμματος σε διεργασίες / threads και την τοποθέτηση τους στους επεξεργαστές.
OpenMP C/C++ API Είναι ένα σύνολο από –εντολές (directives) –ρουτίνες βιβλιοθήκης –μεταβλητές περιβάλλοντος Προστίθενται στον σειριακό κώδικα και καθοδηγούν τον compiler έτσι ώστε να παραλληλοποιήσει τον κώδικα για εκτέλεση σε περιβάλλον πολυεπεξεργαστών κοινής μνήμης Αυτή τη στιγμή το πρότυπο αυτό βρίσκεται στην έκδοση 2.0 και μπορείτε να το βρείτε ολόκληρο στο
OpenMP directives Τα directives είναι της μορφής #pragma omp directive-name [clause[clause] …] •parallel Work-sharing •for •section •Single Data environment •threadprivate Synchronization •critical •atomic •barrier •ordered •flush Clauses •shared •private •firstprivate •lastprivate •reduction •copyin •ordered •nowait •schedule
Runtime βιβλιοθήκη Παράλληλο περιβάλλον –omp_set_num_threads() –omp_get_num_threads() –omp_get_thread_num() –omp_get_num_procs() –omp_in_parallel() –omp_set_dynamic() –omp_get_dynamic() –omp_set_nested() –omp_get_nested() Κλειδαριές –omp_init_lock() –omp_destroy_lock() –omp_set_lock() –omp_unset_lock() –omp_test_lock() −omp_init_nestlock() −omp_destroy_nestlock() −omp_set_nestlock() −omp_unset_nestlock() −omp_test_nestlock()
Μεταβλητές περιβάλλοντος OMP_SCHEDULE OMP_NUM_THREADS OMP_DYNAMIC OMP_NESTED
Παράδειγμα κώδικα int main() { int i; int A[10]; /* Serial Code... */ #pragma omp parallel shared(A) { #pragma omp for #pragma omp for for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) { A[i] = 0; A[i] = 0; } } /* Serial Code... */ /* Serial Code... */ return 0; }
Πλεονεκτήματα OpenMp Είναι ένα κοινά αποδεκτό πρότυπο. Μεταφερσιμότητα Εύκολη μετάβαση από το σειριακό πρόγραμμα στο παράλληλο Παραλληλοποίηση καθοδηγούμενη από το χρήστη – απλότητα compiler
Άλλες Υλοποιήσεις OdinMP: A Free, Portable OpenMP Implementation for C Omni: OpenMP Compiler Project for C and F77
Η υλοποίησή μας OpenMP C API Εξολοκλήρου σε C GNU Flex / Bison Παράγει κώδικα C με POSIX Threads Πληρεί την έκδοση 1.0 του προτύπου Ήδη υποστηρίζονται κάποιες επεκτάσεις της 2.0
POSIX threads Κάθε νήμα εκτελεί μια συνάρτηση Μόνο οι global μεταβλητές είναι κοινές Περιέχουν συναρτήσεις για συγχρονισμό, ατομικότητα, δημιουργία/καταστροφή νημάτων.
Παράδειγμα μετασχηματισμού κώδικα parallel void main() { int a; #pragma omp parallel shared(a) { a++; a++; }}
Παραγόμενος κώδικας (1/3) Δομή που παράγεται: typedef struct { int (*a); int (*a); } main_parallel_0_vars;
Παραγόμενος κώδικας (2/3) void *main_parallel_0 (void *_omp_thread_data) { int _omp_dummy = _omp_assign_key(_omp_thread_data); int _omp_dummy = _omp_assign_key(_omp_thread_data); int (*a) = &_OMP_VARREF (main_parallel_0, a); int (*a) = &_OMP_VARREF (main_parallel_0, a); { (*(a))++; (*(a))++; } return 0; return 0;}
Παραγόμενος κώδικας (3/3) void main () { int a; int a; /* #pragma omp parallel shared(a) */ { _OMP_PARALLEL_DECL_VARSTRUCT(main_parallel_0); _OMP_PARALLEL_DECL_VARSTRUCT(main_parallel_0); _OMP_PARALLEL_INIT_VAR (main_parallel_0, a); _OMP_PARALLEL_INIT_VAR (main_parallel_0, a); _omp_create_team (_OMP_THREAD, main_parallel_0, _omp_create_team (_OMP_THREAD, main_parallel_0, (void *)&main_parallel_0_var); (void *)&main_parallel_0_var); _omp_destroy_team (_OMP_THREAD->parent); _omp_destroy_team (_OMP_THREAD->parent); }}
Παράδειγμα μετασχηματισμού sections int i; int i; #pragma omp sections lastprivate(i) { #pragma omp section i = 1; i = 1; #pragma omp section i = 2; i = 2; #pragma omp section i = 3; i = 3; }
Παραγόμενος κώδικας int i; int i; /* #pragma omp sections lastprivate(i) */ { int (*_omp_firstlastprivate_i) = &i; int (*_omp_firstlastprivate_i) = &i; int i; int i; { int _omp_section_job; int _omp_section_job; _omp_init_sections (1, _omp_num_section[1]); _omp_init_sections (1, _omp_num_section[1]); while (1) { while (1) { _omp_section_job = _omp_get_next_section (1); _omp_section_job = _omp_get_next_section (1); if (_omp_section_job < 0) break; if (_omp_section_job < 0) break; switch (_omp_section_job) { switch (_omp_section_job) { case 0: case 0: i = 1; break; i = 1; break; case 1: /* section */ case 1: /* section */ i = 2; break; i = 2; break; case 2: /* section */ case 2: /* section */ i = 3; break; i = 3; break; } /* switch */ } /* switch */ if (_omp_section_job == 2) { if (_omp_section_job == 2) { *_omp_firstlastprivate_i = i; *_omp_firstlastprivate_i = i; } } /* while */ } /* while */ } _omp_barrier_wait (&_OMP_THREAD->parent->barrier); _omp_barrier_wait (&_OMP_THREAD->parent->barrier); _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ }
Παράδειγμα μετασχηματισμού for #pragma omp for for (i = 0; i < 100; i += 3) for (i = 0; i < 100; i += 3) printf("%d\n", i); printf("%d\n", i);
Παραγόμενος κώδικας (1/2) int i; int i; /* #pragma omp for */ { int i; int i; int _omp_lb, _omp_ub, _omp_incr; int _omp_lb, _omp_ub, _omp_incr; int _omp_num_job = 0, _omp_last_iter = 0; int _omp_num_job = 0, _omp_last_iter = 0; int _omp_schedule, _omp_chunksize = -1; int _omp_schedule, _omp_chunksize = -1; _omp_schedule = _OMP_DYNAMIC; _omp_schedule = _OMP_DYNAMIC; _omp_chunksize = 1; _omp_chunksize = 1; if (_omp_chunksize < 0) if (_omp_chunksize < 0) _omp_chunksize = _omp_get_default_chunksize (_omp_schedule, 100, 0, 3); _omp_chunksize = _omp_get_default_chunksize (_omp_schedule, 100, 0, 3); _omp_incr = (3); _omp_incr = (3); _omp_init_directive (_OMP_FOR, 0, 0, _omp_incr, 0); _omp_init_directive (_OMP_FOR, 0, 0, _omp_incr, 0); _omp_incr *= _omp_chunksize; _omp_incr *= _omp_chunksize;
Παραγόμενος κώδικας (2/2) while (1) { while (1) { /* get next job */ /* get next job */ _omp_lb = _omp_get_next_lb (_omp_schedule, 0, _omp_incr, &_omp_num_job); _omp_lb = _omp_get_next_lb (_omp_schedule, 0, _omp_incr, &_omp_num_job); /* if none left, break */ /* if none left, break */ if (!(_omp_lb - _omp_incr < 100)) break; if (!(_omp_lb - _omp_incr < 100)) break; _omp_ub = _omp_lb + _omp_incr; _omp_ub = _omp_lb + _omp_incr; if (100 <= _omp_ub) { /* check if out of bounds */ if (100 <= _omp_ub) { /* check if out of bounds */ _omp_ub = 100; _omp_ub = 100; _omp_last_iter = 1; _omp_last_iter = 1; } _omp_push_for_data (0, _omp_lb); _omp_push_for_data (0, _omp_lb); for (i = _omp_lb; i < _omp_ub; i += 3) for (i = _omp_lb; i < _omp_ub; i += 3) printf ("%d\n", i); /* original code */ printf ("%d\n", i); /* original code */ _omp_pop_for_data (); _omp_pop_for_data (); } /* while */ } /* while */ _omp_barrier_wait (&_OMP_THREAD->parent->barrier); _omp_barrier_wait (&_OMP_THREAD->parent->barrier); _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ }
Παράδειγμα μετασχηματισμού single #pragma omp single printf("I am a single thread!\n"); printf("I am a single thread!\n");
Παραγόμενος κώδικας /* #pragma omp single */ { /* the first thread executes the code */ if (_omp_run_single (0)) { if (_omp_run_single (0)) { printf ("I am a single thread!\n"); printf ("I am a single thread!\n"); } _omp_barrier_wait (&_OMP_THREAD->parent->barrier); _omp_barrier_wait (&_OMP_THREAD->parent->barrier); _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ }
Παράδειγμα μετασχηματισμού critical int x, y; int x, y; #pragma omp critical (alpha) x++; x++; #pragma omp critical (beta) y++; y++; #pragma omp critical (alpha) x*=2; x*=2;
Παραγόμενος κώδικας int x, y; int x, y; /* #pragma omp critical (alpha) */ pthread_mutex_lock (&_omp_critical_lock[0]); pthread_mutex_lock (&_omp_critical_lock[0]); _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ x++; x++; _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ pthread_mutex_unlock (&_omp_critical_lock[0]); pthread_mutex_unlock (&_omp_critical_lock[0]); /* #pragma omp critical (beta) */ pthread_mutex_lock (&_omp_critical_lock[1]); pthread_mutex_lock (&_omp_critical_lock[1]); _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ y++; y++; _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ pthread_mutex_unlock (&_omp_critical_lock[1]); pthread_mutex_unlock (&_omp_critical_lock[1]); /* #pragma omp critical (alpha) */ pthread_mutex_lock (&_omp_critical_lock[0]); pthread_mutex_lock (&_omp_critical_lock[0]); _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ x *= 2; x *= 2; _omp_flush_all (); /* implied flush */ _omp_flush_all (); /* implied flush */ pthread_mutex_unlock (&_omp_critical_lock[0]); pthread_mutex_unlock (&_omp_critical_lock[0]);
Επιδόσεις (compilation)
Επιδόσεις (runtime)
Επιδόσεις (speedup)
Επιδόσεις (overhead)
Αναφορές OpenMp official web site OdinMP Omni