Εισαγωγή. Μελετώντας την υποδομή OpenMP χρησιμοποιώντας το παράδειγμα του μεταγλωττιστή GCC

Αυτο 22.06.2019
Επισκόπηση προγράμματος Η έκδοση υπολογιστή του Microsoft Excel Viewer θα επιτρέψει...
  • Chercher
  • Αυτο

Κανγκ Σου Γκάτλιν

Μεταξύ των ειδικών που ασχολούνται με τους παράλληλους υπολογιστές, ένα δημοφιλές αστείο είναι «Ο παράλληλος υπολογισμός είναι η τεχνολογία του μέλλοντος… και θα είναι πάντα έτσι». Αυτό το αστείο δεν έχει χάσει τη σημασία του εδώ και αρκετές δεκαετίες. Παρόμοια συναισθήματα επικρατούσαν στην κοινότητα της αρχιτεκτονικής υπολογιστών, με την ανησυχία ότι τα όρια ταχύτητας ρολογιού επεξεργαστή θα επιτευχθούν σύντομα, αλλά οι συχνότητες των επεξεργαστών συνεχίζουν να αυξάνονται, αν και με πολύ πιο αργό ρυθμό από πριν. Η συγχώνευση της αισιοδοξίας των ειδικών παράλληλων υπολογιστών και η απαισιοδοξία των αρχιτεκτόνων συστημάτων συνέβαλαν στην εμφάνιση των επαναστατικών επεξεργαστές πολλαπλών πυρήνων.

Οι μεγάλοι κατασκευαστές επεξεργαστών έχουν μετατοπίσει την εστίασή τους από την αύξηση συχνότητες ρολογιούνα εφαρμόσει παραλληλισμό στους ίδιους τους επεξεργαστές μέσω της χρήσης πολυπύρηνων αρχιτεκτονικών. Η ιδέα είναι απλή: ενσωματώστε περισσότερους από έναν πυρήνες σε έναν μόνο επεξεργαστή. Ένα σύστημα με επεξεργαστή διπλού πυρήνα είναι ουσιαστικά το ίδιο με έναν υπολογιστή διπλού επεξεργαστή και ένα σύστημα με επεξεργαστή τετραπλού πυρήνα είναι ουσιαστικά το ίδιο με έναν υπολογιστή με τέσσερις επεξεργαστές. Αυτή η προσέγγιση αποφεύγει πολλά από τα τεχνολογικά προβλήματα που σχετίζονται με την αύξηση των ταχυτήτων ρολογιού, ενώ δημιουργεί ισχυρότερους επεξεργαστές.

Όλα αυτά είναι καλά και καλά, αλλά αν η εφαρμογή σας δεν χρησιμοποιεί πολλούς πυρήνες, δεν θα έχει διαφορετική απόδοση. Εδώ μπαίνει στο παιχνίδι η τεχνολογία OpenMP, βοηθώντας τους προγραμματιστές της C++ να δημιουργούν πιο γρήγορα εφαρμογές πολλαπλών νημάτων.

Είναι απλά αδιανόητο να περιγράψουμε λεπτομερώς το OpenMP σε ένα άρθρο, καθώς είναι ένα πολύ ογκώδες και ισχυρό API. Σκεφτείτε αυτό το άρθρο ως μια εισαγωγή που δείχνει πώς να χρησιμοποιείτε διάφορα εργαλεία OpenMP για τη γρήγορη σύνταξη προγραμμάτων πολλαπλών νημάτων. Εάν χρειάζεστε πρόσθετες πληροφορίεςσχετικά με αυτό το θέμα, συνιστούμε να συμβουλευτείτε τις προδιαγραφές που είναι διαθέσιμες στον ιστότοπο του OpenMP (www.openmp.org) - είναι εκπληκτικά εύκολο να το διαβάσετε.

Ενεργοποίηση OpenMP σε Visual C++

Το πρότυπο OpenMP αναπτύχθηκε το 1997 ως API που στοχεύει στη σύνταξη φορητών εφαρμογών πολλαπλών νημάτων. Πρώτα βασίστηκε σε Γλώσσα Fortran, αλλά αργότερα συμπεριλήφθηκε C/C++. Τελευταία έκδοση OpenMP - 2.0; υποστηρίζεται πλήρως από το Visual C++ 2005. Το πρότυπο OpenMP υποστηρίζεται επίσης από την πλατφόρμα Xbox 360.

Προτού κωδικοποιήσετε, θα πρέπει να γνωρίζετε πώς να ενεργοποιήσετε τις δυνατότητες OpenMP του μεταγλωττιστή. Για να το κάνετε αυτό, χρησιμοποιήστε την επιλογή μεταγλωττιστή /openmp που εισήχθη στο Visual C++ 2005. (Μπορείτε να ενεργοποιήσετε τις οδηγίες OpenMP στις σελίδες ιδιοτήτων έργου επιλέγοντας Ιδιότητες διαμόρφωσης, C/C++, Γλώσσα και αλλάζοντας την τιμή της ιδιότητας OpenMP Support.) Όταν εμφανιστεί η επιλογή /openmp, ο μεταγλωττιστής ορίζει το σύμβολο _OPENMP, το οποίο μπορεί να είναι χρησιμοποιείται για να προσδιορίσει εάν οι εγκαταστάσεις OpenMP είναι ενεργοποιημένες. Για να το κάνετε αυτό, απλώς γράψτε #ifndef _OPENMP.

Το OpenMP επικοινωνεί με εφαρμογές μέσω της βιβλιοθήκης εισαγωγής vcomp.lib. Η αντίστοιχη βιβλιοθήκη χρόνου εκτέλεσης ονομάζεται vcomp.dll. Υποστηρίζουν εκδόσεις εντοπισμού σφαλμάτων των βιβλιοθηκών εισαγωγής και χρόνου εκτέλεσης (vcompd.lib και vcompd.dll, αντίστοιχα) επιπλέον μηνύματασχετικά με σφάλματα που δημιουργούνται από ορισμένες παράνομες λειτουργίες. Λάβετε υπόψη ότι το Visual C++ δεν υποστηρίζει στατική σύνδεση με τη βιβλιοθήκη OpenMP χρόνου εκτέλεσης, αν και η έκδοση Xbox 360 υποστηρίζει.

Παράλληλη επεξεργασία στο OpenMP

Μια εφαρμογή OpenMP ξεκινά με ένα μόνο νήμα - το κύριο. Μια εφαρμογή μπορεί να περιέχει παράλληλες περιοχές, στις οποίες το κύριο νήμα δημιουργεί ομάδες νημάτων (συμπεριλαμβανομένου του κύριου νήματος). Στο τέλος της παράλληλης περιοχής, οι ομάδες των νημάτων σταματούν και η εκτέλεση του κύριου νήματος συνεχίζεται. Μια παράλληλη περιοχή μπορεί να είναι ένθετη με άλλες παράλληλες περιοχές, στις οποίες κάθε νήμα της αρχικής περιοχής γίνεται το κύριο νήμα για την ομάδα νημάτων της. Οι ένθετες περιοχές μπορούν με τη σειρά τους να περιλαμβάνουν περιοχές σε βαθύτερο επίπεδο φωλιάς.

Η παράλληλη επεξεργασία στο OpenMP απεικονίζεται στο Σχ. 1. Το αριστερό βέλος αντιπροσωπεύει το κύριο νήμα, το οποίο τρέχει μόνο του μέχρι να φτάσει στην πρώτη παράλληλη περιοχή στο σημείο 1. Σε αυτό το σημείο, το κύριο νήμα δημιουργεί μια ομάδα νημάτων και τώρα εκτελούνται όλα ταυτόχρονα στην παράλληλη περιοχή.

Στο σημείο 2, τρία από αυτά τα τέσσερα νήματα, έχοντας φτάσει στην ένθετη παράλληλη περιοχή, δημιουργούν νέες ομάδες νημάτων. Ο αρχικός κύριος και τα νήματα που δημιούργησαν νέες ομάδες γίνονται οι κάτοχοι των ομάδων τους (ο κύριος αυτών των ομάδων). Λάβετε υπόψη ότι τα νήματα μπορούν να δημιουργήσουν νέες ομάδες διαφορετικές στιγμέςή να μην συναντήσετε καθόλου μια ένθετη παράλληλη περιοχή.

Στο σημείο 3 τελειώνει η ένθετη παράλληλη περιοχή. Κάθε νήμα σε μια ένθετη παράλληλη περιοχή συγχρονίζει την κατάστασή του με άλλα νήματα σε αυτήν την περιοχή, αλλά ο συγχρονισμός διαφορετικές περιοχέςδεν συνεργάζονται μεταξύ τους. Στο σημείο 4 τελειώνει η πρώτη παράλληλη περιοχή και στο σημείο 5 ξεκινά μια νέα. Τα τοπικά δεδομένα κάθε νήματος στα διαστήματα μεταξύ παράλληλων περιοχών διατηρούνται.

Αυτά είναι τα βασικά του μοντέλου εκτέλεσης στο OpenMP. Τώρα είστε έτοιμοι να μάθετε πού να ξεκινήσετε την ανάπτυξη μιας παράλληλης εφαρμογής.

Κατασκευές OpenMP

Το OpenMP είναι εύκολο στη χρήση και περιλαμβάνει μόνο δύο βασικούς τύπους δομών: τις οδηγίες pragma και τις λειτουργίες χρόνου εκτέλεσης OpenMP. Οι οδηγίες Pragma συνήθως λένε στον μεταγλωττιστή να εφαρμόσει παράλληλη εκτέλεση μπλοκ κώδικα. Όλες αυτές οι οδηγίες ξεκινούν με το #pragma omp. Όπως οποιεσδήποτε άλλες οδηγίες pragma, αγνοούνται από έναν μεταγλωττιστή που δεν υποστηρίζει μια συγκεκριμένη τεχνολογία - σε σε αυτή την περίπτωση OpenMP.

Οι συναρτήσεις OpenMP χρησιμοποιούνται κυρίως για την αλλαγή και την ανάκτηση παραμέτρων περιβάλλοντος. Επιπλέον, το OpenMP περιλαμβάνει λειτουργίες API για την υποστήριξη ορισμένων τύπων συγχρονισμού. Για να χρησιμοποιήσετε αυτές τις λειτουργίες βιβλιοθήκης χρόνου εκτέλεσης OpenMP (χρόνος εκτέλεσης), πρέπει να συμπεριλάβετε το αρχείο κεφαλίδας omp.h στο πρόγραμμά σας. Εάν χρησιμοποιείτε μόνο οδηγίες OpenMP pragma στην εφαρμογή σας, δεν χρειάζεται να συμπεριλάβετε αυτό το αρχείο.

Για να υλοποιήσετε την παράλληλη εκτέλεση μπλοκ εφαρμογών, χρειάζεται απλώς να προσθέσετε οδηγίες pragma στον κώδικα και, εάν είναι απαραίτητο, να χρησιμοποιήσετε τις λειτουργίες χρόνου εκτέλεσης της βιβλιοθήκης OpenMP. Οι οδηγίες Pragma έχουν την ακόλουθη μορφή:

#pragma omp<директива>[τμήμα [[,] ενότητα]...]

Το OpenMP υποστηρίζει οδηγίες παράλληλες, για, παράλληλες για, τομή, ενότητες, μεμονωμένες, κύριες, κρίσιμες, flush, διατεταγμένες και ατομικές οδηγίες, οι οποίες ορίζουν είτε μηχανισμούς κοινής χρήσης εργασίας είτε δομές συγχρονισμού. Σε αυτό το άρθρο θα συζητήσουμε τις περισσότερες οδηγίες.

Μια ρήτρα είναι ένας προαιρετικός τροποποιητής μιας οδηγίας που επηρεάζει τη συμπεριφορά της. Οι λίστες των ενοτήτων που υποστηρίζονται από κάθε οδηγία ποικίλλουν και πέντε οδηγίες (κύρια, κρίσιμη, flush, διατεταγμένη και ατομική) δεν υποστηρίζουν καθόλου ενότητες.

Υλοποίηση παράλληλης επεξεργασίας

Αν και υπάρχουν πολλές οδηγίες OpenMP, δεν θα τις χρειαστούμε όλες ταυτόχρονα. Η πιο σημαντική και κοινή οδηγία είναι η παράλληλη. Δημιουργεί μια παράλληλη περιοχή για το ακόλουθο δομημένο μπλοκ, για παράδειγμα:

#pragma omp parallel [section[ [,] section]...] δομημένο μπλοκ

Αυτή η οδηγία λέει στον μεταγλωττιστή ότι ένα δομημένο μπλοκ κώδικα πρέπει να εκτελείται παράλληλα, σε πολλαπλά νήματα. Κάθε νήμα θα εκτελεί την ίδια ροή εντολών, αλλά όχι το ίδιο σύνολο εντολών - όλα εξαρτώνται από δηλώσεις που ελέγχουν τη λογική του προγράμματος, όπως το if-else.

Ως παράδειγμα, εξετάστε το κλασικό πρόγραμμα "Hello World":

#pragma omp parallel ( printf("Hello World\n"); )

Σε ένα σύστημα διπλού επεξεργαστή θα περιμένατε φυσικά να λάβετε τα ακόλουθα:

Γεια Κόσμε Γεια Κόσμε

Ωστόσο, το αποτέλεσμα θα μπορούσε να είναι το εξής:

HellHell oo WorWlodrl d

Η δεύτερη επιλογή είναι δυνατή επειδή δύο νήματα που τρέχουν παράλληλα μπορούν να προσπαθήσουν να εκτυπώσουν μια γραμμή ταυτόχρονα. Όταν δύο ή περισσότερα νήματα προσπαθούν ταυτόχρονα να διαβάσουν ή να τροποποιήσουν έναν κοινόχρηστο πόρο (στην περίπτωσή μας, ένα παράθυρο κονσόλας), εμφανίζεται μια συνθήκη αγώνα. Πρόκειται για μη ντετερμινιστικά σφάλματα στον κώδικα του προγράμματος, τα οποία είναι εξαιρετικά δύσκολο να βρεθούν. Ο προγραμματιστής είναι υπεύθυνος για την πρόληψη των αγώνων. Κατά κανόνα, αυτό γίνεται χρησιμοποιώντας κλειδαριές ή ελαχιστοποιώντας την πρόσβαση σε κοινόχρηστους πόρους.

Ας δούμε ένα πιο σοβαρό παράδειγμα που καθορίζει τον μέσο όρο δύο παρακείμενων στοιχείων πίνακα και γράφει τα αποτελέσματα σε έναν άλλο πίνακα. Αυτό το παράδειγμα χρησιμοποιεί τη νέα κατασκευή OpenMP #pragma omp for, η οποία σχετίζεται με οδηγίες κοινής χρήσης εργασίας. Τέτοιες οδηγίες χρησιμοποιούνται όχι για παράλληλη εκτέλεση κώδικα, αλλά για τη λογική κατανομή μιας ομάδας νημάτων προκειμένου να υλοποιηθούν οι καθορισμένες λογικές κατασκευές ελέγχου. Η οδηγία #pragma omp for αναφέρει ότι κατά την εκτέλεση ενός βρόχου for σε μια παράλληλη περιοχή, οι επαναλήψεις βρόχου θα πρέπει να κατανέμονται μεταξύ των νημάτων της ομάδας:

#pragma omp parallel ( #pragma omp for for(int i = 1; i< size; ++i) x[i] = (y + y)/2; }

Εάν αυτός ο κώδικας εκτελούνταν σε υπολογιστή με τέσσερις επεξεργαστές και το μέγεθος είχε τιμή 100, οι επαναλήψεις 1-25 θα μπορούσαν να αντιστοιχιστούν στον πρώτο επεξεργαστή, 26-50 στον δεύτερο, 51-75 στον τρίτο και 76-99 στον το τέταρτο. Αυτό είναι χαρακτηριστικό για μια πολιτική προγραμματισμού που ονομάζεται στατική. Θα συζητήσουμε τις πολιτικές προγραμματισμού αργότερα.

Πρέπει να σημειωθεί ότι στο τέλος της παράλληλης περιοχής γίνεται συγχρονισμός φραγμού. Με άλλα λόγια, όταν φτάσετε στο τέλος της περιοχής, όλα τα νήματα μπλοκάρονται έως ότου το τελευταίο νήμα ολοκληρώσει τη δουλειά του.

Εάν η οδηγία #pragma omp for αφαιρεθεί από το παράδειγμα που μόλις δόθηκε, κάθε νήμα θα εκτελεστεί πλήρης κύκλοςγιατί, έχοντας κάνει πολλά επιπλέον εργασία:

#pragma omp parallel ( for(int i = 1; i< size; ++i) x[i] = (y + y)/2; }

Επειδή οι βρόχοι είναι η πιο κοινή κατασκευή όπου μπορεί να παραλληλιστεί η εκτέλεση κώδικα, το OpenMP υποστηρίζει έναν συνοπτικό τρόπο γραφής ενός συνδυασμού του #pragma omp parallel και του #pragma omp για οδηγίες:

#pragma omp παράλληλη για for(int i = 1; i< size; ++i) x[i] = (y + y)/2;

Λάβετε υπόψη ότι αυτός ο βρόχος δεν έχει εξαρτήσεις, δηλαδή, μια επανάληψη του βρόχου δεν εξαρτάται από τα αποτελέσματα άλλων επαναλήψεων. Αλλά στους επόμενους δύο κύκλους υπάρχουν δύο τύποι εξάρτησης:

Για(int i = 1; i<= n; ++i) // цикл 1 a[i] = a + b[i]; for(int i = 0; i < n; ++i) // цикл 2 x[i] = x + b[i];

Ο παραλληλισμός του βρόχου 1 είναι προβληματικός γιατί για να εκτελέσετε την επανάληψη i πρέπει να γνωρίζετε το αποτέλεσμα της επανάληψης i-1, δηλαδή η επανάληψη i εξαρτάται από την επανάληψη i-1. Η παραλληλοποίηση του βρόχου 2 είναι επίσης προβληματική, αλλά για διαφορετικό λόγο. Σε αυτόν τον βρόχο, μπορείτε να αξιολογήσετε την τιμή του x[i] στο x, ωστόσο, μόλις το κάνετε αυτό, δεν θα μπορείτε πλέον να αξιολογήσετε την τιμή του x. Παρατηρείται η εξάρτηση της επανάληψης i-1 από την επανάληψη i.

Κατά την παραλληλοποίηση βρόχων, πρέπει να βεβαιωθείτε ότι οι επαναλήψεις βρόχων δεν έχουν εξαρτήσεις. Εάν ο βρόχος δεν περιέχει εξαρτήσεις, ο μεταγλωττιστής μπορεί να εκτελέσει τον βρόχο με οποιαδήποτε σειρά, ακόμη και παράλληλα. Ο μεταγλωττιστής δεν ελέγχει εάν πληρούται αυτή η σημαντική απαίτηση - πρέπει να φροντίσετε μόνοι σας. Εάν πείτε στον μεταγλωττιστή να παραλληλίσει έναν βρόχο που περιέχει εξαρτήσεις, ο μεταγλωττιστής θα συμμορφωθεί, με αποτέλεσμα ένα σφάλμα.

Επιπλέον, το OpenMP θέτει περιορισμούς στους βρόχους for που μπορούν να συμπεριληφθούν σε ένα #pragma omp για μπλοκ ή #pragma omp παράλληλα για μπλοκ. Για τους βρόχους πρέπει να ακολουθούν την ακόλουθη μορφή:

For([ακέραιος τύπος] i = αμετάβλητος βρόχος; i (<,>,=,<=,>=) αμετάβλητος βρόχος.

i (+,-)= αμετάβλητος βρόχος)

Αυτές οι απαιτήσεις εισάγονται έτσι ώστε το OpenMP να μπορεί να καθορίσει τον αριθμό των επαναλήψεων κατά την εισαγωγή ενός βρόχου.

Σύγκριση υποστήριξης νημάτων σε OpenMP και Win32

Πιστεύουμε ότι θα είναι χρήσιμο να συγκρίνουμε το παράδειγμα που μόλις δώσαμε, το οποίο περιλαμβάνει το #pragma omp parallel για την οδηγία, με τον κώδικα που θα έπρεπε να γράψουμε για να λύσουμε το ίδιο πρόβλημα χρησιμοποιώντας το API των Windows. Όπως μπορείτε να δείτε στη Λίστα 1, χρειάζεται πολύ περισσότερος κώδικας για να επιτευχθεί το ίδιο αποτέλεσμα και στα παρασκήνια, αυτή η επιλογή κάνει πολύ περισσότερη δουλειά. Έτσι, ο κατασκευαστής της κλάσης ThreadData καθορίζει ποιες θα πρέπει να είναι οι τιμές έναρξης και λήξης κάθε φορά που καλείται το νήμα. Το OpenMP χειρίζεται μόνο του όλες αυτές τις λεπτομέρειες και παρέχει στον προγραμματιστή πρόσθετα μέσα για τη διαμόρφωση παράλληλων περιοχών και κώδικα.

Κλάση ThreadData ( public: // Ο κατασκευαστής αρχικοποιεί τα πεδία έναρξης και διακοπής ThreadData(int threadNum); int start; int stop; ); DWORD ThreadFn(void* passedInData) ( ThreadData *threadData = (ThreadData *)passedInData; for(int i = threadData->start; i< threadData->στάση; ++i) x[i] = (y + y) / 2;< nTeams; ++i) ResumeThread(hTeams[i]); // Для каждого потока здесь неявно вызывается // метод ThreadFn // Ожидание завершения работы WaitForMultipleObjects(nTeams, hTeams, TRUE, INFINITE); } int main(int argc, char* argv) { // Создание групп потоков for(int i=0; i < nTeams; ++i) { ThreadData *threadData = new ThreadData(i); hTeams[i] = CreateThread(NULL, 0, ThreadFn, threadData, CREATE_SUSPENDED, NULL); } ParallelFor(); // имитация OpenMP-конструкции parallel for // Очистка for(int i=0; i < nTeams; ++i) CloseHandle(hTeams[i]); }

επιστροφή 0; ) void ParallelFor() ( // Έναρξη ομάδων νημάτων για(int i=0; i

Γενικά και ιδιωτικά δεδομένα

Κατά την ανάπτυξη παράλληλων προγραμμάτων, πρέπει να κατανοήσετε ποια δεδομένα μοιράζονται και ποια είναι ιδιωτικά - όχι μόνο η απόδοση, αλλά και η σωστή λειτουργία του προγράμματος εξαρτάται από αυτό. Στο OpenMP αυτή η διαφορά είναι προφανής και μπορείτε να τη ρυθμίσετε χειροκίνητα.

Οι κοινόχρηστες μεταβλητές είναι προσβάσιμες σε όλα τα νήματα μιας ομάδας, επομένως οι αλλαγές σε αυτές τις μεταβλητές σε ένα νήμα είναι ορατές σε άλλα νήματα στην παράλληλη περιοχή. Όσον αφορά τις ιδιωτικές μεταβλητές, κάθε νήμα σε μια ομάδα έχει ξεχωριστές παρουσίες τους, επομένως οι αλλαγές σε τέτοιες μεταβλητές σε ένα νήμα δεν επηρεάζουν τις εμφανίσεις τους που ανήκουν σε άλλα νήματα.

Από προεπιλογή, όλες οι μεταβλητές σε μια παράλληλη περιοχή είναι κοινόχρηστες, αλλά υπάρχουν τρεις εξαιρέσεις σε αυτόν τον κανόνα. Πρώτον, οι δείκτες των παράλληλων για βρόχους είναι ιδιωτικοί. Για παράδειγμα, αυτό ισχύει για τη μεταβλητή i στον κώδικα που εμφανίζεται στη Λίστα 2. Η μεταβλητή j δεν είναι ιδιωτική από προεπιλογή, αλλά γίνεται ρητά μέσω του πρώτου ιδιωτικού όρου.

Καταχώριση 2. Ενότητες οδηγιών OpenMP και ένθετες για βρόχο< count; ++i) { int doubleI = 2 * i; for(; j < doubleI; ++j) { sum += myMatrix.GetElement(i, j); } } }

Άθροισμα float = 10,0f; MatrixClass myMatrix; int j = myMatrix.RowStart(); int i? #pragma omp parallel ( #pragma omp for firstprivate(j) lastprivate(i) μείωση(+: sum) for(i = 0; i

Δεύτερον, οι τοπικές μεταβλητές μπλοκ παράλληλων περιοχών είναι ιδιωτικές. Στο Σχ. 3 η μεταβλητή doubleI είναι τέτοια γιατί δηλώνεται στην παράλληλη περιοχή. Τυχόν μη στατικές μεταβλητές που δεν είναι MatrixClass που δηλώνονται στη μέθοδο myMatrix::GetElement θα είναι ιδιωτικές.

Κάθε μία από τις τέσσερις ενότητες που ονομάζονται δέχεται μια λίστα μεταβλητών, αλλά η σημασιολογία αυτών των ενοτήτων διαφέρει. Η ιδιωτική ενότητα λέει ότι κάθε νήμα πρέπει να δημιουργεί ένα ιδιωτικό αντίγραφο κάθε μεταβλητής στη λίστα. Τα ιδιωτικά αντίγραφα θα αρχικοποιηθούν με μια προεπιλεγμένη τιμή (χρησιμοποιώντας τον προεπιλεγμένο κατασκευαστή εάν χρειάζεται). Για παράδειγμα, μεταβλητές τύπου int έχουν προεπιλεγμένη τιμή 0.

Ο πρώτος ιδιωτικός όρος έχει την ίδια σημασιολογία, αλλά πριν από την εκτέλεση της παράλληλης περιοχής, καθορίζει ότι η τιμή της ιδιωτικής μεταβλητής θα αντιγραφεί σε κάθε νήμα, χρησιμοποιώντας έναν κατασκευαστή αντιγραφής εάν χρειάζεται.

Η σημασιολογία της τελευταίας ιδιωτικής ενότητας συμπίπτει επίσης με τη σημασιολογία της ιδιωτικής ενότητας, αλλά όταν εκτελείται η τελευταία επανάληψη του βρόχου ή του τμήματος της κατασκευής παραλληλοποίησης, οι τιμές των μεταβλητών που καθορίζονται στην τελευταία ιδιωτική ενότητα εκχωρούνται στις μεταβλητές του κύριου νήματος. Όταν χρειάζεται, ο τελεστής εκχώρησης αντιγραφής χρησιμοποιείται για την αντιγραφή αντικειμένων.

Η ενότητα μείωσης έχει παρόμοια σημασιολογία, αλλά δέχεται μια μεταβλητή και έναν τελεστή. Οι τελεστές που υποστηρίζονται από αυτήν την ενότητα παρατίθενται στον Πίνακα. 1, και η μεταβλητή πρέπει να είναι βαθμωτός τύπος (όπως float, int ή long, αλλά όχι std::vector, int, κ.λπ.). Η μεταβλητή του τμήματος μείωσης αρχικοποιείται σε κάθε νήμα με την τιμή που καθορίζεται στον πίνακα. Στο τέλος του μπλοκ κώδικα, η δήλωση ρήτρας μείωσης εφαρμόζεται σε κάθε ιδιωτικό αντίγραφο της μεταβλητής καθώς και στην αρχική τιμή της μεταβλητής.

Τραπέζι 1. Χειριστές του τμήματος μείωσης

Στην Λίστα 2, το άθροισμα αρχικοποιείται σιωπηρά σε κάθε νήμα σε 0,0f (σημειώστε ότι ο πίνακας εμφανίζει μια κανονική τιμή 0, αλλά σε αυτήν την περίπτωση έχει τη μορφή 0,0f επειδή το άθροισμα είναι float). Αφού εκτελεστεί το #pragma omp for block, η λειτουργία + εκτελείται σε όλες τις μερικές τιμές και στην αρχική τιμή αθροίσματος (που στην περίπτωσή μας είναι 10,0f). Το αποτέλεσμα εκχωρείται στο αρχικό κοινό άθροισμα μεταβλητών.

Παράλληλη επεξεργασία σε κατασκευές εκτός από βρόχους

Συνήθως, το OpenMP χρησιμοποιείται για την παραλληλοποίηση βρόχων, αλλά το OpenMP υποστηρίζει επίσης παραλληλισμό σε επίπεδο συνάρτησης. Αυτός ο μηχανισμός ονομάζεται τμήματα OpenMP. Είναι αρκετά απλό και συχνά χρήσιμο.

Ας δούμε έναν από τους πιο σημαντικούς αλγόριθμους στον προγραμματισμό - τη γρήγορη ταξινόμηση. Για παράδειγμα, εφαρμόσαμε μια αναδρομική μέθοδο για γρήγορη ταξινόμηση μιας λίστας ακεραίων. Για λόγους απλότητας, αποφασίσαμε να μην δημιουργήσουμε μια καθολική έκδοση προτύπου της μεθόδου, αλλά η ουσία του θέματος δεν αλλάζει καθόλου. Ο κώδικας για τη μέθοδό μας, που υλοποιείται χρησιμοποιώντας ενότητες OpenMP, εμφανίζεται στη Λίστα 3 (ο κώδικας για τη μέθοδο Partition παραλείπεται για να αποφευχθεί η ακαταστασία της συνολικής εικόνας).

Λίστα 3. Γρήγορη ταξινόμηση με χρήση παράλληλων κατατμήσεων

Void QuickSort (int numList, int nLower, int nUpper) ( if (nLower< nUpper) { // Разбиение интервала сортировки int nSplit = Partition (numList, nLower, nUpper); #pragma omp parallel sections { #pragma omp section QuickSort (numList, nLower, nSplit - 1); #pragma omp section QuickSort (numList, nSplit + 1, nUpper); } } }

ΣΕ σε αυτό το παράδειγμαΗ πρώτη οδηγία #pragma δημιουργεί μια περιοχή παράλληλης τομής. Κάθε ενότητα ορίζεται από την οδηγία ενότητας #pragma omp. Σε κάθε τμήμα σε μια παράλληλη περιοχή εκχωρείται ένα νήμα από μια ομάδα νημάτων και όλα τα τμήματα εκτελούνται ταυτόχρονα. Κάθε ενότητα καλεί τη μέθοδο QuickSort αναδρομικά.

Όπως και με το #pragma omp parallel για την κατασκευή, πρέπει να βεβαιωθείτε ότι τα τμήματα είναι ανεξάρτητα το ένα από το άλλο, ώστε να μπορούν να εκτελεστούν παράλληλα. Εάν αλλάξουν οι ενότητες κοινόχρηστους πόρουςΧωρίς συγχρονισμό της πρόσβασης σε αυτά, το αποτέλεσμα μπορεί να είναι απρόβλεπτο.

Σημειώστε ότι αυτό το παράδειγμα χρησιμοποιεί τη συντομογραφία #pragma omp παράλληλες ενότητες, η οποία είναι παρόμοια με την παράλληλη #pragma omp για κατασκευή. Κατ' αναλογία με το #pragma omp for, η οδηγία #pragma omp τμήματα μπορεί να χρησιμοποιηθεί ξεχωριστά σε μια παράλληλη περιοχή.

Υπάρχουν μερικά ακόμη πράγματα που μπορείτε να πείτε σχετικά με τον κωδικό που εμφανίζεται στη Λίστα 3. Αρχικά, παρατηρήστε ότι οι παράλληλες ενότητες καλούνται αναδρομικά. Οι αναδρομικές κλήσεις υποστηρίζονται από παράλληλες περιοχές και (όπως στο παράδειγμά μας) παράλληλες ενότητες. Εάν η ένθεση είναι ενεργοποιημένη, θα δημιουργούνται όλο και περισσότερα νήματα, καθώς το QuickSort καλείται αναδρομικά. Αυτό μπορεί να μην είναι αυτό που θέλει ο προγραμματιστής, καθώς αυτή η προσέγγιση θα μπορούσε να οδηγήσει στη δημιουργία μεγάλο αριθμόρέματα. Για να περιορίσετε τον αριθμό των νημάτων, μπορείτε να απενεργοποιήσετε την ένθεση στο πρόγραμμά σας. Στη συνέχεια, η εφαρμογή μας θα καλέσει τη μέθοδο QuickSort αναδρομικά χρησιμοποιώντας μόνο δύο νήματα.

Η μεταγλώττιση αυτής της εφαρμογής χωρίς την επιλογή /openmp θα δημιουργήσει τη σωστή σειριακή έκδοση. Ένα από τα πλεονεκτήματα του OpenMP είναι ότι είναι συμβατό με μεταγλωττιστές που δεν υποστηρίζουν OpenMP.

Οδηγίες Pragma για συγχρονισμό

Στο ταυτόχρονη εκτέλεσηΠολλά νήματα χρειάζεται συχνά να συγχρονιστούν. Το OpenMP υποστηρίζει διάφορους τύπους συγχρονισμού που βοηθούν σε πολλές περιπτώσεις.

Ένας τύπος είναι ο σιωπηρός συγχρονισμός φραγμού, ο οποίος συμβαίνει στο τέλος κάθε παράλληλης περιοχής για όλα τα νήματα που σχετίζονται με αυτό. Ο μηχανισμός συγχρονισμού φραγμού είναι τέτοιος ώστε έως ότου όλα τα νήματα φτάσουν στο τέλος της παράλληλης περιοχής, κανένα νήμα δεν μπορεί να διασχίσει τα σύνορά του.

Ο σιωπηρός συγχρονισμός φραγμού εκτελείται επίσης στο τέλος κάθε μπλοκ τμημάτων #pragma omp for, #pragma omp single και #pragma omp. Για να απενεργοποιήσετε τον σιωπηρό συγχρονισμό φραγμού σε οποιοδήποτε από αυτά τα τρία μπλοκ κοινής χρήσης εργασίας, καθορίστε την ενότητα nowait:

#pragma omp parallel ( #pragma omp for nowait for(int i = 1; i< size; ++i) x[i] = (y + y)/2; }

Όπως μπορείτε να δείτε, αυτή η ενότητα της οδηγίας παραλληλοποίησης λέει ότι δεν υπάρχει ανάγκη συγχρονισμού νημάτων στο τέλος του βρόχου for, αν και θα εξακολουθούν να συγχρονίζονται στο τέλος της παράλληλης περιοχής.

Ο δεύτερος τύπος είναι ο ρητός συγχρονισμός φραγμού. Σε ορισμένες περιπτώσεις είναι σκόπιμο να το εκτελέσετε μαζί με το σιωπηρό. Για να το κάνετε αυτό, συμπεριλάβετε στον κώδικά σας την οδηγία #pragma omp barrier.

Τα κρίσιμα τμήματα μπορούν να χρησιμοποιηθούν ως εμπόδια. Στο Win32 API, οι συναρτήσεις EnterCriticalSection και LeaveCriticalSection χρησιμοποιούνται για την είσοδο και την έξοδο από μια κρίσιμη ενότητα. Στο OpenMP, η οδηγία #pragma omp κρίσιμη [όνομα] χρησιμοποιείται για αυτό. Έχει την ίδια σημασιολογία με την κρίσιμη ενότητα Win32 και βασίζεται στο EnterCriticalSection. Μπορείτε να χρησιμοποιήσετε μια κρίσιμη ενότητα με όνομα και, στη συνέχεια, η πρόσβαση σε ένα μπλοκ κώδικα είναι αμοιβαία αποκλειστική μόνο για άλλες κρίσιμες ενότητες με το ίδιο όνομα (αυτό ισχύει για ολόκληρη τη διαδικασία). Εάν δεν καθορίζεται ένα όνομα, η οδηγία αντιστοιχίζεται σε κάποιο όνομα που επιλέγεται από το σύστημα. Η πρόσβαση σε όλες τις κρίσιμες ενότητες που δεν κατονομάζονται είναι αμοιβαία αποκλειστική.

Σε παράλληλες περιοχές, υπάρχουν συχνά μπλοκ κώδικα στα οποία θέλετε να έχει πρόσβαση μόνο ένα νήμα, όπως μπλοκ κώδικα που είναι υπεύθυνοι για την εγγραφή δεδομένων σε ένα αρχείο. Σε πολλές από αυτές τις περιπτώσεις, δεν έχει σημασία ποιο νήμα εκτελεί τον κώδικα, μόνο ότι είναι το μόνο νήμα. Για να γίνει αυτό, το OpenMP χρησιμοποιεί την ενιαία οδηγία #pragma omp.

Μερικές φορές οι δυνατότητες της ενιαίας οδηγίας δεν επαρκούν. Σε ορισμένες περιπτώσεις, θέλετε ένα μπλοκ κώδικα να εκτελείται από το κύριο νήμα - για παράδειγμα, εάν αυτό το νήμα είναι υπεύθυνο για την επεξεργασία του GUI και το χρειάζεστε για να εκτελέσετε κάποια εργασία. Στη συνέχεια εφαρμόζεται η κύρια οδηγία #pragma omp. Σε αντίθεση με την ενιαία οδηγία, δεν υπάρχει σιωπηρό εμπόδιο κατά την είσοδο ή την έξοδο από το κύριο μπλοκ.

Για να ολοκληρώσετε όλες τις λειτουργίες μνήμης που εκκρεμούν πριν ξεκινήσετε την επόμενη λειτουργία, χρησιμοποιήστε την οδηγία #pragma omp flush, η οποία είναι ισοδύναμη με τη λειτουργία εσωτερικού μεταγλωττιστή _ReadWriteBarrier.

Σημειώστε ότι οι οδηγίες OpenMP pragma πρέπει να υποβάλλονται σε επεξεργασία από όλα τα νήματα της ομάδας με την ίδια σειρά (ή να μην υποβάλλονται σε επεξεργασία από κανένα νήμα). Επομένως, το ακόλουθο παράδειγμα κώδικα είναι λανθασμένο και τα αποτελέσματα της εκτέλεσής του δεν μπορούν να προβλεφθούν (πιθανές επιλογές είναι η κατάρρευση ή το πάγωμα του συστήματος):

#pragma omp parallel ( if(omp_get_thread_num() > 3) ( #pragma omp single // ο κώδικας δεν είναι διαθέσιμος σε όλα τα νήματα x++; ) )

Ρουτίνες χρόνου εκτέλεσης OpenMP

Εκτός από τις οδηγίες που έχουν ήδη περιγραφεί, το OpenMP υποστηρίζει μια σειρά από χρήσιμες ρουτίνες. Αυτές εμπίπτουν σε τρεις μεγάλες κατηγορίες: χρόνο εκτέλεσης, κλείδωμα/συγχρονισμό και λειτουργίες χρονοδιακόπτη (οι τελευταίες εκ των οποίων δεν καλύπτονται σε αυτό το άρθρο). Όλες αυτές οι συναρτήσεις έχουν ονόματα που ξεκινούν με omp_ και ορίζονται στο αρχείο κεφαλίδας omp.h.

Οι υπορουτίνες της πρώτης κατηγορίας σας επιτρέπουν να κάνετε ερωτήσεις και να ορίσετε διάφορες παραμέτρους λειτουργικό περιβάλλον OpenMP. Οι συναρτήσεις των οποίων τα ονόματα αρχίζουν με omp_set_ μπορούν να κληθούν μόνο εκτός παράλληλων περιοχών. Όλες οι άλλες λειτουργίες μπορούν να χρησιμοποιηθούν τόσο εντός παράλληλων περιοχών όσο και εκτός αυτών.

Για να μάθετε ή να ορίσετε τον αριθμό των νημάτων σε μια ομάδα, χρησιμοποιήστε τις συναρτήσεις omp_get_num_threads και omp_set_num_threads. Το πρώτο επιστρέφει τον αριθμό των νημάτων στην τρέχουσα ομάδα νημάτων. Εάν το νήμα που καλεί δεν εκτελείται σε παράλληλη περιοχή, αυτή η συνάρτηση επιστρέφει 1. Η μέθοδος omp_set_num_thread ορίζει τον αριθμό των νημάτων που θα εκτελεστούν στην επόμενη παράλληλη περιοχή που συναντά το τρέχον νήμα. Επιπλέον, ο αριθμός των νημάτων που χρησιμοποιούνται για την εκτέλεση παράλληλων περιοχών εξαρτάται από δύο άλλες παραμέτρους του περιβάλλοντος OpenMP: υποστήριξη για δυναμική δημιουργία νημάτων και ένθεση περιοχής.

Η υποστήριξη για τη δημιουργία δυναμικών νημάτων καθορίζεται από την τιμή της ιδιότητας boolean, η οποία ορίζεται από προεπιλογή σε false. Εάν αυτή η ιδιότητα είναι ψευδής όταν ένα νήμα εισέρχεται σε μια παράλληλη περιοχή, ο χρόνος εκτέλεσης OpenMP δημιουργεί μια ομάδα της οποίας ο αριθμός των νημάτων είναι ίσος με την τιμή που επιστρέφεται από το omp_get_max_threads. Από προεπιλογή, το omp_get_max_threads επιστρέφει τον αριθμό των νημάτων που υποστηρίζονται από το υλικό ή την τιμή της μεταβλητής OMP_NUM_THREADS. Εάν είναι ενεργοποιημένη η υποστήριξη για τη δημιουργία δυναμικών νημάτων, ο χρόνος εκτέλεσης OpenMP θα δημιουργήσει μια ομάδα που μπορεί να περιέχει μεταβλητός αριθμόςνήματα, που δεν υπερβαίνουν την τιμή που επιστρέφεται από τη συνάρτηση omp_get_max_threads.

Η ένθεση των παράλληλων περιοχών καθορίζεται επίσης από μια δυαδική ιδιότητα, η οποία έχει οριστεί ως false από προεπιλογή. Η ένθεση παράλληλης περιοχής συμβαίνει όταν ένα νήμα που ήδη εκτελεί μια παράλληλη περιοχή συναντά μια άλλη παράλληλη περιοχή. Εάν επιτρέπεται η ένθεση, δημιουργείται μια νέα ομάδα νημάτων, ακολουθώντας τους κανόνες που περιγράφηκαν προηγουμένως. Και αν δεν επιτρέπεται η ένθεση, σχηματίζεται μια ομάδα που περιέχει ένα νήμα.

Για να ορίσετε και να διαβάσετε ιδιότητες που καθορίζουν τη δυνατότητα δυναμικής δημιουργίας νημάτων και ένθεσης παράλληλων περιοχών, χρησιμοποιήστε τις συναρτήσεις omp_set_dynamic, omp_get_dynamic, omp_set_nested και omp_get_nested. Επιπλέον, κάθε νήμα μπορεί να ζητήσει πληροφορίες για το περιβάλλον του. Για να μάθετε τον αριθμό νήματος σε μια ομάδα νημάτων, καλέστε το omp_get_thread_num. Θυμηθείτε ότι δεν επιστρέφει το αναγνωριστικό νήματος των Windows, αλλά έναν αριθμό στην περιοχή από 0 έως omp_get_num_threads - 1.

Η συνάρτηση omp_in_parallel ενημερώνει ένα νήμα εάν εκτελεί αυτήν τη στιγμή μια παράλληλη περιοχή και το omp_get_num_procs επιστρέφει τον αριθμό των επεξεργαστών στο μηχάνημα.

Για να κατανοήσετε καλύτερα τις σχέσεις μεταξύ των διαφόρων συναρτήσεων χρόνου εκτέλεσης, ρίξτε μια ματιά στη Λίστα 4. Σε αυτό το παράδειγμα, έχουμε υλοποιήσει τέσσερις ξεχωριστές παράλληλες περιοχές και δύο ένθετες.

Λίστα 4. Χρήση ρουτίνες χρόνου εκτέλεσης OpenMP

#συμπεριλαμβάνω #συμπεριλαμβάνω int main() ( omp_set_dynamic(1); omp_set_num_threads(10); #pragma omp parallel // parallel area 1 ( #pragma omp single printf("Num threads in dynamic area is = %d\n", omp_get_num_threads()); ) printf("\n"); omp_set_dynamic(0); ; printf("\n"); omp_set_num_threads(10) #pragma omp. ) ) printf("\n"); omp_set_nested(1); omp_get_num_threads() ) )

Μεταγλώττιση αυτού του κώδικα σε Visual StudioΤο 2005 και εκτελώντας το σε έναν κανονικό υπολογιστή διπλού επεξεργαστή, πήραμε το ακόλουθο αποτέλεσμα:

Ο αριθμός νημάτων στη δυναμική περιοχή είναι = 2 Αριθμός νημάτων στη μη δυναμική περιοχή είναι = 10 Ο αριθμός νημάτων στην απενεργοποιημένη περιοχή ένθεσης είναι = 1 Ο αριθμός νημάτων στην απενεργοποιημένη περιοχή ένθεσης είναι = 1 Ο αριθμός νημάτων σε ένθετη περιοχή είναι = 2 ο αριθμός νημάτων είναι σε ένθετη περιοχή = 2

Για την πρώτη περιοχή, ενεργοποιήσαμε τη δημιουργία δυναμικών νημάτων και ορίσαμε τον αριθμό των νημάτων σε 10. Τα αποτελέσματα του προγράμματος δείχνουν ότι με ενεργοποιημένη τη δυναμική δημιουργία νημάτων, ο χρόνος εκτέλεσης OpenMP αποφάσισε να δημιουργήσει μια ομάδα που περιελάμβανε μόνο δύο νήματα, καθώς ο υπολογιστής έχει δύο επεξεργαστές. Για τη δεύτερη παράλληλη περιοχή, ο χρόνος εκτέλεσης OpenMP δημιούργησε μια ομάδα νημάτων 10 επειδή η δυναμική δημιουργία νημάτων ήταν απενεργοποιημένη για αυτήν την περιοχή.

Τα αποτελέσματα της λειτουργίας της τρίτης και της τέταρτης παράλληλης περιοχής απεικονίζουν τις συνέπειες της ενεργοποίησης και απενεργοποίησης της ικανότητας φωλεοποίησης περιοχών. Στην τρίτη παράλληλη περιοχή, η ένθεση απενεργοποιήθηκε, επομένως δεν δημιουργήθηκαν νέα νήματα για την ένθετη παράλληλη περιοχή - τόσο η εξωτερική όσο και η ένθετη παράλληλη περιοχή εκτελέστηκαν από δύο νήματα. Στην τέταρτη παράλληλη περιοχή όπου ενεργοποιήθηκε η ένθεση, δημιουργήθηκε μια ομάδα δύο νημάτων για την ένθετη παράλληλη περιοχή (δηλαδή, συνολικά τέσσερα νήματα εκτελούσαν την περιοχή). Η διαδικασία διπλασιασμού του αριθμού των νημάτων για κάθε ένθετη παράλληλη περιοχή μπορεί να συνεχιστεί μέχρι να εξαντληθεί ο χώρος στοίβας. Στην πράξη, είναι δυνατό να δημιουργηθούν αρκετές εκατοντάδες νήματα, αν και το κόστος που σχετίζεται με αυτό θα υπερβεί εύκολα τυχόν οφέλη.

Όπως ίσως έχετε παρατηρήσει, η δυναμική δημιουργία νήματος έχει ενεργοποιηθεί για την τρίτη και την τέταρτη παράλληλη περιοχή. Ας δούμε τι θα συμβεί αν εκτελέσουμε τον ίδιο κώδικα, απενεργοποιώντας τη δυναμική δημιουργία νημάτων:

Omp_set_dynamic(0); omp_set_nested(1); omp_set_num_threads(10); #pragma omp parallel ( #pragma omp parallel ( #pragma omp single printf("Ο αριθμός νημάτων στην ένθετη περιοχή είναι = %d\n", omp_get_num_threads()); ) )

Και αυτό που συμβαίνει είναι αυτό που θα περίμενες. Δημιουργείται μια ομάδα 10 νημάτων για την πρώτη παράλληλη περιοχή και, στη συνέχεια, κατά την είσοδο σε μια ένθετη παράλληλη περιοχή, δημιουργείται επίσης μια ομάδα 10 νημάτων για καθένα από αυτά τα 10 νήματα. Συνολικά 100 νήματα εκτελούν την ένθετη παράλληλη περιοχή:

Ο αριθμός νημάτων στην ένθετη περιοχή είναι = 10 Ο αριθμός νημάτων στην ένθετη περιοχή είναι = 10 ο αριθμός των νημάτων στην ένθετη περιοχή είναι = 10 ο αριθμός των νημάτων στην ένθετη περιοχή είναι = 10 ο αριθμός των νημάτων στην ένθετη περιοχή είναι = 10 ο αριθμός των νημάτων στην ένθετη περιοχή είναι = 10 ο αριθμός των νημάτων σε την ένθετη περιοχή στην ένθετη περιοχή είναι = 10 Αριθμός νημάτων στην ένθετη περιοχή είναι = 10 Ο αριθμός νημάτων στην ένθετη περιοχή είναι = 10 Ο αριθμός νημάτων στην ένθετη περιοχή είναι = 10

Μέθοδοι συγχρονισμού/κλειδώματος

Το OpenMP περιλαμβάνει επίσης λειτουργίες σχεδιασμένες για συγχρονισμό κώδικα. Υπάρχουν δύο τύποι κλειδαριών στο OpenMP: απλές και ένθετες (φωλιασμένες). Και οι δύο τύποι κλειδαριών μπορούν να βρίσκονται σε μία από τις τρεις καταστάσεις - μη αρχικοποιημένες, κλειδωμένες και ξεκλείδωτες.

Οι απλές κλειδαριές (omp_lock_t) δεν μπορούν να αποκτηθούν περισσότερες από μία φορές, ακόμη και από το ίδιο νήμα. Οι ένθετες κλειδαριές (omp_nest_lock_t) είναι πανομοιότυπες με τις απλές, εκτός από το ότι όταν ένα νήμα προσπαθεί να αποκτήσει μια ένθετη κλειδαριά που ήδη κατέχει, δεν μπλοκάρει. Επιπλέον, το OpenMP διατηρεί αρχείο με τις αναφορές ένθετων κλειδαριών και παρακολουθεί πόσες φορές έχουν οριστεί.

Το OpenMP παρέχει ρουτίνες που εκτελούν λειτουργίες σε αυτές τις κλειδαριές. Κάθε τέτοια λειτουργία έχει δύο επιλογές: για απλές και για ένθετες κλειδαριές. Μπορείτε να εκτελέσετε πέντε ενέργειες σε μια κλειδαριά: να την αρχικοποιήσετε, να την αποκτήσετε (να την αποκτήσετε), να την απελευθερώσετε, να την ελέγξετε και να την καταστρέψετε. Όλες αυτές οι λειτουργίες είναι πολύ παρόμοιες με τις λειτουργίες Win32 για εργασία με κρίσιμα τμήματα και αυτό δεν είναι τυχαίο: στην πραγματικότητα, η τεχνολογία OpenMP υλοποιείται ως περιτύλιγμα γύρω από αυτές τις λειτουργίες. Η αντιστοιχία μεταξύ των λειτουργιών OpenMP και Win32 απεικονίζεται στον Πίνακα. 2.

Τραπέζι 2. Λειτουργίες για εργασία με κλειδαριές σε OpenMP και Win32

Απλός αποκλεισμός OpenMP Ένθετο κλείδωμα OpenMP Λειτουργία Win32
omp_lock_t omp_nest_lock_t CRITICAL_SECTION
omp_init_lock omp_init_nest_lock InitializeCriticalSection
omp_destroy_lock omp_destroy_nest_lock DeleteCriticalSection
omp_set_lock omp_set_nest_lock EnterCriticalSection
omp_unset_lock omp_unset_nest_lock LeaveCriticalSection
omp_test_lock omp_test_nest_lock Δοκιμάστε το EnterCriticalSection

Μπορείτε να χρησιμοποιήσετε τόσο τις ρουτίνες χρόνου εκτέλεσης όσο και τις οδηγίες συγχρονισμού για να συγχρονίσετε τον κώδικα. Το πλεονέκτημα των οδηγιών είναι ότι είναι καλά δομημένες. Αυτό τα καθιστά πιο ξεκάθαρα και διευκολύνει τον εντοπισμό των συγχρονισμένων περιοχών που εισέρχεστε και εξέρχονται.

Το πλεονέκτημα των ρουτινών χρόνου εκτέλεσης είναι η ευελιξία. Για παράδειγμα, μπορείτε να περάσετε το κλείδωμα σε άλλη λειτουργία και να το ρυθμίσετε/απελευθερώσετε σε αυτήν τη λειτουργία. Αυτό δεν είναι δυνατό όταν χρησιμοποιούνται οδηγίες. Γενικά, εκτός εάν χρειάζεστε την ευελιξία που παρέχεται μόνο από τις ρουτίνες χρόνου εκτέλεσης, είναι προτιμότερο να χρησιμοποιείτε οδηγίες συγχρονισμού.

Παράλληλη επεξεργασία δομών δεδομένων

Η λίστα 5 δείχνει τον κώδικα για δύο βρόχους που εκτελούνται παράλληλα, στην αρχή των οποίων ο χρόνος εκτέλεσης δεν γνωρίζει τον αριθμό των επαναλήψεων. Το πρώτο παράδειγμα επαναλαμβάνεται πάνω από τα στοιχεία του κοντέινερ STL std::vector και το δεύτερο επαναλαμβάνεται πάνω από τα στοιχεία μιας τυπικής συνδεδεμένης λίστας.

Λίστα 5. Εκτέλεση άγνωστου αριθμού επαναλήψεων

#pragma omp parallel ( // Παράλληλη επεξεργασία vector STL std::vector ::iter iter;

for(iter = xVect.begin(); iter != xVect.end(); ++iter) ( #pragma omp single nowait ( process1(*iter); ) ) // Παράλληλη επεξεργασία μιας τυπικής συνδεδεμένης λίστας για (LLlist *listWalk = listHead listWalk != NULL = listWalk->next) (#pragma omp single nowait);

Στο παράδειγμα του διανύσματος STL, κάθε νήμα στην ομάδα των νημάτων εκτελεί έναν βρόχο for και έχει τη δική του παρουσία του επαναλήπτη, αλλά σε κάθε επανάληψη, μόνο ένα νήμα εισέρχεται στο μεμονωμένο μπλοκ (αυτή είναι η σημασιολογία της μεμονωμένης οδηγίας). Όλες οι ενέργειες που εγγυώνται την ενιαία εκτέλεση του μεμονωμένου μπλοκ σε κάθε επανάληψη αναλαμβάνονται από το περιβάλλον χρόνου εκτέλεσης OpenMP. Υπάρχει σημαντικό κόστος για την εκτέλεση του βρόχου με αυτόν τον τρόπο, επομένως είναι χρήσιμο μόνο εάν γίνεται πολλή δουλειά στη συνάρτηση process1. Το παράδειγμα της συνδεδεμένης λίστας χρησιμοποιεί την ίδια λογική.

Αξίζει να σημειωθεί ότι στο παράδειγμα με το διάνυσμα STL, πριν μπούμε στον βρόχο, μπορούμε να προσδιορίσουμε τον αριθμό των επαναλήψεών του με την τιμή του std::vector.size, που μας επιτρέπει να φέρουμε τον βρόχο στην κανονική μορφή για το OpenMP :< xVect.size(); ++i) process(xVect[i]);

#pragma omp παράλληλη για for(int i = 0; i

Αυτό μειώνει σημαντικά την επιβάρυνση του χρόνου εκτέλεσης και είναι η προσέγγιση που προτείνουμε για την επεξεργασία πινάκων, διανυσμάτων και οποιωνδήποτε άλλων κοντέινερ των οποίων τα στοιχεία μπορούν να επαναληφθούν σε έναν βρόχο for που ακολουθεί την κανονική μορφή για το OpenMP.

Πιο περίπλοκοι αλγόριθμοι προγραμματισμού

Από προεπιλογή, το OpenMP χρησιμοποιεί έναν αλγόριθμο που ονομάζεται στατικός προγραμματισμός για να προγραμματίσει την παράλληλη εκτέλεση των βρόχων for. Αυτό σημαίνει ότι όλα τα νήματα σε μια ομάδα εκτελούν τον ίδιο αριθμό επαναλήψεων του βρόχου. Εάν n είναι ο αριθμός των επαναλήψεων βρόχου και T είναι ο αριθμός των νημάτων στην ομάδα, κάθε νήμα θα εκτελεί επαναλήψεις n/T (αν το n δεν διαιρείται ομοιόμορφα με το T, δεν πειράζει). Ωστόσο, το OpenMP υποστηρίζει επίσης άλλους μηχανισμούς προγραμματισμού που είναι βέλτιστοι σε διαφορετικές καταστάσεις: δυναμικός προγραμματισμός, προγραμματισμός χρόνου εκτέλεσης και καθοδηγούμενος προγραμματισμός.

Για να καθορίσετε έναν από αυτούς τους μηχανισμούς προγραμματισμού, χρησιμοποιήστε την ενότητα χρονοδιαγράμματος της #pragma omp for ή #pragma omp παράλληλη για την οδηγία. Η μορφή αυτής της ενότητας μοιάζει με αυτό:

Χρονοδιάγραμμα (αλγόριθμος προγραμματισμού[, αριθμός επαναλήψεων])

Ακολουθούν παραδείγματα αυτών των οδηγιών:< 100; ++i) ... #pragma omp parallel #pragma omp for schedule(guided)

Στον δυναμικό προγραμματισμό, κάθε νήμα εκτελεί έναν καθορισμένο αριθμό επαναλήψεων. Εάν αυτός ο αριθμός δεν έχει καθοριστεί, ορίζεται από προεπιλογή σε 1. Αφού το νήμα ολοκληρώσει τις καθορισμένες επαναλήψεις, μεταβαίνει στο επόμενο σύνολο επαναλήψεων. Αυτό συνεχίζεται μέχρι να ολοκληρωθούν όλες οι επαναλήψεις. Τελευταίο σετΕνδέχεται να υπάρχουν λιγότερες επαναλήψεις από ό,τι είχε αρχικά καθοριστεί.

Στον διαχειριζόμενο προγραμματισμό, ο αριθμός των επαναλήψεων που εκτελούνται από κάθε νήμα καθορίζεται από τον ακόλουθο τύπο:

Number_of_iterations_executed by a thread = max(number_of_unallocated_iterations/omp_get_num_threads(), αριθμός επαναλήψεων)

Έχοντας ολοκληρώσει τις επαναλήψεις που του έχουν ανατεθεί, το νήμα ζητά ένα άλλο σύνολο επαναλήψεων, ο αριθμός των οποίων καθορίζεται από τον τύπο που μόλις δόθηκε. Έτσι, ο αριθμός των επαναλήψεων που εκχωρούνται σε κάθε νήμα μειώνεται με την πάροδο του χρόνου. Το τελευταίο σύνολο επαναλήψεων μπορεί να είναι μικρότερο από την τιμή που υπολογίζεται από τον τύπο.

Καθορίζοντας την οδηγία #pragma omp for schedule(dynamic, 15), ένας βρόχος for 100 επαναλήψεων μπορεί να εκτελεστεί από τέσσερα νήματα ως εξής:

Το νήμα 0 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 1-15 Το νήμα 1 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 16-30 Το νήμα 2 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 31-45 Το νήμα 3 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 46-60 Το νήμα 2 ολοκληρώνει τις επαναλήψεις είναι εξουσιοδοτημένο νήμα 2 των επαναλήψεων 61-75 Το νήμα 3 ολοκληρώνει την εκτέλεση των επαναλήψεων Το νήμα 3 εξουσιοδοτείται να εκτελέσει επαναλήψεις 76-90 Το νήμα 0 ολοκληρώνει την εκτέλεση των επαναλήψεων Το νήμα 0 εξουσιοδοτείται να εκτελέσει επαναλήψεις 91-100

Αλλά αυτό θα μπορούσε να είναι το αποτέλεσμα της εκτέλεσης του ίδιου βρόχου με τέσσερα νήματα εάν προσδιορίζεται η οδηγία #pragma omp for schedule(guided, 15):

Το νήμα 0 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 1-25 Το νήμα 1 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 26-44 Το νήμα 2 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 45-59 Το νήμα 3 είναι εξουσιοδοτημένο να εκτελεί επαναλήψεις 60-64 Το νήμα 2 ολοκληρώνει τις επαναλήψεις είναι εξουσιοδοτημένο νήμα 2 των επαναλήψεων 65-79 Το νήμα 3 ολοκληρώνει την εκτέλεση των επαναλήψεων Το νήμα 3 εξουσιοδοτείται για την εκτέλεση επαναλήψεων 80-94 Το νήμα 2 ολοκληρώνει την εκτέλεση των επαναλήψεων Το νήμα 2 εξουσιοδοτείται για την εκτέλεση επαναλήψεων 95-100

Ο δυναμικός και ελεγχόμενος προγραμματισμός είναι καλές επιλογές εάν εκτελούνται διαφορετικοί όγκοι εργασίας σε κάθε επανάληψη ή εάν ορισμένοι επεξεργαστές είναι πιο αποτελεσματικοί από άλλους. Με τον στατικό προγραμματισμό, δεν υπάρχει τρόπος να εξισορροπηθεί το φορτίο σε διαφορετικά νήματα. Με τον δυναμικό και διαχειριζόμενο προγραμματισμό, το φορτίο κατανέμεται αυτόματα - αυτή είναι η ίδια η ουσία αυτών των προσεγγίσεων. Ο διαχειριζόμενος προγραμματισμός συνήθως εκτελεί τον κώδικα πιο γρήγορα από τον δυναμικό προγραμματισμό λόγω χαμηλότερου κόστους προγραμματισμού.

Η τελευταία προσέγγιση - προγραμματισμός κατά την εκτέλεση - μάλλον δεν είναι καν ένας αλγόριθμος προγραμματισμού, αλλά μια μέθοδος δυναμική επιλογήέναν από τους τρεις αλγόριθμους που περιγράφηκαν. Εάν η παράμετρος χρόνου εκτέλεσης καθορίζεται στην ενότητα χρονοδιαγράμματος, ο χρόνος εκτέλεσης OpenMP χρησιμοποιεί τον αλγόριθμο προγραμματισμού που έχει καθοριστεί για έναν συγκεκριμένο βρόχο for χρησιμοποιώντας τη μεταβλητή OMP_SCHEDULE. Έχει τη μορφή "τύπος[, αριθμός επαναλήψεων]", για παράδειγμα:

Ορίστε OMP_SCHEDULE=δυναμικό,8

Ο προγραμματισμός χρόνου εκτέλεσης παρέχει κάποια ευελιξία στην επιλογή του τύπου προγραμματισμού, με τον στατικό προγραμματισμό να είναι η προεπιλογή.

Πότε να χρησιμοποιήσετε το OpenMP;

Το να γνωρίζετε πότε να χρησιμοποιείτε την τεχνολογία OpenMP είναι εξίσου σημαντικό με το να γνωρίζετε πώς να τη χρησιμοποιείτε. Ελπίζουμε ότι οι συμβουλές μας θα σας βοηθήσουν.

Η πλατφόρμα στόχος είναι πολυεπεξεργαστής ή πολυπύρηνος. Εάν μια εφαρμογή χρησιμοποιεί πλήρως τους πόρους ενός πυρήνα ή ενός επεξεργαστή, τότε η δημιουργία της πολλαπλών νημάτων χρησιμοποιώντας το OpenMP θα βελτιώσει σχεδόν σίγουρα την απόδοσή της.

Η εφαρμογή πρέπει να είναι cross-platform. Το OpenMP είναι ένα cross-platform και ευρέως υποστηριζόμενο API. Και δεδομένου ότι υλοποιείται με βάση τις οδηγίες pragma, η εφαρμογή μπορεί να μεταγλωττιστεί ακόμη και χρησιμοποιώντας έναν μεταγλωττιστή που δεν υποστηρίζει το πρότυπο OpenMP.

Η εκτέλεση βρόχου πρέπει να παραλληλιστεί. Το OpenMP επιδεικνύει το πλήρες δυναμικό του όταν οργανώνει την παράλληλη εκτέλεση βρόχων. Εάν η εφαρμογή σας έχει μεγάλους βρόχους χωρίς εξαρτήσεις, το OpenMP είναι η ιδανική λύση.

Πριν κυκλοφορήσετε την εφαρμογή σας, πρέπει να βελτιώσετε την απόδοσή της.Επειδή το OpenMP δεν απαιτεί επανασχεδιασμό της αρχιτεκτονικής της εφαρμογής σας, είναι εξαιρετικό για να κάνετε μικρές αλλαγές στον κώδικά σας για να βελτιώσετε την απόδοση.

Ταυτόχρονα, θα πρέπει να αναγνωριστεί ότι το OpenMP δεν είναι πανάκεια για όλα τα δεινά. Αυτή η τεχνολογία απευθύνεται κυρίως σε προγραμματιστές υπολογιστικών συστημάτων υψηλής απόδοσης και είναι πιο αποτελεσματική εάν ο κώδικας περιλαμβάνει πολλούς βρόχους και λειτουργεί με κοινόχρηστους πίνακες δεδομένων.

Η δημιουργία τόσο κανονικών νημάτων όσο και παράλληλων περιοχών OpenMP έχει κόστος. Για να είναι το OpenMP επωφελές, το όφελος ταχύτητας που παρέχεται από μια παράλληλη περιοχή πρέπει να υπερβαίνει το κόστος δημιουργίας μιας ομάδας νημάτων. Στην έκδοση Visual C++ του OpenMP, δημιουργείται μια ομάδα νημάτων κατά την είσοδο στην πρώτη παράλληλη περιοχή. Μόλις ολοκληρωθεί μια περιοχή, η ομάδα νημάτων τίθεται σε αναστολή μέχρι να χρειαστεί ξανά. Πίσω από τις σκηνές, το OpenMP χρησιμοποιεί το νήμα των Windows. Ρύζι. Το σχήμα 2 απεικονίζει την αύξηση της απόδοσης του απλού προγράμματος που δίνεται στην αρχή του άρθρου, η οποία επιτυγχάνεται χάρη στο OpenMP σε έναν υπολογιστή διπλού επεξεργαστή με διαφορετικό αριθμό επαναλήψεων. Η μέγιστη αύξηση απόδοσης είναι περίπου 1,7 από την αρχική, η οποία είναι τυπική για συστήματα διπλού επεξεργαστή.

Επί αυτό το διάγραμμαΟ άξονας y αντιπροσωπεύει την αναλογία του χρόνου διαδοχικής εκτέλεσης του κώδικα προς τον χρόνο παράλληλης εκτέλεσης του ίδιου κώδικα. Σημειώστε ότι η παράλληλη έκδοση ξεπερνά τη διαδοχική έκδοση σε περίπου 5000 επαναλήψεις, αλλά αυτό είναι σχεδόν το χειρότερο σενάριο. Οι περισσότεροι παράλληλοι βρόχοι θα εκτελούνται ταχύτερα από τους διαδοχικούς βρόχους ακόμη και με σημαντικά λιγότερες επαναλήψεις. Αυτό εξαρτάται από την ποσότητα της εργασίας που γίνεται σε κάθε επανάληψη. Ανεξάρτητα από αυτό, αυτό το γράφημα δείχνει πόσο σημαντικό είναι να αξιολογείται η απόδοση του λογισμικού. Η χρήση μόνο του OpenMP δεν εγγυάται ότι ο κώδικάς σας θα εκτελείται πιο γρήγορα.

Οι οδηγίες OpenMP pragma είναι εύκολες στη χρήση, αλλά δεν παρέχουν λεπτομερείς πληροφορίες σφαλμάτων. Αν γράφεις κριτικά σημαντική εφαρμογή, το οποίο θα πρέπει να εντοπίσει σφάλματα και να επαναφέρει σωστά κανονική δουλειά, το OpenMP μάλλον θα πρέπει να εγκαταλειφθεί (σύμφωνα με τουλάχιστον, Αντίο). Για παράδειγμα, εάν το OpenMP δεν μπορεί να δημιουργήσει νήματα για παράλληλες περιοχές ή ένα κρίσιμο τμήμα, η συμπεριφορά του προγράμματος καθίσταται απροσδιόριστη. Στο Visual C++ 2005, ο χρόνος εκτέλεσης OpenMP συνεχίζει να προσπαθεί να εκτελεστεί την επιθυμητή εργασία, μετά την οποία εγκαταλείπει. Σε μελλοντικές εκδόσεις του OpenMP, μεταξύ άλλων, πρόκειται να εφαρμόσουμε έναν τυπικό μηχανισμό ειδοποίησης σφαλμάτων.

Μια άλλη κατάσταση όπου πρέπει να είστε προσεκτικοί είναι όταν χρησιμοποιείτε νήματα των Windows με νήματα OpenMP. Τα νήματα OpenMP βασίζονται σε νήματα των Windows, επομένως εκτελούνται όμορφα με την ίδια διαδικασία. Δυστυχώς, το OpenMP δεν γνωρίζει τίποτα για τα νήματα των Windows που δημιουργούνται από άλλες μονάδες. Δύο προβλήματα προκύπτουν από αυτό: πρώτον, ο χρόνος εκτέλεσης OpenMP δεν παρακολουθεί άλλα νήματα των Windows και δεύτερον, οι μέθοδοι συγχρονισμού OpenMP δεν συγχρονίζονται νήματα των Windowsεπειδή δεν αποτελούν μέρος ομάδων νημάτων.

Παγίδες στις οποίες μπορείτε να πέσετε όταν χρησιμοποιείτε το OpenMP

Αν και η χρήση του OpenMP δεν είναι καθόλου δύσκολη, ορισμένες πτυχές εξακολουθούν να απαιτούν ιδιαίτερη προσοχή. Για παράδειγμα, η μεταβλητή ευρετηρίου της πιο εξωτερικής παράλληλης για βρόχο είναι ιδιωτική, αλλά οι μεταβλητές ευρετηρίου των ένθετων βρόχων for είναι δημόσιες από προεπιλογή. Όταν εργάζεστε με ένθετους βρόχους, συνήθως θέλετε τα ευρετήρια του εσωτερικού βρόχου να είναι ιδιωτικά. Χρησιμοποιήστε το ιδιωτικό τμήμα για αυτό.

Κατά την ανάπτυξη εφαρμογών OpenMP, θα πρέπει να είστε προσεκτικοί όταν ρίχνετε εξαιρέσεις C++. Εάν μια εφαρμογή ρίχνει μια εξαίρεση σε μια παράλληλη περιοχή, πρέπει να αντιμετωπιστεί στην ίδια περιοχή από το ίδιο νήμα. Με άλλα λόγια, η εξαίρεση δεν πρέπει να φύγει από την περιοχή. Κατά γενικό κανόνα, οποιεσδήποτε εξαιρέσεις μπορεί να τεθούν σε παράλληλη περιοχή πρέπει να συλληφθούν. Εάν δεν καταλάβετε την εξαίρεση στην ίδια ταυτόχρονη περιοχή, η εφαρμογή πιθανότατα θα διακοπεί.

Για να μπορέσετε να ανοίξετε ένα δομημένο μπλοκ, η έκφραση

#pragma omp<директива>[κεφάλαιο]

πρέπει να τελειώνει με ένα σύμβολο νέα γραμμή, όχι σγουρό στήριγμα. Μια οδηγία που τελειώνει με ένα σγουρό στήριγμα θα έχει ως αποτέλεσμα ένα σφάλμα μεταγλώττισης1:

// Κακό #pragma omp parallel ( // Σφάλμα μεταγλώττισης) // Καλό #pragma omp parallel ( // Κωδικός)

Ο εντοπισμός σφαλμάτων των εφαρμογών OpenMP στο Visual Studio 2005 μπορεί να είναι δύσκολος. Συγκεκριμένα, ορισμένες ενοχλήσεις σχετίζονται με την είσοδο και/ή την έξοδο από μια παράλληλη περιοχή πατώντας το πλήκτρο F10/F11. Αυτό συμβαίνει επειδή ο μεταγλωττιστής δημιουργεί πρόσθετος κωδικόςγια να καλέσετε τις ομάδες χρόνου εκτέλεσης και νημάτων. Το πρόγραμμα εντοπισμού σφαλμάτων δεν το γνωρίζει αυτό, επομένως αυτό που βλέπετε μπορεί να σας φαίνεται περίεργο. Συνιστούμε να ορίσετε ένα σημείο διακοπής σε μια παράλληλη περιοχή και να πατήσετε το F5 για να το χτυπήσετε. Για έξοδο από μια παράλληλη περιοχή, ορίστε ένα σημείο διακοπής εκτός αυτής της περιοχής και πατήστε F5.

Όταν βρίσκεστε σε μια παράλληλη περιοχή, το παράθυρο νημάτων του προγράμματος εντοπισμού σφαλμάτων θα εμφανίζει πληροφορίες σχετικά με τα νήματα που εκτελούνται στην ομάδα νημάτων. Τα αναγνωριστικά αυτών των νημάτων δεν θα αντιστοιχούν στα νήματα OpenMP, αλλά στα υποκείμενα νήματα των Windows.

Προς το παρόν, δεν μπορείτε να χρησιμοποιήσετε το Profile Guided Optimization (PGO) με το OpenMP. Ευτυχώς, η τεχνολογία OpenMP βασίζεται σε οδηγίες pragma, οπότε μπορείτε να μεταγλωττίσετε την εφαρμογή σας με την επιλογή /openmp και με το PGO και να δείτε ποια προσέγγιση είναι πιο αποτελεσματική.

OpenMP και .NET

Λίγοι άνθρωποι συσχετίζουν τους υπολογιστές υψηλής απόδοσης με το .NET, αλλά το Visual C++ 2005 βελτιώνει αυτήν την κατάσταση. Αξίζει ιδιαίτερα να σημειωθεί τι έχουμε πετύχει συνεργασία OpenMP με διαχειριζόμενο κώδικα C++. Για να το πετύχουμε αυτό, έχουμε κάνει το /openmp συμβατό με τα /clr και /clr:OldSyntax. Δηλαδή, μπορείτε να χρησιμοποιήσετε το OpenMP για να εκτελέσετε μεθόδους τύπων .NET παράλληλα που υπόκεινται σε συλλογή απορριμμάτων. Λάβετε υπόψη ότι το /openmp δεν είναι προς το παρόν συμβατό ούτε με το /clr:safe ούτε με το /clr:pure, αλλά σκοπεύουμε να το διορθώσουμε.

Θα πρέπει να αναφέρουμε έναν σημαντικό περιορισμό που σχετίζεται με τη χρήση του OpenMP σε διαχειριζόμενο κώδικα. Μια εφαρμογή που χρησιμοποιεί OpenMP θα πρέπει να χρησιμοποιείται μόνο σε έναν τομέα εφαρμογής. Κατά τη φόρτωση ενός άλλου AppDomain σε μια διαδικασία με έναν εκτελεστή ήδη φορτωμένο Περιβάλλον OpenMPη εφαρμογή μπορεί να διακοπεί.

Το OpenMP είναι μια απλή αλλά ισχυρή τεχνολογία παραλληλοποίησης εφαρμογών. Σας επιτρέπει να εφαρμόσετε παράλληλη εκτέλεση τόσο βρόχων όσο και λειτουργικών μπλοκ κώδικα. Ενσωματώνεται εύκολα σε υπάρχουσες εφαρμογέςκαι ενεργοποιείται/απενεργοποιείται από μία παράμετρο μεταγλωττιστή. Το OpenMP σάς επιτρέπει να χρησιμοποιείτε πληρέστερα την υπολογιστική ισχύ των πολυπύρηνων επεξεργαστών. Σας συμβουλεύουμε ανεπιφύλακτα να εξοικειωθείτε με την προδιαγραφή OpenMP. Καλή τύχη στην ανάπτυξη προγραμμάτων πολλαπλών νημάτων!

Υποδομή OpenMPσας επιτρέπει να εφαρμόζετε αποτελεσματικά τεχνολογίες παράλληλου προγραμματισμού σε C, C++ και Fortran. Η συλλογή GNU Compiler (GCC) έκδοση 4.2 υποστηρίζει την προδιαγραφή OpenMP 2.5 και η έκδοση 4.4 GCC υποστηρίζει την πιο πρόσφατη προδιαγραφή OpenMP 3. Άλλοι μεταγλωττιστές, συμπεριλαμβανομένου του Microsoft® Visual Studio, υποστηρίζουν επίσης το OpenMP. Αυτό το άρθρο θα σας διδάξει πώς να χρησιμοποιείτε τα pragmas Μεταγλωττιστής OpenMP; Περιέχει επίσης πληροφορίες σχετικά με ορισμένα από τα API του OpenMP και καλύπτει ορισμένες τεχνικές παράλληλων υπολογιστών που χρησιμοποιούν OpenMP. Όλα τα παραδείγματα σε αυτό το άρθρο χρησιμοποιούν τον μεταγλωττιστή GCC 4.2.

Ξεκινώντας

Ένα τεράστιο πλεονέκτημα του OpenMP είναι ότι δεν υπάρχει ανάγκη πρόσθετες ενέργειες, με εξαίρεση την τυπική εγκατάσταση του μεταγλωττιστή GCC. Οι εφαρμογές OpenMP πρέπει να μεταγλωττίζονται με την επιλογή -fopenmp.

Δημιουργία της πρώτης εφαρμογής OpenMP

Ας ξεκινήσουμε με τη συγγραφή απλή εφαρμογή Γεια, Κόσμος!που περιέχει ένα πρόσθετο πράγμα. Ο κωδικός για αυτήν την εφαρμογή εμφανίζεται στη Λίστα 1.

Λίστα 1. Πρόγραμμα "Hello World" γραμμένο με χρήση OpenMP
#συμπεριλαμβάνω int main() ( #pragma omp parallel ( std::cout<< "Hello World!\n"; } }

Αφού μεταγλωττίσετε και εκτελέσετε αυτόν τον κώδικα χρησιμοποιώντας το g++, θα δείτε το ακόλουθο μήνυμα στην οθόνη σας: Γεια, Κόσμος!. Τώρα ας μεταγλωττίσουμε ξανά τον κώδικα με την επιλογή -fopenmp. Το αποτέλεσμα του προγράμματος παρουσιάζεται στην Λίστα 2.

Λίστα 2. Μεταγλώττιση και εκτέλεση κώδικα χρησιμοποιώντας την επιλογή -fopenmp
tintin$ g++ test1.cpp -fopenmp tintin$ ./a.out Hello World! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο!

Τι συνέβη; Όταν χρησιμοποιείτε την επιλογή μεταγλωττιστή -fopenmp, η παράλληλη οδηγία #pragma omp μπαίνει στο παιχνίδι. Κατά τη μεταγλώττιση, τα εσωτερικά του GCC δημιουργούν τόσα παράλληλα νήματα όσα μπορούν να εκτελεστούν υπό τις βέλτιστες συνθήκες φόρτωσης του συστήματος (ανάλογα με το υλικό και τις διαμορφώσεις του λειτουργικού συστήματος), με κάθε νήμα που δημιουργείται να εκτελεί τον κώδικα που περικλείεται στο μπλοκ μετά το pragma. Αυτή η συμπεριφορά ονομάζεται σιωπηρή παραλληλοποίηση, και ο πυρήνας OpenMP αποτελείται από ένα σύνολο ισχυρών πρακτικών που σας γλιτώνουν από τη σύνταξη πολλών τυπικών τμημάτων κώδικα (για διασκέδαση, μπορείτε να συγκρίνετε τον δεδομένο κώδικα με την υλοποίηση της ίδιας εργασίας χρησιμοποιώντας νήματα POSIX). Χρησιμοποιώ έναν υπολογιστή με επεξεργαστή Intel® Core i7 με 4 φυσικούς πυρήνες από 2 λογικούς πυρήνες ο καθένας, κάτι που εξηγεί τα αποτελέσματα στη Λίστα 2 (8 νήματα = 8 λογικούς πυρήνες).

Παράλληλες λειτουργίες OpenMP

Ο αριθμός των νημάτων μπορεί εύκολα να ελεγχθεί χρησιμοποιώντας ένα pragma με το όρισμα num_threads. Παρακάτω είναι ο κωδικός από τη Λίστα 1 με τον αριθμό των νημάτων που έχουν οριστεί (5 νήματα):

Λίστα 3. Έλεγχος του αριθμού των νημάτων χρησιμοποιώντας το num_threads
#συμπεριλαμβάνω int main() ( #pragma omp parallel num_threads(5) ( std::cout<< "Hello World!\n"; } }

Αντί για το όρισμα num_threads, μπορείτε να χρησιμοποιήσετε μια εναλλακτική μέθοδο για να καθορίσετε τον αριθμό των νημάτων εκτέλεσης κώδικα. Εδώ ερχόμαστε στο πρώτο OpenMP API που ονομάζεται omp_set_num_threads. Αυτή η συνάρτηση ορίζεται στο αρχείο κεφαλίδας omp.h. Δεν χρειάζεται να χρησιμοποιήσετε επιπλέον βιβλιοθήκες για να εκτελέσετε τον κώδικα στη Λίστα 4, απλώς χρησιμοποιήστε την επιλογή -fopenmp.

Λίστα 4. Λεπτόκοκκος έλεγχος νημάτων με omp_set_num_threads
#συμπεριλαμβάνω #συμπεριλαμβάνω int main() ( omp_set_num_threads(5); #pragma omp parallel ( std::cout<< "Hello World!\n"; } }

Τέλος, οι μεταβλητές εξωτερικού περιβάλλοντος μπορούν να χρησιμοποιηθούν για τον έλεγχο της λειτουργίας του OpenMP. Μπορείτε να διορθώσετε τον κώδικα στη Λίστα 2 και απλώς να εκτυπώσετε τη φράση Γεια σου Κόσμο!έξι φορές ορίζοντας τη μεταβλητή OMP_NUM_THREADS σε 6, όπως φαίνεται στη Λίστα 5.

Λίστα 5. Μεταβλητές περιβάλλοντος για τη διαμόρφωση του OpenMP
tintin$ εξαγωγή OMP_NUM_THREADS=6 tintin$ ./a.out Hello World! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο! Γεια σου Κόσμο!

Εξετάσαμε τρεις πτυχές του OpenMP: πρακτικά μεταγλωττιστή, API χρόνου εκτέλεσης και μεταβλητές περιβάλλοντος. Τι συμβαίνει όταν χρησιμοποιείτε μεταβλητές περιβάλλοντος με API χρόνου εκτέλεσης; Τα API έχουν υψηλότερη προτεραιότητα.

Μελέτη περίπτωσης

Το OpenMP χρησιμοποιεί τεχνολογίες σιωπηρής παραλληλοποίησης και μπορεί να χρησιμοποιήσει πραγματικές λειτουργίες, ρητές συναρτήσεις και μεταβλητές περιβάλλοντος για να περάσει οδηγίες στον μεταγλωττιστή. Ας δούμε ένα παράδειγμα που δείχνει ξεκάθαρα τα οφέλη από τη χρήση του OpenMP. Ρίξτε μια ματιά στον κωδικό που εμφανίζεται στην Λίστα 6.

Λίστα 6. Διαδοχική επεξεργασία σε βρόχο for
int main() ( int a, b; // ... κωδικός προετοιμασίας για τους πίνακες a και b; int c; for (int i = 0; i< 1000000; ++i) c[i] = a[i] * b[i] + a * b; // ... выполняем некоторые действия с массивом c }

Προφανώς, ο βρόχος for μπορεί να παραλληλιστεί και να υποβληθεί σε επεξεργασία από πολλούς πυρήνες επεξεργαστή ταυτόχρονα, αφού ο υπολογισμός της τιμής οποιουδήποτε στοιχείου c[k] δεν εξαρτάται σε καμία περίπτωση από τα υπόλοιπα στοιχεία του πίνακα c. Η λίστα 7 δείχνει πώς μπορείτε να το κάνετε αυτό χρησιμοποιώντας το OpenMP.

Λίστα 7. Παράλληλη επεξεργασία σε βρόχο for χρησιμοποιώντας την παράλληλη για pragma
int main() ( int a, b; // ... κώδικας για την προετοιμασία των πινάκων a και b; int c; #pragma omp parallel for for (int i = 0; i< 1000000; ++i) c[i] = a[i] * b[i] + a * b; // ... выполняем некоторые действия с массивом c }

Το παράλληλο για το pragma βοηθά στην κατανομή του φόρτου εργασίας ενός βρόχου for σε πολλαπλά νήματα, καθένα από τα οποία μπορεί να υποβληθεί σε επεξεργασία από έναν ξεχωριστό πυρήνα επεξεργαστή. έτσι ο συνολικός χρόνος υπολογισμού μειώνεται σημαντικά. Αυτό επιβεβαιώνεται στη Λίστα 8.

Λίστα 8. Παράδειγμα χρησιμοποιώντας τη συνάρτηση API omp_get_wtime
#συμπεριλαμβάνω #συμπεριλαμβάνω #συμπεριλαμβάνω #συμπεριλαμβάνω int main(int argc, char *argv) ( int i, nthreads; clock_t clock_timer; double wall_timer; double c; for (nthreads = 1; nthreads<=8; ++nthreads) { clock_timer = clock(); wall_timer = omp_get_wtime(); #pragma omp parallel for private(i) num_threads(nthreads) for (i = 0; i < 1000000; i++) c[i] = sqrt(i * 4 + i * 2 + i); std::cout << "threads: " << nthreads << " time on clock(): " << (double) (clock() - clock_timer) / CLOCKS_PER_SEC << " time on wall: " << omp_get_wtime() - wall_timer << "\n"; } }

Στην Λίστα 8, μετράμε τον χρόνο εκτέλεσης του εσωτερικού βρόχου for ενώ αυξάνουμε τον αριθμό των νημάτων. Η συνάρτηση omp_get_wtime API επιστρέφει τον πραγματικό χρόνο που έχει παρέλθει (σε ​​δευτερόλεπτα) από την έναρξη ενός δεδομένου σημείου αναφοράς. Έτσι, το omp_get_wtime() - wall_timer επιστρέφει τον πραγματικό χρόνο εκτέλεσης του βρόχου for. Η κλήση συστήματος clock() χρησιμοποιείται για την εκτίμηση του χρόνου που αφιερώνει η CPU για να εκτελέσει ολόκληρο το πρόγραμμα, δηλαδή προσθέτουμε όλα αυτά τα χρονικά διαστήματα, λαμβάνοντας υπόψη τα νήματα, πριν πάρουμε το τελικό αποτέλεσμα. Στον υπολογιστή μου Intel Core i7, έλαβα τα αποτελέσματα που εμφανίζονται στη Λίστα 9.

Λίστα 9. Στατιστικά στοιχεία για τον εσωτερικό βρόχο for
Σπειρώματα: 1 ώρα στο ρολόι (): 0.015229 Χρόνος στον τοίχο: 0.0152249 Σπειρώματα: 2 Χρόνος στο ρολόι (): 0.014221 Χρόνος στον τοίχο: 0.00618792 Σπειρώματα: 3 Χρόνος στο ρολόι clock(): 0.014666 time on wall: 0.00440478 threads: 5 time on clock(): 0.01594 time on wall: 0.00359988 threads: 6 time on clock(): 0.015069 time on wall: 0.015069 time on wall: 0.608 365 time on wall: 0.00258303 threads: 8 time on clock(): 0.01678 time on wall: 0.00237703

Παρόλο που η ώρα της CPU (ώρα στο ρολόι) αποδείχθηκε ότι ήταν περίπου η ίδια σε όλες τις περιπτώσεις (όπως θα έπρεπε, χωρίς να λαμβάνεται υπόψη ο επιπλέον χρόνος που δαπανήθηκε για τη δημιουργία νημάτων και τον διακόπτη περιβάλλοντος), ο πραγματικός χρόνος που μας ενδιαφέρει (χρόνος στον τοίχο) μειώνονταν συνεχώς καθώς αυξάναμε τον αριθμό των νημάτων που υποτίθεται ότι εκτελούνταν παράλληλα από μεμονωμένους πυρήνες επεξεργαστή. Έτσι, μια τελευταία σημείωση σχετικά με τη σύνταξη του pragma: #pragma parallel για private(i) σημαίνει ότι η μεταβλητή βρόχου i αντιμετωπίζεται ως νήμα-τοπική μνήμη. κάθε νήμα περιέχει το δικό του αντίγραφο αυτής της μεταβλητής. Η τοπική μεταβλητή του νήματος δεν έχει αρχικοποιηθεί.

Κρίσιμες ενότητες κώδικα στο OpenMP

Φυσικά, καταλαβαίνετε ότι δεν μπορείτε να εμπιστευτείτε πλήρως το OpenMP για να χειρίζεται αυτόματα κρίσιμα τμήματα του κώδικα, σωστά; Φυσικά, δεν χρειάζεται να δημιουργήσετε ρητά αμοιβαίες εξαιρέσεις (mutexes), αλλά πρέπει να προσδιορίσετε τις κρίσιμες περιοχές. Η σύνταξη δίνεται στο ακόλουθο παράδειγμα:

#pragma omp kritik (προαιρετικό όνομα ενότητας) ( // 2 νήματα δεν μπορούν να εκτελέσουν αυτό το μπλοκ κώδικα ταυτόχρονα)

Ο κώδικας που ακολουθεί την κρίσιμη οδηγία pragma omp μπορεί να εκτελεστεί μόνο σε ένα νήμα τη φορά. Επιπλέον, το προαιρετικό όνομα ενότητας είναι ένα καθολικό αναγνωριστικό και οι κρίσιμες ενότητες με το ίδιο αναγνωριστικό δεν μπορούν να υποβληθούν σε επεξεργασία από δύο νήματα ταυτόχρονα. Ας δούμε τον κώδικα στη Λίστα 10.

Λίστα 10. Πολλαπλές κρίσιμες ενότητες με τα ίδια ονόματα
#pragma omp kritik (section1) ( myhashtable.insert("key1", "value1"); ) // ... περιέχει κάποιο άλλο κωδικό #pragma omp kritik (section1) ( myhashtable.insert("key2", "value2 ");

Εξετάζοντας τον κώδικα σε αυτήν την καταχώριση, μπορείτε να υποθέσετε ότι δύο εισαγωγές σε έναν πίνακα κατακερματισμού δεν θα συμβούν ποτέ ταυτόχρονα, επειδή τα ονόματα των κρίσιμων περιοχών είναι τα ίδια. Αυτό είναι λίγο διαφορετικό από αυτό που έχετε συνηθίσει όταν χειρίζεστε κρίσιμα τμήματα σε pthread, τα οποία τείνουν να χρησιμοποιούν πολύ κλείδωμα (που μπορεί να οδηγήσει σε περιττή πολυπλοκότητα).

Κλείδωμα και mutexe στο OpenMP

Είναι ενδιαφέρον ότι το OpenMP περιέχει τις δικές του εκδόσεις mutexes (εξάλλου, το OpenMP δεν είναι μόνο πρακτικά). Έτσι, ρίξτε μια ματιά στον τύπο omp_lock_t, που ορίζεται στο αρχείο κεφαλίδας omp.h. Οι κανονικές λειτουργίες mutex σε στυλ pthread αξιολογούνται ως true ακόμα και αν τα ονόματα των συναρτήσεων API είναι τα ίδια. Ακολουθούν πέντε λειτουργίες API για τις οποίες πρέπει να γνωρίζετε:

  • omp_init_lock: Αυτή η συνάρτηση API θα πρέπει να χρησιμοποιείται πρώτα κατά την πρόσβαση στον τύπο omp_lock_t και προορίζεται για προετοιμασία. Πρέπει να σημειωθεί ότι αμέσως μετά την αρχικοποίηση η κλειδαριά θα βρίσκεται στην αρχική (unset) κατάσταση.
  • omp_destroy_lock: Καταστρέφει την κλειδαριά. Όταν καλείται αυτή η συνάρτηση API, το κλείδωμα πρέπει να βρίσκεται στην αρχική του κατάσταση. Αυτό σημαίνει ότι δεν μπορείτε να καλέσετε το omp_set_lock και στη συνέχεια να καταστρέψετε το κλείδωμα.
  • omp_set_lock: ορίζει το omp_lock_t, δηλαδή ενεργοποιεί το mutex. Εάν ένα νήμα δεν μπορεί να αποκτήσει κλειδαριά, συνεχίζει να περιμένει μέχρι να γίνει διαθέσιμη η ευκαιρία.
  • omp_test_lock: επιχειρεί να αποκτήσει κλειδαριά εάν είναι διαθέσιμη. επιστρέφει 1 στην επιτυχία και 0 στην αποτυχία. Αυτή η λειτουργία είναι μη μπλοκάρισμα, δηλαδή δεν αναγκάζει το νήμα να περιμένει την απόκτηση της κλειδαριάς.
  • omp_unset_lock: Επαναφέρει την κλειδαριά στην αρχική της κατάσταση.

Η λίστα 11 περιέχει μια απλή υλοποίηση της παλαιού τύπου ουρά μονού νήματος, βελτιωμένη για να χειρίζεται πολλαπλά νήματα χρησιμοποιώντας κλειδαριές OpenMP. Λάβετε υπόψη ότι αυτό το παράδειγμα δεν είναι η καλύτερη περίπτωση γενικής χρήσης και παρέχεται απλώς για να επιδείξει τις δυνατότητες.

Λίστα 11. Βελτίωση μιας ουράς μονού νήματος με το OpenMP
#συμπεριλαμβάνω #include "myqueue.h" class omp_q: public myqueue (δημόσιο: typedef myqueue βάση; omp_q() ( omp_init_lock(&lock); ) ~omp_q() ( omp_destroy_lock(&lock); ) bool push(const int& value) (omp_set_lock(&lock); bool αποτέλεσμα = this->base::push(value); omp_unset_lock( &lock return ) bool trypush(const int& value) (Bool result = omp_test_lock(&lock); if (result) ( result = αποτέλεσμα && this->base::push(value); omp_unset_lock(&lock); ) επιστρέφει το αποτέλεσμα ) //ομοίως για το pop private: omp_lock_t lock; )

Φωλιασμένες κλειδαριές

Άλλοι τύποι κλειδαριών στο OpenMP είναι οι διάφορες κλειδαριές omp_nest_lock_t. Είναι παρόμοια με το omp_lock_t αλλά έχουν το πρόσθετο πλεονέκτημα ότι το κλείδωμα μπορεί να αποκτηθεί πολλές φορές από το νήμα που το κρατά. Κάθε φορά που αποκτάται ένα ένθετο κλείδωμα από το νήμα που το κρατά χρησιμοποιώντας το omp_set_nest_lock, ο μετρητής εσωτερικού κλειδώματος αυξάνεται. Ένα κλείδωμα απελευθερώνεται από το νήμα αναμονής όταν μία ή περισσότερες κλήσεις στο omp_unset_nest_lock μειώνουν τον μετρητή εσωτερικού κλειδώματος στο 0. Οι ακόλουθες συναρτήσεις API χρησιμοποιούνται για εργασία με το omp_nest_lock_t:

  • omp_init_nest_lock(omp_nest_lock_t*): Ορίζει τον εσωτερικό μετρητή ένθεσης στο 0.
  • omp_destroy_nest_lock(omp_nest_lock_t*): Καταστρέφει την κλειδαριά. Η κλήση αυτής της συνάρτησης API για μια κλειδαριά με τιμή μετρητή διαφορετική από το μηδέν παράγει απρόβλεπτα αποτελέσματα.
  • omp_set_nest_lock(omp_nest_lock_t*): Παρόμοιο με το omp_set_lock, εκτός από το ότι το νήμα διατήρησης μπορεί να το καλέσει πολλές φορές.
  • omp_test_nest_lock(omp_nest_lock_t*): είναι μια μη αποκλειστική έκδοση της συνάρτησης API omp_set_nest_lock.
  • omp_unset_nest_lock(omp_nest_lock_t*): Απελευθερώνει την κλειδαριά όταν ο μετρητής εσωτερικής κλειδαριάς φτάσει στο 0. Σε άλλες περιπτώσεις, κάθε κλήση σε αυτήν τη συνάρτηση API μειώνει την τιμή του μετρητή.

Λεπτομερής έλεγχος ολοκλήρωσης εργασιών

Έχουμε ήδη δει ότι το μπλοκ κώδικα που ακολουθεί την παράλληλη οδηγία pragma omp επεξεργάζεται παράλληλα από όλα τα νήματα. Ο κώδικας μέσα σε αυτά τα μπλοκ μπορεί επίσης να χωριστεί σε κατηγορίες που θα εκτελεστούν σε καθορισμένα νήματα. Ας δούμε τον κωδικό στη Λίστα 12.

Λίστα 12. Χρησιμοποιώντας τις παράλληλες ενότητες pragma
int main() ( #pragma omp parallel ( cout<< "Это выполняется во всех потоках\n"; #pragma omp sections { #pragma omp section { cout << "Это выполняется параллельно\n"; } #pragma omp section { cout << "Последовательный оператор 1\n"; cout << "Это всегда выполняется после оператора 1\n"; } #pragma omp section { cout << "Это тоже выполняется параллельно\n"; } } } }

Ο κώδικας που προηγείται της οδηγίας pragma omp sections και αμέσως μετά την οδηγία pragma omp parallel επεξεργάζεται παράλληλα από όλα τα νήματα. Χρησιμοποιώντας την οδηγία pragma omp sections, ο κώδικας που ακολουθεί χωρίζεται σε ξεχωριστές υποενότητες. Κάθε μπλοκ τμήματος pragma omp μπορεί να εκτελεστεί από ένα ξεχωριστό νήμα. Ωστόσο, μεμονωμένες δηλώσεις σε ένα μπλοκ ενότητας εκτελούνται πάντα διαδοχικά. Η λίστα 13 δείχνει τα αποτελέσματα της εκτέλεσης του κώδικα στη Λίστα 12.

Λίστα 13. Αποτελέσματα εκτέλεσης του κώδικα από τη Λίστα 12
tintin$ ./a.out Αυτό τρέχει σε όλα τα νήματα Αυτό εκτελείται σε όλα τα νήματα Αυτό εκτελείται σε όλα τα νήματα Αυτό εκτελείται σε όλα τα νήματα Αυτό τρέχει σε όλα τα νήματα Αυτό εκτελείται σε όλα τα νήματα Αυτό εκτελείται παράλληλα Διαδοχική δήλωση 1 Αυτό εκτελείται επίσης παράλληλα Αυτό εκτελείται πάντα μετά την πρόταση 1

Στη Λίστα 13, βλέπουμε ξανά τα 8 νήματα που δημιουργήθηκαν αρχικά. Τρία από τα οκτώ νήματα επαρκούν για την επεξεργασία του μπλοκ τμημάτων pragma omp. Μέσα στη δεύτερη ενότητα, καθορίσαμε τη σειρά με την οποία εκτελούνται οι εντολές εξόδου κειμένου. Αυτό είναι το νόημα της χρήσης των ενοτήτων pragma. Εάν είναι απαραίτητο, μπορείτε να καθορίσετε τη σειρά με την οποία εκτελούνται τα μπλοκ κώδικα.

Πρώτη ιδιωτική και τελευταία ιδιωτική οδηγία σε συνδυασμό με παράλληλους βρόχους

Σε αυτό το άρθρο, έδειξα ήδη πώς να δηλώνω τοπική μνήμη νήματος χρησιμοποιώντας την ιδιωτική οδηγία. Αλλά πώς να αρχικοποιήσετε τις τοπικές μεταβλητές του νήματος; Μήπως να τα συγχρονίσετε με την τιμή της μεταβλητής του κύριου νήματος πριν προχωρήσετε περαιτέρω; Σε τέτοιες περιπτώσεις, η πρώτη ιδιωτική οδηγία είναι χρήσιμη.

πρώτη ιδιωτική οδηγία

Χρησιμοποιώντας την οδηγία firstprivate(variable), μπορείτε να αρχικοποιήσετε μια μεταβλητή σε ένα νήμα σε οποιαδήποτε τιμή είχε στο κύριο νήμα. Ας δούμε τον κωδικό από τη Λίστα 14.

Λίστα 14. Χρήση μιας τοπικής μεταβλητής νήματος που δεν είναι συγχρονισμένη με το κύριο νήμα
#συμπεριλαμβάνω #συμπεριλαμβάνω int main() ( int idx = 100; #pragma omp parallel private(idx) ( printf("Στο νήμα %d idx = %d\n", omp_get_thread_num(), idx); ) )

Να τι έλαβα ως αποτέλεσμα (τα αποτελέσματά σας μπορεί να διαφέρουν).

Σε ροή 1 idx = 1 Σε ροή 5 idx = 1 Σε ροή 6 idx = 1 σε ροή 0 idx = 0 Σε ροή 4 idx = 1 σε ροή 7 idx = 1 σε ροή 2 idx = 1 σε ροή 3 idx = 1

Η λίστα 15 περιέχει κώδικα που χρησιμοποιεί την πρώτη ιδιωτική οδηγία. Όπως αναμενόταν, η έξοδος δείχνει ότι η μεταβλητή idx έχει οριστεί στο 100 σε όλα τα νήματα.

Λίστα 15. Χρήση της πρώτης ιδιωτικής οδηγίας για την προετοιμασία των τοπικών μεταβλητών νήματος
#συμπεριλαμβάνω #συμπεριλαμβάνω int main() ( int idx = 100; #pragma omp parallel firstprivate(idx) ( printf("Σε νήμα %d idx = %d\n", omp_get_thread_num(), idx); ) )

Σημειώστε επίσης ότι η μέθοδος omp_get_thread_num() χρησιμοποιήθηκε για πρόσβαση στο αναγνωριστικό νήματος. Αυτό το αναγνωριστικό είναι διαφορετικό από την έξοδο του αναγνωριστικού από την κορυφαία εντολή του λειτουργικού συστήματος Linux® και αυτό το σχήμα είναι απλώς ένας τρόπος για το OpenMP να παρακολουθεί τον αριθμό των νημάτων. Εάν σκοπεύετε να χρησιμοποιήσετε την πρώτη ιδιωτική οδηγία στον κώδικα C++, τότε σημειώστε ένα άλλο χαρακτηριστικό: η μεταβλητή που χρησιμοποιείται από την πρώτη ιδιωτική οδηγία είναι ένας κατασκευαστής αντιγραφής για να αρχικοποιηθεί από μια μεταβλητή κύριου νήματος, οπότε αν ο κατασκευαστής αντιγραφής είναι ιδιωτικός για την κλάση σας, αυτό θα μπορούσε οδηγήσει σε δυσάρεστες συνέπειες. Ας περάσουμε τώρα στην τελευταία οδηγία για την ιδιωτική, η οποία από πολλές απόψεις είναι η άλλη όψη του νομίσματος.

τελευταία ιδιωτική οδηγία

Αντί να συγχρονίσουμε την τοπική μεταβλητή νήματος με τα δεδομένα του κύριου νήματος, θα συγχρονίσουμε τη μεταβλητή του κύριου νήματος με τα δεδομένα που θα ληφθούν ως αποτέλεσμα του τελευταίου βρόχου. Η λίστα 16 εκτελεί έναν παράλληλο βρόχο για.

Λίστα 16. Παράλληλος βρόχος χωρίς συγχρονισμό δεδομένων με το κύριο νήμα
#συμπεριλαμβάνω #συμπεριλαμβάνω int main() ( int idx = 100; int main_var = 2120; #pragma omp parallel για private(idx) για (idx = 0; idx< 12; ++idx) { main_var = idx * idx; printf("В потоке %d idx = %d main_var = %d\n", omp_get_thread_num(), idx, main_var); } printf("Возврат в главный поток со значением переменной main_var = %d\n", main_var); }

Στο μηχάνημα 8 πυρήνων μου, το OpenMP δημιουργεί έξι νήματα για το παράλληλο για μπλοκ. Κάθε νήμα, με τη σειρά του, έχει δύο επαναλήψεις στον βρόχο. Η τελική τιμή της μεταβλητής main_var εξαρτάται από το νήμα που εκτελέστηκε τελευταίο και, επομένως, από την τιμή της μεταβλητής idx σε αυτό το νήμα. Με άλλα λόγια, η τιμή της μεταβλητής main_var δεν εξαρτάται από την τελευταία τιμή της μεταβλητής idx, αλλά εξαρτάται από την τιμή που περιείχε η μεταβλητή idx στο νήμα που εκτελέστηκε τελευταία. Αυτό το παράδειγμα παρουσιάζεται στην Λίστα 17.

Λίστα 17. Εξάρτηση της τιμής της μεταβλητής main_var από το τελευταίο νήμα που εκτελέστηκε
Στο νήμα 4 idx = 8 main_var = 64 Στο νήμα 2 idx = 4 main_var = 16 Στο νήμα 5 idx = 10 main_var = 100 στο νήμα 3 idx = 6 main_var = 36 στο νήμα 0 idx = 0 inxvar 2 main_var = 4 Σε νήμα 4 idx = 9 main_var = 81 Σε νήμα 2 idx = 5 main_var = 25 Σε νήμα 5 idx = 11 main_var = 121 Σε νήμα 3 idx = 7 main_var = 49 σε νήμα idx1 = 0 νήμα 1 idx = 3 main_var = 9 Επιστροφή στο κύριο νήμα με την τιμή της μεταβλητής main_var = 9

Εκτελέστε τον κώδικα στη Λίστα 17 πολλές φορές για να βεβαιωθείτε ότι η τιμή του main_var στο κύριο νήμα εξαρτάται πάντα από την τιμή του idx στο τελευταίο νήμα που εκτελέστηκε. Τι γίνεται αν χρειαστεί να συγχρονίσετε την τιμή της μεταβλητής του κύριου νήματος με την τελική τιμή της μεταβλητής idx σε έναν βρόχο; Εδώ είναι χρήσιμη η τελευταία ιδιωτική οδηγία, όπως φαίνεται στην Λίστα 18. Όπως και στο προηγούμενο παράδειγμα, εκτελέστε τον κώδικα στη Λίστα 18 μερικές φορές και θα δείτε ότι η τελική τιμή της μεταβλητής main_var στο κύριο νήμα είναι 121 (δηλαδή η τιμή της μεταβλητής idx στην τελευταία επανάληψη του βρόχου).

Λίστα 18. Συγχρονισμός με χρήση της τελευταίας ιδιωτικής οδηγίας
#συμπεριλαμβάνω #συμπεριλαμβάνω int main() ( int idx = 100; int main_var = 2120; #pragma omp parallel για private(idx) lastprivate(main_var) for (idx = 0; idx< 12; ++idx) { main_var = idx * idx; printf("В потоке %d idx = %d main_var = %d\n", omp_get_thread_num(), idx, main_var); } printf("Возврат в главный поток со значением переменной main_var = %d\n", main_var); }

Η λίστα 19 δείχνει τα αποτελέσματα της εκτέλεσης του κώδικα στη Λίστα 18.

Λίστα 19. Αποτελέσματα εκτέλεσης του κώδικα από τη Λίστα 18 (σημειώστε ότι το main_var είναι πάντα ρυθμισμένο στο 121 στο κύριο νήμα)
Στο νήμα 3 idx = 6 main_var = 36 Στο νήμα 2 idx = 4 main_var = 16 Στο νήμα 1 idx = 2 main_var = 4 Στο νήμα 4 idx = 8 main_var = 64 στο νήμα 5 idx = 10 main_var = 1100_var = 7 main_var = 49 Σε νήμα 0 idx = 0 main_var = 0 Σε νήμα 2 idx = 5 main_var = 25 Σε νήμα 1 idx = 3 main_var = 9 Στο νήμα 4 idx = 9 main_var = 81 σε νήμα 5 idx = 5 idx = 3 main_var = 9 νήμα 0 idx = 1 main_var = 1 Επιστροφή στο κύριο νήμα με την τιμή της μεταβλητής main_var = 121

Μια τελευταία σημείωση: για να υποστηρίξετε τον τελευταίο ιδιωτικό τελεστή σε ένα αντικείμενο C++, η αντίστοιχη κλάση πρέπει να περιέχει μια διαθέσιμη μέθοδο public operator=.

Συγχώνευση ταξινόμησης στο OpenMP

Ας δούμε ένα πραγματικό παράδειγμα στο οποίο το OpenMP μειώνει τον χρόνο εκτέλεσης μιας εργασίας. Ας πάρουμε μια όχι πολύ βελτιστοποιημένη έκδοση της διαδικασίας ταξινόμησης συγχώνευσης, αρκετή για να δείξουμε τα οφέλη από τη χρήση του OpenMP. Αυτό το παράδειγμα εμφανίζεται στη Λίστα 20.

Καταχώριση 20. Συγχώνευση ταξινόμησης στο OpenMP
#συμπεριλαμβάνω #συμπεριλαμβάνω #συμπεριλαμβάνω χρησιμοποιώντας namespace std? διάνυσμα συγχώνευση (διάνυσμα const & αριστερά, διάνυσμα const & δεξιά) (διάνυσμα αποτέλεσμα; ανυπόγραφο left_it = 0, right_it = 0;< left.size() && right_it < right.size()) { if(left < right) { result.push_back(left); left_it++; } else { result.push_back(right); right_it++; } } // Занесение оставшихся данных из обоих векторов в результирующий while(left_it < left.size()) { result.push_back(left); left_it++; } while(right_it < right.size()) { result.push_back(right); right_it++; } return result; } vectorενώ (left_it συγχώνευση (διάνυσμα & vec, νήματα int) ( // Συνθήκη τερματισμού: η λίστα είναι πλήρως ταξινομημένη εάν // περιέχει μόνο ένα στοιχείο. if(vec.size() == 1) ( return vec; ) // Προσδιορίστε τη θέση της μέσης στοιχείο στο διάνυσμα std ::vector ::iterator middle = vec.begin() + (vec.size() / 2); διάνυσμα left(vec.begin(), middle);<1000000; ++i) v[i] = (i * i) % 1000000; v = mergesort(v, 1); for (long i=0; i<1000000; ++i) cout << v[i] << "\n"; }

διάνυσμα

right(middle, vec.end());

// Εκτελέστε ταξινόμηση συγχώνευσης σε δύο μικρότερα διανύσματα εάν (νήματα > 1) ( #pragma omp παράλληλες ενότητες ( #pragma omp section ( αριστερά = συγχώνευση (αριστερά, νήματα/2); ) #pragma omp τμήμα ( δεξιά = συγχώνευση(δεξιά, νήματα - νήματα/2) ) else (αριστερά = συγχώνευση(αριστερά, 1); δεξιά = συγχώνευση(δεξιά, 1); ) επιστροφή συγχώνευση(αριστερά, δεξιά); ) int main() ( διάνυσμα

Το OpenMP διευκολύνει τη δημιουργία παράλληλων προγραμμάτων για συστήματα κοινής μνήμης (πολυπύρηνων και πολλαπλών επεξεργαστών). Σε αυτό το άρθρο θα μιλήσω για το πώς να ενεργοποιήσετε το OpenMP στο πιο κοινό περιβάλλον προγραμματισμού - Visual Studio. Σύμφωνα με την επίσημη έκδοση της Microsoft, το OpenMP υποστηρίζεται μόνο στις Professional εκδόσεις του περιβάλλοντος ανάπτυξης του Visual Studio 2005/2008/2010. Ωστόσο, το δωρεάν Visual Studio Express έχει τον ίδιο μεταγλωττιστή με την έκδοση Professional. Επομένως, μετά από μια μικρή τροποποίηση με ένα αρχείο, παράλληλα προγράμματα OpenMP θα μεταγλωττιστούν και, το πιο σημαντικό, θα λειτουργήσουν ακόμη και στο Visual Studio Express.

Το OpenMP από τη Microsoft υλοποιείται μέσω των παρακάτω στοιχείων:

  1. Ο μεταγλωττιστής C++ περιλαμβάνεται στο Visual Studio.
  2. αρχείο κεφαλίδας omp.h;
  3. βιβλιοθήκες μεταγλώττισης σταδίου: vcomp.lib και vcompd.lib (το τελευταίο χρησιμοποιείται για εντοπισμό σφαλμάτων).
  4. Βιβλιοθήκες χρόνου εκτέλεσης: vcomp90.dll και vcomp90d.dll. Οι αριθμοί στο όνομα ενδέχεται να διαφέρουν: στο Visual Studio 2005, αντί για 90, οι αριθμοί είναι 80.

Το δωρεάν Visual Studio Express δεν περιέχει τις αναγραφόμενες βιβλιοθήκες.

OpenMP και Visual Studio Express

Εάν θέλετε να δημιουργήσετε παράλληλα προγράμματα OpenMP στα Windows, τότε ο πιο βολικός τρόπος είναι να χρησιμοποιήσετε το Visual Studio 2005/2008/2010 Professional. Να σας υπενθυμίσω ότι είναι δωρεάν για προπτυχιακούς και μεταπτυχιακούς φοιτητές. Εναλλακτικά, μπορείτε να αγοράσετε το Visual Studio Professional για $600 (φυσικά, υπάρχει και μια τρίτη επιλογή, αλλά δεν θα το συζητήσουμε).

Εάν έχετε εγκατεστημένη την Professional έκδοση στον υπολογιστή σας, προχωρήστε στην επόμενη ενότητα του άρθρου. Σε αυτήν την ενότητα, θα εξετάσουμε την περίπτωση που για κάποιο λόγο αναγκαστείτε να χρησιμοποιήσετε το Visual Studio Express.

Το Visual Studio Express είναι μια δωρεάν, απογυμνωμένη έκδοση του Visual Studio. Θα μας ενδιαφέρει η έκδοση του 2008. Μπορείτε να το κατεβάσετε από εδώ: http://www.microsoft.com/exPress/download/. Φυσικά, θα προγραμματίσουμε σε C++, οπότε επιλέξτε Visual C++ 2008.

Το πρόγραμμα εγκατάστασης θα πραγματοποιήσει λήψη δεδομένων από το Διαδίκτυο (περίπου 100 megabyte), ώστε να μπορείτε να εξοικονομήσετε κάποιο εύρος ζώνης απενεργοποιώντας την εγκατάσταση του Microsoft Silverlight και του Microsoft SQL Server, εάν δεν τα χρειάζεστε.

Μετά την εγκατάσταση του Visual Studio Express, θα χρειαστεί να προσθέσουμε στοιχεία OpenMP σε αυτό. Ο νόμιμος, δωρεάν τρόπος για να το κάνετε αυτό είναι να εγκαταστήσετε το Windows SDK για Windows Server 2008 και .NET Framework 3.5. Το Visual Studio θα ενημερωθεί κατά την εγκατάσταση αυτού του πακέτου λογισμικού. Η διαδικασία ενημέρωσης δεν εξετάζει ποια έκδοση του Visual Studio έχετε εγκαταστήσει (Express ή Professional), επομένως κατά την εγκατάσταση θα προσθέσει "κατά λάθος" στοιχεία που λείπουν.

Δεδομένου ότι εγκαθιστούμε το Windows SDK μόνο για χάρη του OpenMP, δεν χρειαζόμαστε τα gigabyte τεκμηρίωσης που συνοδεύει το κιτ. Συνιστώ να αφήσετε μόνο τα ακόλουθα στοιχεία:

Εικόνα 1. Στοιχεία SDK που χρειαζόμαστε

Δυστυχώς, το SDK δεν περιλαμβάνει τη βιβλιοθήκη vcomp90d.dll, επομένως προς το παρόν μπορείτε να εκτελέσετε μόνο προγράμματα OpenMP που έχουν μεταγλωττιστεί σε λειτουργία έκδοσης στο Visual Studio Express. Βρήκα έναν τρόπο να ξεπεράσω αυτόν τον περιορισμό, διαβάστε σχετικά περαιτέρω (ενότητα "Εντοπισμός σφαλμάτων ενός προγράμματος OpenMP στο Visual Studio Express").

Χρήση του OpenMP στο Visual Studio

Αφού ολοκληρώσετε τα βήματα στην προηγούμενη ενότητα, δεν έχει σημασία ποια έκδοση του Visual Studio χρησιμοποιείτε. Θα σας δείξω βήμα προς βήμα πώς να δημιουργήσετε ένα έργο με υποστήριξη OpenMP σε αυτό το περιβάλλον ανάπτυξης. Πρώτα απ 'όλα, πρέπει να εκκινήσετε το Visual Studio και να επιλέξετε Αρχείο→ Νέο→ Έργο... Θα εμφανιστεί το παράθυρο δημιουργίας έργου. Επιλέξτε τον τύπο έργου "Win32", το πρότυπο - "Εφαρμογή Win32 Console". Εισαγάγετε ένα ουσιαστικό όνομα έργου, επιλέξτε έναν φάκελο για να αποθηκεύσετε το έργο, καταργήστε την επιλογή "Δημιουργία καταλόγου για λύση":

Εικόνα 2. Παράθυρο δημιουργίας έργου

Κάντε κλικ στο κουμπί "OK", θα εμφανιστεί ένα παράθυρο για τη ρύθμιση του μελλοντικού έργου. Επιλέξτε την καρτέλα "Ρυθμίσεις εφαρμογής" και ενεργοποιήστε το πλαίσιο ελέγχου "Κενό έργο":

Εικόνα 3. Παράθυρο ρυθμίσεων μελλοντικού έργου

Κάνοντας κλικ στο κουμπί «Τέλος», θα δημιουργηθεί το έργο. Δεν θα υπάρχουν ορατές αλλαγές στο κύριο παράθυρο του Visual Studio. Μόνο το όνομα του έργου στον τίτλο του παραθύρου μας λέει ότι εργαζόμαστε με ένα έργο.

Τώρα κάντε κλικ στο Project→ Add New Item, θα εμφανιστεί ένα παράθυρο για την προσθήκη στοιχείων στο έργο. Προσθέστε το αρχείο .cpp στο έργο:

Εικόνα 4. Παράθυρο για την προσθήκη στοιχείων στο έργο

Μετά από αυτό, θα σας δοθεί ένα παράθυρο για να εισαγάγετε τον πηγαίο κώδικα του προγράμματος. Θα εκτελέσουμε δοκιμές στον ακόλουθο κώδικα, ο οποίος ελέγχει διάφορες πτυχές της λειτουργίας του OpenMP:

#συμπεριλαμβάνω #συμπεριλαμβάνω χρησιμοποιώντας namespace std? int main(int argc, char **argv) ( int test(999); omp_set_num_threads(2); #pragma omp parallel reduce(+:test) ( #pragma omp kritik cout<< "test = " << test << endl; } return EXIT_SUCCESS; } Листинг 1. Простейшая программа, использующая OpenMP

Ξεκινήστε το πρόγραμμα κάνοντας κλικ στο Debug→Start Without Debugging. Εάν όλα έγιναν σωστά, το πρόγραμμα θα μεταγλωττιστεί (αν σας ρωτήσει αν θα κάνετε μεταγλώττιση, κάντε κλικ στο "Ναι"), τότε θα εκτελεστεί και θα εκτυπωθεί δοκιμή = 999:

Εικόνα 5. Αποτέλεσμα του προγράμματος από τη Λίστα 1

«Πώς έτσι;! - λέτε, "Τελικά, το πρόγραμμα πρέπει να έχει έξοδο μηδέν και δύο φορές!" Το γεγονός είναι ότι το OpenMP δεν είναι ακόμη ενεργοποιημένο και επομένως οι αντίστοιχες οδηγίες αγνοήθηκαν από τον μεταγλωττιστή.

Για να ενεργοποιήσετε το OpenMP, κάντε κλικ στο Project→ OMP Properties (το OMP είναι το όνομα του έργου από τα παραδείγματά μου). Στο επάνω αριστερό μέρος του παραθύρου που εμφανίζεται, επιλέξτε «Όλες οι διαμορφώσεις» και στην ενότητα Ιδιότητες διαμόρφωσης→C/C++→Γλώσσα, ενεργοποιήστε την «Υποστήριξη OpenMP»:

Εικόνα 6. Ενεργοποιήστε το OpenMP στις ιδιότητες του έργου

Μετά από αυτό, εκτελέστε ξανά το πρόγραμμα κάνοντας κλικ στο Debug→Start Without Debugging. Αυτή τη φορά το πρόγραμμα θα εκτυπώσει δοκιμή = 0 δύο φορές:

Εικόνα 7. Το αποτέλεσμα της εκτέλεσης του προγράμματος από τη Λίστα 1 με ενεργοποιημένο το OpenMP

Ζήτω! Το OpenMP λειτουργεί.

Σημείωμα.Εάν χρησιμοποιείτε το Visual Studio Express, επιλέξτε την τρέχουσα διαμόρφωση "Release", διαφορετικά δεν θα λειτουργήσει (διαβάστε περαιτέρω):

Εικόνα 8. Επιλογή της τρέχουσας διαμόρφωσης

Όπως αναφέρθηκε προηγουμένως, ακόμη και μετά την εγκατάσταση του SDK των Windows, δεν θα έχουμε τη βιβλιοθήκη vcomp90d.dll που είναι απαραίτητη για τον εντοπισμό σφαλμάτων, επομένως δεν μπορούμε ακόμη να κάνουμε εντοπισμό σφαλμάτων του προγράμματος OpenMP στο Visual Studio Express. Η απλή αντιγραφή της υπάρχουσας βιβλιοθήκης vcomp90.dll και η μετονομασία της σε vcomp90d.dll δεν θα λειτουργήσει, επειδή το άθροισμα ελέγχου και η έκδοση που καθορίζονται στο μανιφέστο που είναι ενσωματωμένο στο αρχείο exe δεν ταιριάζουν. Επομένως, θα «σκάψουμε» από την αντίθετη πλευρά.

Όταν μεταγλωττίζεται στη ρύθμιση παραμέτρων εντοπισμού σφαλμάτων, το αρχείο κεφαλίδας omp.h απαιτεί τη βιβλιοθήκη vcompd.lib (την έχουμε), η οποία με τη σειρά της απαιτεί vcomp90d.dll (λείπει). Η άδεια χρήσης δεν μας επιτρέπει να χρησιμοποιούμε τροποποιημένα αρχεία κεφαλίδας από τη Microsoft σε εφαρμογές, επομένως αντί να τροποποιήσουμε το omp.h, θα το συμπεριλάβουμε στο πρόγραμμά μας ως εξής, ώστε να μην μαντέψει ότι είναι ενεργοποιημένη η λειτουργία εντοπισμού σφαλμάτων:

#συμπεριλαμβάνω #ifdef _DEBUG #undef _DEBUG #include #define_DEBUG #else #include #endif χρησιμοποιώντας χώρο ονομάτων std; int main(int argc, char **argv) ( int test(999); omp_set_num_threads(2); #pragma omp parallel reduce(+:test) ( #pragma omp kritik cout<< "test = " << test << endl; } return EXIT_SUCCESS; } Листинг 2. Включаем omp.h «хитрым» способом

Η παραπάνω ενέργεια δεν αρκεί για να λειτουργήσουν όλα (μέχρι στιγμής έχουμε διορθώσει μόνο το μανιφέστο που είναι ενσωματωμένο στο πρόγραμμα). Το γεγονός είναι ότι το Visual Studio σε λειτουργία εντοπισμού σφαλμάτων εξακολουθεί να συνδέει αυτόματα το vcompd.lib (λόγω της ενεργοποίησης του OpenMP), το οποίο απαιτεί vcomp90d.dll. Για να το διορθώσετε, μεταβείτε ξανά στις ρυθμίσεις του έργου (Project→OMP Properties), αυτή τη φορά επιλέξτε Configuration: "Debug". Στην ενότητα Configuration Properties→Linker→Input, καθορίστε ότι το vcompd.lib δεν χρειάζεται να συνδεθεί, αλλά το vcompd.lib χρειάζεται:

Εικόνα 9. Αντικαταστήστε τη βιβλιοθήκη στις ιδιότητες του έργου

Ας ελέγξουμε τώρα εάν ο εντοπισμός σφαλμάτων λειτουργεί και εάν το πρόγραμμα εκτελείται όντως παράλληλα. Τοποθετήστε ένα σημείο διακοπής στη γραμμή που εμφανίζει την τιμή της μεταβλητής. Για να το κάνετε αυτό, κάντε αριστερό κλικ στη γκρι γραμμή στα αριστερά του πηγαίου κώδικα:

Εικόνα 10. Σημείο διακοπής

Μετά από αυτό, εκτελέστε το πρόγραμμα σε λειτουργία εντοπισμού σφαλμάτων: Εντοπισμός σφαλμάτων→ Έναρξη εντοπισμού σφαλμάτων (μην ξεχάσετε να επιστρέψετε την τρέχουσα διαμόρφωση "Εντοπισμός σφαλμάτων", βλέπε Εικόνα 8). Το πρόγραμμα θα ξεκινήσει και θα σταματήσει αμέσως στο σημείο διακοπής. Στην καρτέλα "Νήματα" βλέπουμε ότι το πρόγραμμα εκτελείται πραγματικά χρησιμοποιώντας δύο νήματα:

Εικόνα 11. Εντοπισμός σφαλμάτων ενός προγράμματος OpenMP στο Visual Studio Express

Το OpenMP είναι ένα πρότυπο διεπαφής προγραμματισμού εφαρμογών για συστήματα παράλληλης κοινής μνήμης. Υποστηρίζει γλώσσες C, C++, Fortran.

Μοντέλο προγράμματος OpenMP

Μοντέλο παράλληλου προγράμματος σε OpenMPμπορεί να διατυπωθεί ως εξής:

  • Το πρόγραμμα αποτελείται από σειριακές και παράλληλες ενότητες (Εικ. 2.1).
  • Την αρχική χρονική στιγμή, δημιουργείται ένα κύριο νήμα που εκτελεί διαδοχικές ενότητες του προγράμματος.
  • Κατά την είσοδο σε παράλληλο τμήμα, εκτελείται μια λειτουργία πιρούνι, δημιουργώντας μια οικογένεια νημάτων. Κάθε νήμα έχει το δικό του μοναδικό αριθμητικό αναγνωριστικό (το κύριο νήμα είναι 0). Όταν οι βρόχοι παραλληλίζονται, όλα τα παράλληλα νήματα εκτελούν τον ίδιο κώδικα. Γενικά, τα νήματα μπορούν να εκτελέσουν διαφορετικά κομμάτια κώδικα.
  • Κατά την έξοδο από το παράλληλο τμήμα, εκτελείται η λειτουργία ενώνω.Η εκτέλεση όλων των νημάτων εκτός από το κύριο τελειώνει.

OpenMPαποτελείται από τα ακόλουθα στοιχεία:

  • Οδηγίες μεταγλωττιστή- χρησιμοποιείται για τη δημιουργία νημάτων, τη διανομή εργασίας μεταξύ των νημάτων και τον συγχρονισμό τους. Οδηγίες περιλαμβάνονται στον πηγαίο κώδικα του προγράμματος.
  • Ρουτίνες βιβλιοθήκης χρόνου εκτέλεσης- χρησιμοποιείται για τον ορισμό και τον καθορισμό χαρακτηριστικών ροής. Οι κλήσεις σε αυτές τις ρουτίνες περιλαμβάνονται στον πηγαίο κώδικα του προγράμματος.
  • Μεταβλητές Περιβάλλοντος- χρησιμοποιείται για τον έλεγχο της συμπεριφοράς ενός παράλληλου προγράμματος. Οι μεταβλητές περιβάλλοντος ορίζονται για το περιβάλλον εκτέλεσης του παράλληλου προγράμματος με κατάλληλες εντολές (για παράδειγμα, εντολές φλοιού σε λειτουργικά συστήματα UNIX).

Η χρήση οδηγιών μεταγλωττιστή και ρουτίνες βιβλιοθήκης χρόνου εκτέλεσης υπόκειται σε κανόνες που διαφέρουν μεταξύ των γλωσσών προγραμματισμού. Το σύνολο τέτοιων κανόνων ονομάζεται γλωσσικά.

Fortran δεσμευτική γλώσσα

Σε προγράμματα Fortran οδηγίες μεταγλωττιστή, τα ονόματα των υπορουτινών και των μεταβλητών περιβάλλοντος ξεκινούν με OMP. Σχήμα και διάταξις βιβλίου οδηγίες μεταγλωττιστή:

(!|C|*)$OMP οδηγία [operator_1[, operator_2, ...]]

Η οδηγία ξεκινά από την πρώτη (σταθερή μορφή κειμένου Fortran 77) ή αυθαίρετη (ελεύθερη μορφή) θέση γραμμής. Είναι δυνατή η συνέχιση της οδηγίας στην επόμενη γραμμή, οπότε ισχύει ο τυπικός κανόνας σε αυτήν την έκδοση της γλώσσας για την ένδειξη μιας γραμμής συνέχειας (ένας μη κενός χαρακτήρας στην έκτη θέση για μια σταθερή μορφή εγγραφής και ένα συμπλεκτικό σύμβολο για ένα ελεύθερη μορφή).

Παράδειγμα προγράμματος Fortran με χρήση OpenMP

πρόγραμμα omp_example ακέραιος i, k, N real*4 sum, h, x print *, "Please, type in N:" read *, N h = 1,0 / N sum = 0,0 C$OMP PARALLEL DO CHEDULLE(STATIC) REDUCTION( +:sum) do i = 1, N x = i * h sum = sum + 1.e0 * h / (1.e0 + x**2) enddo print *, 4.0 * sum end

Η βιβλιοθήκη αναπτύσσεται ενεργά, επί του παρόντος το τρέχον πρότυπο είναι η έκδοση 4.5 (κυκλοφόρησε το 2015), αλλά η έκδοση 5.0 αναπτύσσεται ήδη. Ταυτόχρονα, ο μεταγλωττιστής Microsoft C++ υποστηρίζει μόνο την έκδοση 2.0 και το gcc υποστηρίζει την έκδοση 4.0.

Η βιβλιοθήκη OpenMP χρησιμοποιείται συχνά σε μαθηματικούς υπολογισμούς επειδή... σας επιτρέπει να παραλληλίσετε το πρόγραμμά σας πολύ γρήγορα και χωρίς ιδιαίτερη δυσκολία. Ταυτόχρονα, η ιδεολογία OpenMP δεν είναι πολύ κατάλληλη, ας πούμε, για ανάπτυξη λογισμικού διακομιστή (υπάρχουν πιο κατάλληλα εργαλεία για αυτό).

Πολλά βιβλία και άρθρα είναι αφιερωμένα σε αυτή τη βιβλιοθήκη. Τα πιο δημοφιλή είναι τα βιβλία των Antonov και Gergel, έχω γράψει επίσης μια σειρά άρθρων (επιφανειακά) σχετικά με αυτό το θέμα και μια σειρά από παραδείγματα προγραμμάτων στο φόρουμ μας. Κατά τη γνώμη μου, αυτά τα βιβλία έχουν μια σειρά από ελλείψεις (τις οποίες, φυσικά, διορθώνω):

  • Τα βιβλία προσπαθούν να περιγράψουν όλα τα χαρακτηριστικά του OpenMP, πολλά από τα οποία είναι ο αταβισμός. Σε παλαιότερες εκδόσεις της βιβλιοθήκης αυτό ήταν απαραίτητο, αλλά τώρα η χρήση αυτών των δυνατοτήτων καθιστά τον κώδικά σας πιο επικίνδυνο και αυξάνει το εμπόδιο στην είσοδο (ένας άλλος προγραμματιστής θα αφιερώσει πολύ χρόνο για να το καταλάβει). Περιέγραψα εν συντομία μερικές από αυτές τις δυνατότητες στη σημείωση "" και δεν υπάρχει τίποτα σχετικά με αυτές στο "σχολικό βιβλίο". Λόγω αυτού, το σχολικό μου βιβλίο είναι πιο σύντομο και δεν υπερφορτώνει τον αναγνώστη με ξεπερασμένες πληροφορίες.
  • Υπάρχουν λίγα «πραγματικά» παραδείγματα στα βιβλία - η αναζήτηση για ελάχιστο/μέγιστο δεν μετράει. Καλώ τους αναγνώστες μου να αναζητήσουν τέτοια παραδείγματα. Στο φόρουμ μπορείτε να λάβετε τον πηγαίο κώδικα των προγραμμάτων που έχουν διορθωθεί και λειτουργούν, και κάθε πρόγραμμα συνοδεύεται από λεπτομερή ανάλυση της λύσης και της προσέγγισης παραλληλισμού. Μερικές φορές μπορείτε να βρείτε πολλές υλοποιήσεις. Θα προστεθούν παραδείγματα.

Υπολογιστικά συστήματα. OpenMP ιδεολογία

Υπάρχουν πολλοί τύποι παράλληλων υπολογιστικών συστημάτων - υπολογιστές πολλαπλών πυρήνων/πολλαπλών επεξεργαστών, συμπλέγματα, συστήματα σε κάρτες βίντεο, προγραμματιζόμενα ολοκληρωμένα κυκλώματα κ.λπ. Η βιβλιοθήκη OpenMP είναι κατάλληλη μόνο για προγραμματισμό συστημάτων κοινής μνήμης με χρήση παραλληλισμού νημάτων. Τα νήματα δημιουργούνται μέσα σε μια ενιαία διαδικασία και έχουν τη δική τους μνήμη. Επιπλέον, όλα τα νήματα έχουν πρόσβαση στη μνήμη διεργασιών. Αυτό φαίνεται σχηματικά στο Σχήμα 1:
ρύζι. 1 μοντέλο μνήμης σε OpenMP

Για να χρησιμοποιήσετε τη βιβλιοθήκη OpenMP, πρέπει να συμπεριλάβετε το αρχείο κεφαλίδας "omp.h" και επίσης να προσθέσετε την επιλογή κατασκευής -fopenmp (για τον μεταγλωττιστή gcc) ή να ορίσετε την κατάλληλη σημαία στις ρυθμίσεις του έργου (για το Visual Studio). Μετά την εκκίνηση του προγράμματος, δημιουργείται μια ενιαία διαδικασία, η οποία αρχίζει να εκτελείται όπως ένα κανονικό διαδοχικό πρόγραμμα. Έχοντας συναντήσει μια παράλληλη περιοχή (καθορίζεται από την οδηγία #pragma omp parallel), η διαδικασία δημιουργεί έναν αριθμό νημάτων (ο αριθμός τους μπορεί να οριστεί ρητά, αλλά από προεπιλογή θα δημιουργήσει τόσα νήματα όσα υπάρχουν στο σύστημα υπολογιστικών πυρήνων σας) . Τα όρια μιας παράλληλης περιοχής σημειώνονται με σγουρά στηρίγματα στο τέλος της περιοχής, τα νήματα καταστρέφονται. Αυτή η διαδικασία φαίνεται σχηματικά στο Σχήμα 2:


Εικ.2 omp παράλληλη οδηγία

Οι μαύρες γραμμές στο σχήμα δείχνουν τη διάρκεια ζωής των νημάτων και οι κόκκινες γραμμές δείχνουν τη γενιά τους. Φαίνεται ότι όλα τα νήματα δημιουργούνται από ένα (κύριο) νήμα, το οποίο υπάρχει για όλη τη διάρκεια της διαδικασίας. Ένα τέτοιο νήμα στο OpenMP ονομάζεται κύριος, όλα τα άλλα νήματα δημιουργούνται και καταστρέφονται επανειλημμένα. Αξίζει να σημειωθεί ότι μπορούν να ενσωματωθούν παράλληλες οδηγίες και ανάλογα με τις ρυθμίσεις (που αλλάζουν από τη συνάρτηση omp_set_nested), μπορούν να δημιουργηθούν ένθετα νήματα.

Το OpenMP μπορεί να χρησιμοποιηθεί σε μια αρχιτεκτονική συμπλέγματος, αλλά όχι ανεξάρτητα, γιατί δεν μπορεί να φορτώσει ολόκληρο το σύμπλεγμα (για αυτό πρέπει να δημιουργήσετε διαδικασίες, να χρησιμοποιήσετε εργαλεία όπως MPI). Ωστόσο, εάν ο κόμβος συμπλέγματος είναι πολλαπλών πυρήνων, τότε η χρήση του OpenMP μπορεί να αυξήσει σημαντικά την απόδοση. Μια τέτοια εφαρμογή θα είναι "υβριδική" σε αυτό το άρθρο δεν θα μπω στο θέμα, ειδικά επειδή έχουν γραφτεί πολλά καλά υλικά.

Συγχρονισμός - κρίσιμα τμήματα, ατομικός, φραγμός

Όλες οι μεταβλητές που δημιουργούνται πριν από την παράλληλη οδηγία είναι κοινές σε όλα τα νήματα. Οι μεταβλητές που δημιουργούνται μέσα σε ένα νήμα είναι τοπικές (ιδιωτικές) και είναι προσβάσιμες μόνο στο τρέχον νήμα. Όταν πολλά νήματα αλλάζουν μια κοινόχρηστη μεταβλητή ταυτόχρονα, προκύπτει μια συνθήκη κούρσας (δεν μπορούμε να εγγυηθούμε κάποια συγκεκριμένη σειρά γραφής και επομένως το αποτέλεσμα) - αυτό είναι ένα πρόβλημα και δεν πρέπει να επιτρέπεται να συμβεί. Το ίδιο πρόβλημα παρουσιάζεται όταν ένα νήμα προσπαθεί να διαβάσει μια μεταβλητή ενώ ένα άλλο την αλλάζει. Το ακόλουθο παράδειγμα απεικονίζει την κατάσταση:

#include "omp.h" #include int main() ( int value = 123; #pragma omp parallel ( value++; #pragma ompcritic ( std::cout<< value++ << std::endl; } } }

Το πρόγραμμα ορίζει μια μεταβλητή τιμής που είναι κοινή σε όλα τα νήματα. Κάθε νήμα αυξάνει την τιμή μιας μεταβλητής και στη συνέχεια εκτυπώνει την τιμή που προκύπτει στην οθόνη. Το έτρεξα σε υπολογιστή διπλού πυρήνα και έλαβα τα ακόλουθα αποτελέσματα:

Πρόβλημα κούρσας νημάτων OpenMP

Είναι σαφές ότι πιο συχνάΚάθε νήμα θα αυξήσει πρώτα την τιμή της μεταβλητής και μετά θα εκτυπώσει εκ περιτροπής τα αποτελέσματα (το καθένα αυξάνει ξανά την τιμή), αλλά σε ορισμένες περιπτώσεις η σειρά εκτέλεσης θα είναι διαφορετική. Σε αυτό το παράδειγμα, μας ενδιαφέρει τώρα όταν προσπαθούμε να αυξήσουμε ταυτόχρονα την τιμή το πρόγραμμα μπορεί να συμπεριφέρεται όπως θέλει - να αυξηθεί μόνο μία φορά ή ακόμη και να τερματιστεί ασυνήθιστα.

Για την επίλυση του προβλήματος, υπάρχει μια κρίσιμη οδηγία ένα παράδειγμα χρήσης της. Ο κοινόχρηστος πόρος σε αυτό το παράδειγμα δεν είναι μόνο η μνήμη (οι μεταβλητές που τοποθετούνται σε αυτήν), αλλά και η κονσόλα (στην οποία καταλήγουν τα νήματα). Στο παράδειγμα, οι κούρσες εμφανίζονται όταν μια μεταβλητή αυξάνεται, αλλά όχι όταν εμφανίζεται στην οθόνη, επειδή οι λειτουργίες cout τοποθετούνται στο κρίσιμο τμήμα. Μόνο ένα νήμα μπορεί να βρίσκεται στο κρίσιμο τμήμα κάθε φορά, οι υπόλοιποι περιμένουν να κυκλοφορήσει. Θεωρείται καλός εμπειρικός κανόνας εάν η κρίσιμη ενότητα περιέχει κλήσεις μόνο σε έναν κοινόχρηστο πόρο (στο παράδειγμα, η ενότητα όχι μόνο εμφανίζει δεδομένα στην οθόνη, αλλά εκτελεί και μια αύξηση - αυτό δεν είναι πολύ καλό, στη γενική περίπτωση ).

Για έναν αριθμό λειτουργιών είναι πιο αποτελεσματικό να χρησιμοποιείται η ατομική οδηγία από το κρίσιμο τμήμα. Συμπεριφέρεται το ίδιο, αλλά λειτουργεί λίγο πιο γρήγορα. Μπορεί να χρησιμοποιηθεί για λειτουργίες προσαύξησης/μείωσης προθέματος/μετα-καθορισμού και λειτουργίες όπως X BINOP = EXPR, όπου το BINOP αντιπροσωπεύει όχι υπερφορτωμένοτελεστής +, *, -, /, &, ^, |,<<, >> . Ένα παράδειγμα χρήσης μιας τέτοιας οδηγίας:

#include "omp.h" #include int main() ( τιμή int = 123; #pragma omp parallel ( #pragma omp ατομική τιμή++; #pragma omp κρίσιμη (cout) ( std::cout<< value << std::endl; } } }

Η δυνατότητα ονομασίας κρίσιμων ενοτήτων εμφανίζεται επίσης εδώ - συνιστώ ανεπιφύλακτα να το χρησιμοποιείτε πάντα. Το θέμα είναι ότι όλες οι ενότητες χωρίς όνομα αντιμετωπίζονται ως μία (πολύ μεγάλες) και αν δεν τους δώσετε ρητά ονόματα, τότε μόνο μία από αυτές τις ενότητες θα έχει ένα νήμα τη φορά. Τα υπόλοιπα θα περιμένουν. Το όνομα της ενότητας πρέπει να λέει στον προγραμματιστή σε ποιον τύπο πόρου ανήκει - στο παράδειγμα που δίνεται, ένας τέτοιος πόρος είναι η ροή εξόδου οθόνης (cout).

Παρόλο που κάθε πράξη σε κοινόχρηστα δεδομένα στο τελευταίο παράδειγμα τοποθετείται σε ένα κρίσιμο τμήμα ή είναι ατομική, έχει πρόβλημα επειδή Η σειρά με την οποία θα εκτελεστούν αυτές οι λειτουργίες είναι ακόμα ασαφής. Αφού εκτέλεσα το πρόγραμμα 20 φορές, κατάφερα να βγω στην οθόνη όχι μόνο το "125 125", αλλά και το "124 125". Εάν θέλουμε κάθε νήμα να αυξάνει πρώτα την τιμή και μετά να τα εκτυπώνει στην οθόνη, μπορούμε να χρησιμοποιήσουμε την οδηγία φραγμού:

#pragma omp parallel ( #pragma omp ατομική τιμή++; #pragma omp barrier #pragma omp kritik (cout) ( std::cout<< value << std::endl; } }

Το νήμα, έχοντας ολοκληρώσει το μέρος των υπολογισμών του, φτάνει στην οδηγία φραγμού και περιμένει έως ότου όλα τα νήματα φτάσουν στο ίδιο σημείο. Έχοντας περιμένει το τελευταίο, τα νήματα συνεχίζουν να εκτελούνται. Τώρα δεν υπάρχουν προβλήματα συγχρονισμού στο πρόγραμμα, αλλά σημειώστε ότι όλα τα νήματα κάνουν την ίδια δουλειά (που περιγράφεται στην παράλληλη περιοχή), αν και παράλληλα. Σε αυτήν την περίπτωση, το πρόγραμμα δεν θα αρχίσει να λειτουργεί πιο γρήγορα, πρέπει να κατανείμουμε τις εργασίες που πρέπει να επιλυθούν μεταξύ των νημάτων, αυτό μπορεί να γίνει με διάφορους τρόπους...

Διαχωρισμός εργασιών μεταξύ νημάτων

Παράλληλος βρόχος

Ο πιο δημοφιλής τρόπος διανομής εργασιών στο OpenMP είναι ένας παράλληλος βρόχος. Δεν είναι μυστικό ότι τα προγράμματα περνούν σχεδόν ολόκληρη τη ζωή τους εκτελώντας βρόχους και αν δεν υπάρχουν εξαρτήσεις μεταξύ των επαναλήψεων βρόχων, τότε ο βρόχος ονομάζεται διανυσματοποιήσιμος (οι επαναλήψεις του μπορούν να χωριστούν μεταξύ νημάτων και να εκτελεστούν ανεξάρτητα το ένα από το άλλο). Στο άρθρο "" περιέγραψα επιφανειακά αυτήν την κατασκευή χρησιμοποιώντας παραδείγματα υπολογισμού του αθροίσματος στοιχείων πίνακα και αριθμητικής ολοκλήρωσης, δεν θα επαναλάβω τον εαυτό μου - συνιστώ να ακολουθήσετε τον σύνδεσμο, επειδή Τα παρακάτω περιγράφουν τις πιο «ενδιαφέρουσες» πτυχές του παράλληλου βρόχου.

Ένας παράλληλος βρόχος σάς επιτρέπει να ορίσετε την επιλογή χρονοδιαγράμματος, η οποία αλλάζει τον αλγόριθμο για την κατανομή των επαναλήψεων μεταξύ των νημάτων. Υποστηρίζονται συνολικά 3 τέτοιοι αλγόριθμοι. Στη συνέχεια, υποθέτουμε ότι έχουμε p νήματα που εκτελούν n επαναλήψεις:

Επιλογές προγραμματισμού:

  • χρονοδιάγραμμα(στατικός) - στατικός προγραμματισμός. Όταν χρησιμοποιείτε αυτήν την επιλογή, οι επαναλήψεις βρόχου θα κατανεμηθούν εξίσου (περίπου) μεταξύ των νημάτων. Το νήμα μηδέν θα λάβει τις πρώτες επαναλήψεις \(\frac(n)(p)\), το νήμα ένα θα λάβει τη δεύτερη κ.λπ.
  • χρονοδιάγραμμα (στατικό, 10) — μπλοκ-κυκλική κατανομή επαναλήψεων. Κάθε νήμα λαμβάνει έναν δεδομένο αριθμό επαναλήψεων στην αρχή του βρόχου, και στη συνέχεια (αν υπάρχουν άλλες επαναλήψεις) η διαδικασία κατανομής συνεχίζεται. Ο προγραμματισμός εκτελείται μία φορά, με κάθε νήμα να «μαθαίνει» τις επαναλήψεις που πρέπει να εκτελέσει.
  • χρονοδιάγραμμα (δυναμικό), χρονοδιάγραμμα (δυναμικό, 10) — δυναμικός προγραμματισμός. Από προεπιλογή, η παράμετρος επιλογής είναι 1. Κάθε νήμα λαμβάνει τον καθορισμένο αριθμό επαναλήψεων, τις εκτελεί και ζητά ένα νέο τμήμα. Σε αντίθεση με τον στατικό σχεδιασμό, εκτελείται επανειλημμένα (κατά την εκτέλεση του προγράμματος). Η ειδική κατανομή των επαναλήψεων μεταξύ των νημάτων εξαρτάται από τον ρυθμό εργασίας των νημάτων και την πολυπλοκότητα των επαναλήψεων.
  • schedule(guided), schedule(guided, 10) είναι ένας τύπος δυναμικού σχεδιασμού με τον αριθμό των επαναλήψεων να αλλάζει με κάθε επόμενη διανομή. Η διανομή ξεκινά από κάποιο αρχικό μέγεθος, ανάλογα με την υλοποίηση της βιβλιοθήκης, μέχρι την τιμή που καθορίζεται στην επιλογή (προεπιλογή 1). Το μέγεθος του εκχωρημένου τμήματος εξαρτάται από τον αριθμό των μη εκχωρημένων επαναλήψεων

Στις περισσότερες περιπτώσεις, η καλύτερη επιλογή είναι η στατική, γιατί εκτελεί την κατανομή μόνο μία φορά, είναι λογικό να παίζετε με αυτήν την παράμετρο εάν η πολυπλοκότητα των επαναλήψεων στο πρόβλημά σας ποικίλλει πολύ. Για παράδειγμα, εάν μετράτε το άθροισμα των στοιχείων ενός τετραγωνικού πίνακα που βρίσκεται κάτω από την κύρια διαγώνιο, τότε η στατική δεν θα δώσει το καλύτερο αποτέλεσμα, επειδή το πρώτο νήμα θα εκτελεί σημαντικά λιγότερες λειτουργίες και θα είναι αδρανές.

Το άρθρο παράλληλου βρόχου περιγράφει επίσης τις επιλογές nowait και μείωσης. Το πρώτο από αυτά πολύ σπάνια θα δώσει αξιοσημείωτο κέρδος και συνιστώ να χρησιμοποιείτε το δεύτερο όσο πιο συχνά γίνεται (αντί για κρίσιμα τμήματα). Σε αυτό το άρθρο, η μείωση χρησιμοποιήθηκε σε όλα τα παραδείγματα και λόγω αυτού, ήταν δυνατό να αποφευχθεί η ρητή χρήση κρίσιμων ενοτήτων, ωστόσο, αυτό δεν είναι πάντα δυνατό και επομένως αξίζει να γνωρίζουμε τι υπάρχει μέσα σε αυτό. Έτσι, παράλληλα, μπορείτε να υπολογίσετε το άθροισμα των στοιχείων του πίνακα ως εξής:

Int sum_arr(int *a, const int n) ( int sum = 0; #pragma omp παράλληλη μείωση (+: sum) ( #pragma omp for for (int i = 0; i< n; ++i) sum += a[i]; } return sum; }

Αυτό φαίνεται ωραίο, αλλά στην πραγματικότητα, δημιουργείται μια τοπική μεταβλητή σε κάθε νήμα για να αποθηκεύει το άθροισμα του τμήματος του πίνακα (ο υπολογισμός του οποίου εκχωρείται στο τρέχον νήμα) και της εκχωρείται η τιμή 0 (από τη μείωση με ο τελεστής +). Κάθε νήμα υπολογίζει το άθροισμα, αλλά πρέπει να προσθέσετε όλες αυτές τις τιμές για να έχετε το τελικό αποτέλεσμα; — Αυτό γίνεται χρησιμοποιώντας ένα κρίσιμο τμήμα ή μια ατομική λειτουργία περίπου ως εξής:

Int sum_arr(int *a, const int n) ( int sum = 0; #pragma omp parallel ( int local_sum = 0; #pragma omp for for (int i = 0; i< n; ++i) local_sum += a[i]; #pragma omp atomic sum += local_sum; } return sum; }

Αυτή η προσέγγιση χρησιμοποιείται συνεχώς, γι' αυτό προτείνω να ρίξετε μια προσεκτική ματιά σε αυτόν τον κώδικα. Ένα ελαφρώς πιο σύνθετο παράδειγμα είναι το παράλληλο. Ως εργασία για να ελέγξετε την κυριαρχία σας στο υλικό, προτείνω να προσπαθήσετε να δημιουργήσετε ένα ιστόγραμμα (για παράδειγμα, για μια εικόνα).

Παράλληλες εργασίες

Οι παράλληλες εργασίες είναι ένας πιο ευέλικτος μηχανισμός από έναν παράλληλο βρόχο. Ένας παράλληλος βρόχος περιγράφεται σε μια παράλληλη περιοχή και μπορεί να προκύψουν προβλήματα. Για παράδειγμα, γράψαμε μια παράλληλη συνάρτηση για τον υπολογισμό του αθροίσματος των στοιχείων ενός μονοδιάστατου πίνακα και αποφασίσαμε να χρησιμοποιήσουμε τη συνάρτησή μας για να υπολογίσουμε το άθροισμα των στοιχείων του πίνακα, αλλά και να το κάνουμε παράλληλα. Το αποτέλεσμα είναι ο ένθετος παραλληλισμός. Εάν (θεωρητικά) ο κώδικάς μας τρέχει σε 8 πυρήνες, τότε θα δημιουργηθούν στην πραγματικότητα 64 νήματα. Λοιπόν, τι γίνεται αν κάποιος έχει την ιδέα να κάνει κάτι άλλο παράλληλα;

Μερικές φορές μια τέτοια κατάσταση δεν είναι εύκολο να εντοπιστεί, για παράδειγμα, στο φόρουμ μας μπορείτε να βρείτε μια παράλληλη υλοποίηση, στην οποία n προσδιοριστές υπολογίζονται παράλληλα. καλεί τη συνάρτηση.

Το πρόβλημα με έναν παράλληλο βρόχο είναι ότι ο αριθμός των νημάτων που δημιουργούνται εξαρτάται από το ποιες συναρτήσεις είναι παραλληλισμένες και πώς καλούν η μία την άλλη. Είναι πολύ δύσκολο να τα παρακολουθήσεις όλα αυτά και, ακόμη περισσότερο, να τα διατηρήσεις. Η λύση στο πρόβλημα είναι παράλληλες εργασίες που δεν δημιουργούν νήμα, αλλά προσθέτουν μόνο μια εργασία στην ουρά, το νήμα που έχει ελευθερωθεί επιλέγει μια εργασία από τη συγκέντρωση. Περιέγραψα αυτόν τον μηχανισμό στο άρθρο
"" και δεν θα επαναλάβω τον εαυτό μου (συνιστώ να διαβάσετε το υλικό στον σύνδεσμο - το άρθρο συζητά την πιθανότητα παραλληλισμός αναδρομικών συναρτήσεωνχρησιμοποιώντας τον μηχανισμό εργασιών). Θα σημειώσω μόνο ότι οι παράλληλες εργασίες προτάθηκαν στο πρότυπο OpenMP 3.0 (το 2008), επομένως η υποστήριξή τους δεν είναι διαθέσιμη στη Microsoft C++. Επιπλέον, το νέο πρότυπο OpenMP 4.5 πρότεινε την κατασκευή του βρόχου εργασιών, λόγω της οποίας είναι πλέον τόσο βολικό να χρησιμοποιούνται παράλληλες εργασίες για την παραλληλοποίηση βρόχων όσο και ένας παράλληλος βρόχος.

Παράλληλες τομές

Ο μηχανισμός των παράλληλων τμημάτων μου φαίνεται αρκετά χαμηλού επιπέδου. Ωστόσο, είναι χρήσιμο σε πολλές περιπτώσεις. Όπως σημειώθηκε παραπάνω, ένας παράλληλος βρόχος μπορεί να χρησιμοποιηθεί μόνο σε περιπτώσεις όπου οι επαναλήψεις βρόχου είναι ανεξάρτητες μεταξύ τους, π.χ. Δεν μπορείτε να το κάνετε αυτό εδώ:

Για (int i = 1; i< n; ++i) a[i] = a+1;

Εάν έχουμε πολλά τμήματα στο πρόγραμμά μας που είναι ανεξάρτητα μεταξύ τους, αλλά έχουν εξαρτήσεις μέσα τους, τότε παραλληλίζονται χρησιμοποιώντας τον μηχανισμό των παράλληλων τμημάτων:

#pragma omp parallel ( #pragma omp sections ( #pragma omp section ( for (int i = 1; i< n; ++i) a[i] = a+1; } #pragma omp section { for (int i = 1; i < n; ++i) b[i] = b+1; } } }

Το παράδειγμα δεν είναι και το καλύτερο, γιατί... Ακόμα δεν έχω συναντήσει μια πραγματική εργασία όπου αυτό χρειάζεται. Πολύ σημαντικό - μην προσπαθήσετε να παραλληλίσετε αναδρομικές συναρτήσεις χρησιμοποιώντας ενότητες (χρησιμοποιήστε εργασίες για αυτό). Ίσως στο μέλλον (ειδικά αν η Microsoft εφαρμόσει το πρότυπο OpenMP 3.0) θα μετακινήσω αυτήν την ενότητα στο .

Συμπέρασμα και περαιτέρω ανάγνωση

Εδώ τελειώνει το σεμινάριο... Εάν θέλετε να δείτε περισσότερα παραδείγματα πηγαίου κώδικα με το OpenMP ή έχετε ερωτήσεις σχετικά με την επίλυση συγκεκριμένων προβλημάτων, ρίξτε μια ματιά στο φόρουμ μας. Εάν έχετε οποιεσδήποτε ερωτήσεις σχετικά με το κείμενο του άρθρου, γράψτε στα σχόλια.

  1. Υποστήριξη OpenMP στο Lazarus/Freepascal: http://wiki.lazarus.freepascal.org/OpenMP_support
  2. OpenMP για Java: http://www.omp4j.org/
  3. Antonov A.S. Τεχνολογίες παράλληλου προγραμματισμού MPI και OpenMP: Textbook. επίδομα. Πρόλογος: V.A. Sadovnichy. – M.: Moscow University Publishing House, 2012.-344 σελ.-(Σειρά «Εκπαίδευση Υπερυπολογιστή»). ISBN 978-5-211-06343-3.
  4. Gergel V.P. Υπολογισμός υψηλής απόδοσης για πολυπύρηνα συστήματα πολλαπλών επεξεργαστών. Εγχειρίδιο - Νίζνι Νόβγκοροντ; Εκδοτικός οίκος UNN με το όνομά του. N.I Lobachevsky, 2010
  5. : https://site/archives/1150
  6. Παράλληλες εργασίες OpenMP:
  7. : https://site/forums/forum/programming/parallel_programming/openmp_library
  8. : href="https://site/forums/topic/openmp-problems
  9. : https://site/forums/topic/matrix-triangulation_cplusplus
  10. Προφίλ υβριδικών εφαρμογών συμπλέγματος MPI+OpenMP: https://habrahabr.ru/company/intel/blog/266409/
  11. : https://site/forums/topic/openmp-cramer-method_cplusplus
  12. 32 παγίδες OpenMP κατά τον προγραμματισμό σε C++: https://www.viva64.com/ru/a/0054/
  13. OpenMP και ανάλυση στατικού κώδικα: https://www.viva64.com/ru/a/0055/


Συνιστούμε να διαβάσετε

Κορυφή