OpenMP


Η εισαγωγή αυτή είναι βασισμένη στο tutorial "Introduction to OpenMP" του Blaise Barney, Lawrence Livermore Laboratory, USA. Μετάφραση και προσαρμογή: Κ.Γ. Μαργαρίτης και Π. Γεωργιόπουλος, Εργαστήριο Παράλληλης Κατανεμημένης Επεξεργασίας, Τμήμα Εφαρμοσμένης Πληροφορικής, Πανεπιστήμιο Μακεδονίας.


Περιεχόμενα

  1. Περίληψη
  2. Εισαγωγή
    1. Τί είναι το OpenMP?
    2. Ιστορικό
    3. Στόχοι του OpenMP
  3. Το Μοντέλο Προγραμματισμού του OpenMP
  4. Οδηγίες Directives
    1. Οδηγία PARALLEL
    2. Οδηγίες Διαμοιρασμού Εργασίας
      1. Οδηγία FOR
      2. Οδηγία SECTIONS
      3. Οδηγία SINGLE
    3. Συνδυασμένες Οδηγίες Διαμοιρασμού Εργασίας
      1. Οδηγία PARALLEL FOR 
      2. Οδηγία PARALLEL SECTIONS
    4. Οδηγίες Συγχρονισμού
      1. Οδηγία BARRIER
      2. Οδηγία MASTER
      3. Οδηγία CRITICAL
      4. Οδηγία ATOMIC 
      5. Οδηγία FLUSH
      6. Οδηγία ORDERED
    5. Οδηγία THREADPRIVATE 
    6. Φράσεις Εμβέλειας Δηλώσεων Δεδομένων
      1. Φράση PRIVATE 
      2. Φράση SHARED
      3. Φράση DEFAULT
      4. Φράση FIRSTPRIVATE
      5. Φράση LASTPRIVATE
      6. Φράση COPYIN
      7. Φράση COPYPRIVATE
      8. Φράση REDUCTION
    7. Σύνοψη Φράσεων / Οδηγιών
    8. Κανόνες Σύνδεσης και Ένθεσης Οδηγιών
  5. Ρουτίνες Συστήματος Εκτέλεσης
    1. OMP_SET_NUM_THREADS
    2. OMP_GET_NUM_THREADS
    3. OMP_GET_MAX_THREADS
    4. OMP_GET_THREAD_NUM
    5. OMP_GET_THREAD_LIMIT
    6. OMP_GET_NUM_PROCS
    7. OMP_IN_PARALLEL
    8. OMP_SET_DYNAMIC
    9. OMP_GET_DYNAMIC
    10. OMP_SET_NESTED
    11. OMP_GET_NESTED
    12. OMP_INIT_LOCK
    13. OMP_DESTROY_LOCK
    14. OMP_SET_LOCK
    15. OMP_UNSET_LOCK
    16. OMP_TEST_LOCK
    17. OMP_INIT_NEST_LOCK
    18. OMP_DESTROY_NEST_LOCK
    19. OMP_SET_NEST_LOCK
    20. OMP_UNSET_NEST_LOCK
    21. OMP_TEST_NEST_LOCK
    22. OMP_GET_WTIME
    23. OMP_GET_WTICK
  6. Μεταβλητές Περιβάλλοντος
  7. Ζητήματα Μνήμης και Απόδοσης
  8. Αναφορές και Περισσότερες Πληροφορίες
  9. Ασκήσεις


Περίληψη


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



Εισαγωγή

Τι είναι το OpenMP?

Το OpenMP είναι: OpenMP Logo

     Το OpenMP είναι μια Διεπαφή Προγραμματισμού Εφαρμογών (Application Programming Interface, API) το οποίο δημιουργήθηκε από κατασκευαστές υλικού και λογισμικού. Τα αρχικά αντιστοιχούν στη φράση "Open Specifications for Multi Processing"]. Το πρότυπο αυτό προσφέρει στον προγραμματιστή ένα σύνολο από οδηγίες, οι οποίες ενσωματώνονται στον κώδικα ενός προγράμματος, και έτσι μπορεί ο μεταγλωττιστής να επιτύχει παραλληλισμό στο πρόγραμμα αυτό, σύμφωνα πάντα με τις οδηγίες και παραμέτρους που έχει θέσει ο προγραμματιστής. Το API αυτό αποτελείται κυρίως από τρία βασικά συστατικά:

     Το πρότυπο αυτό έχει ορισθεί για τις γλώσσες προγραμματισμού C/C++ και FORTRAN. Προς το παρόν δεν υπάρχουν σχέδια για ανάπτυξη σε άλλες γλώσσες προγραμματισμού [1]. Οι κώδικες σε OpenMP είναι μεταφέρσιμοι και υπάρχουν υλοποιήσεις για αρκετές πλατφόρμες, μέσα στις οποίες περιλαμβάνονται οι περισσότερες πλατφόρμες UNIX/Linux καθώς και Windows NT και μεταγενέστερα. Ο παραλληλισμός που προσφέρει αφορά συστήματα μοιραζόμενης μνήμης, οπότε δεν μπορεί από μόνο του να εφαρμοστεί σε συστήματα κατανεμημένης μνήμης. Επίσης οι εκάστοτε υλοποιήσεις δεν είναι απαραίτητα υλοποιημένες τέλεια, και προς το παρόν δεν μπορεί να κάνει την καλύτερη χρήση της μοιραζόμενης μνήμης, διότι δεν προσφέρει ακόμα οδηγίες για την τοπικότητα των δεδομένων.



Εισαγωγή

Ιστορικό

     Η ιστορία του OpenMP ξεκινάει από τις αρχές της δεκαετίας του '90. Εκείνη τη εποχή, οι εταιρίες συστημάτων μοιραζόμενης μνήμης παρείχαν παρόμοιες επεκτάσεις προγραμμάτων Fortran, βασισμένες σε οδηγίες μεταγλωττιστή. Ο προγραμματιστής έδινε στον μεταφραστή ένα σειραικό πρόγραμμα Fortran με οδηγίες που καθόριζαν ποιοι βρόχοι θα παραλληλοποιούνταν. Στη συνέχεια, ο μεταφραγλωττιστής ήταν υπεύθυνος για την αυτόματη παραλληλοποίηση αυτών των βρόγχων σε συμμετρικούς επεξεργαστές (SMP's). Αν και οι υλοποιήσεις των διάφορων εταιριών ήταν παρόμοιες ως προς τη λειτουργικότητα, ωστόσο, διέφεραν αρκετά. Έτσι, έγινε μια προσπάθεια για να δημιουργηθεί ένα πρότυπο. Το πρώτο πρότυπο που δημιουργήθηκε ήταν το ANSI X3H5 to 1994, αλλά δεν υιοθετήθηκε επειδή εκείνη την εποχή το ενδιαφέρον για αρχιτεκτονικές μοιραζόμενης μνήμης έπεσε, ενώ ανέβηκε το ενδιαφέρον για συστήματα με αρχιτεκτονική κατανεμημένης μνήμης. Το 1997 προτάθηκε το πρότυπο OpenMP συνεχίζοντας από εκεί που είχε μείνει το ANSI X3H5, μιας και τώρα τα συστήματα μοιραζόμενης μνήμης άρχισαν να γίνονται δημοφιλέστερα. Εκείνη τη χρονιά είχε οριστεί μόνο το API για τη Fortran. To 1998 ορίστηκε το API για τις γλώσσες C/C++, ενώ το 2000 φτιάχτηκε μια δεύτερη έκδοση για τη Fortran και το 2002 ακολούθησε η δεύτερη έκδοση για C/C++. Οι εταιρίες που συμμετέχουν επισήμως στις προδιαγραφές του OpenMP προτύπου είναι:

    Εταιρίες Υλικού:
  • Compaq / Digital
  • Hewlett-Packard Company
  • Intel Corporation
  • International Business Machines (IBM)
  • Kuck & Associates, Inc.
  • Silicon Graphics, Inc.
  • Sun Microsystems, Inc.
  • U.S. Department of Energy ASCI program
    Εταιρίες Λογισμικού:
  • Absoft Corporation
  • Edinburgh Portable Compilers
  • GENIAS Software GmBH
  • International Business Machines (IBM)
  • Myrias Computer Technologies, Inc.
  • The Portland Group, Inc. (PGI)

Ημερομηνία Έκδοση
October 1997 Fortran version 1.0
Late 1998 C/C++ version 1.0
June 2000 Fortran version 2.0
April 2002 C/C++ version 2.0
Ιστορικό εκδόσεων
OpenMP Release Timeline

Εισαγωγή

Στόχοι του OpenMP

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

α. Τη δυνατότητα παραλληλοποίησης ενός προγράμματος σταδιακά (εν αντιθέσει, για παράδειγμα, με το MPI όπου κάθε φορά γίνεται include όλη η βιβλιοθήκη).
 
β. Τη δυνατότητα τόσο αδρομερούς (coarse-grain) όσο και λεπτομερούς (fine-grain) παραλληλισμού.

Τέλος, ο κυριότερος στόχος του OpenMP είναι η μεταφερτότητα. Υποστηρίζει Fortran (77, 90 και 95) και C/C++ ενώ έχει υλοποιηθεί για τις πιο πολλές από τις σημαντικές πλατφόρμες όπως Unix/Linux και Windows.



To Μοντέλο Προγραμματισμού του OpenMP 

     Το μοντέλο προγραμματισμού του OpenMP είναι βασισμένο στο πολυνηματικό μοντέλο παραλληλισμού. Αρχικά, μία εφαρμογή σε OpenMP ξεκινά με ένα μόνο νήμα, το οποίο ονομάζεται master thread. Όταν το πρόγραμμα εισέρχεται σε μία περιοχή που έχει ορίσει ο προγραμματιστής να εκτελεστεί παράλληλα (παράλληλη περιοχή, parallel region), τότε δημιουργούνται αρκετά νήματα (fork-join μοντέλο) και το μέρος του κώδικα που βρίσκεται μέσα στη παράλληλη περιοχή εκτελείται παράλληλα. Όταν ολοκληρωθεί ο υπολογισμός της παράλληλης περιοχής όλα τα νήματα τερματίζουν και συνεχίζει μόνο το master thread. Στην εικόνα 1 φαίνεται ο τρόπος με τον οποίο λειτουργεί το μοντέλο αυτό.

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

Fork - Join Model

Μοντέλος Μνήμης: Συχνό FLUSH;

      Το μοντέλο μνήμης του OpenMP είναι ένα χαλαρό μοντέλο μοιραζόμενης μνήμης. Όλα τα νήματα έχουν πρόσβαση στη κύρια μνήμη. Κάθε νήμα μπορεί να έχει μια προσωρινή άποψη (temporary view) της μνήμης. Με αυτόν τον τρόπο, αν και δεν είναι υποχρεωτικό στο μοντέλο μνήμης του OpenMP, μπορεί να ενταμιεύσει (cache) κάποιες μεταβλητές και έτσι να αποφεύγεται η συνεχής προσπέλαση στη μνήμη. Επίσης κάθε νήμα έχει και την ιδιωτική του μνήμη (thread-private memory), όπου τα υπόλοιπα νήματα δεν έχουν πρόσβαση. Επιγραμματικά στο OpenMP ισχύουν τα εξής:

Στη συνέχεια βλέπουμε τη βασική δομή σε έναν κώδικα C/C++ για τον ορισμό της παράλληλης περιοχής.


Παράδειγμα Γενικής Δομής Κώδικα OpenMP



Οδηγίες OpenMP 

     Στη συνέχεια θα δούμε τις οδηγίες που παρέχει το OpenMP. Θα δούμε τις οδηγίες για τη γλώσσα C/C++. Πολλές από τις παραμέτρους είναι κοινές στις οδηγίες, και αυτές θα εξηγηθούν στο τέλος της ενότητας αυτής. Πριν μελετήσουμε ξεχωριστά τις οδηγίες που παρέχονται από το OpenMP, θα δούμε πρώτα έναν γενικό κανόνα για τον τρόπο που συντάσσονται οι οδηγίες.

Σύνταξη οδηγιών C / C++:

Παράδειγμα:

Γενικοί κανόνες:


Οδηγίες OpenMP 

Οδηγία PARALLEL

Στόχος:

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

Μορφή:

C/C++
#pragma omp parallel [clause ...]  newline 
if (scalar_expression)
private (list)
shared (list)
default (shared | none)
firstprivate (list)
reduction (operator: list)
copyin (list)
num_threads (integer-expression)


structured_block

     Όταν ένα νήμα φτάσει σε μια οδηγία παραλληλης περιοχής, δημιουργεί μια ομάδα από νήματα και το ίδιο γίνεται ο master της ομάδας. Ο master είναι κι ο ίδιος μέλος της ομάδας. Ο κώδικας της παράλληλης περιοχής αντιγράφεται τόσες φορές όσα τα νήματα και δίδεται σε αυτά για να τον εκτελέσουν. Γενικά, δεν υπάρχει συγχρονισμός μεταξύ των νημάτων. Κάθε νήμα μπορεί να φτάσει σε οποιοδήποτε σημείο μέσα στη παράλληλη περιοχή, σε ακαθόριστη χρονική στιγμή. Στο τέλος της παράλληλης περιοχής υπονοείται ένα φράγμα, πέρα από το οποίο μόνο ο master θα συνεχίσει την εκτέλεση του προγράμματος. Το OpenMP παρέχει τη δυνατότητα δημιουργίας παράλληλης περιοχής μέσα σε μια άλλη παράλληλη περιοχή (τακτική που πρέπει γενικά να αποφεύγεται). Η φωλιασμένη αυτή παράλληλη περιοχή, καταλήγει στη δημιουργία μιας καινούριας ομάδας από νήματα η οποία αποτελείται εξ' ορισμού από ένα νήμα. Ωστόσο, διάφορες υλοποιήσεις μπορούν να επιτρέψουν παραπάνω από ένα νήμα σε μια φωλιασμένη παράλληλη περιοχή.

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

α. Η χρήση της συνάρτησης βιβλιοθήκης omp_set_num_threads().
β. Η τιμή της μεταβλητής περιβάλλοντος OMP_NUM_THREADS.
γ. Η χρήση της φράσης default (προκαθορισμένη υλοποίηση).

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

α. Η χρήση της συνάρτησης βιβλιοθήκης omp_set_dynamic().
β. Η τιμή της μεταβλητής περιβάλλοντος OMP_DYNAMIC.

     Η αρίθμηση των νημάτων ξεκινάει από τον αριθμό 0, οποίος δίνεται στον master κάθε ομάδας και φτάνει στον αριθμό Ν-1 όπου Ν ο αριθμός των νημάτων. Η ταυτότητα κάθε νήματος μπορεί να βρεθεί χρησιμοποιώντας τη συνάρτηση βιβλιοθήκης omp_get_thread_num(), ενώ ο συνολικός αριθμός των νημάτων μπορεί να βρεθεί με τη χρήση της συνάρτησης βιβλιοθήκης omp_get_num_threads(). Αν και τα νήματα εκτελούν το ίδιο τμήμα κώδικα, ωστόσο, θα θέλαμε να εκτελέσουν διαφορετικά μονοπάτια αυτού του κώδικα. Αυτό επιτυγχάνεται με το να δίνουμε διαφορετικό κομμάτι κώδικα σε κάθε νήμα (για παράδειγμα, με τη χρήση if-else, μοντέλο παραλληλισμού SPMD).

     Στον κώδικα που ακολουθεί φαίνεται ένα απλό παράδειγμα. Αν υποθέσουμε ότι θα δημιουργηθούν πέντε νήματα, τότε στην έξοδο θα είναι το μήνυμα με το "Hello World" πέντε φορές αλλά το μήνυμα για το πλήθος των νημάτων θα το τυπώσει μόνο το master thread, και αυτό γιατί το id του είναι το 0.


Παράδειγμα: Παράλληλη Περιοχή



Οδηγίες OpenMP 

Οδηγίες Διαμοιρασμού Εργασίας

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

FOR - Διαμοιράζει τις επαναλήψεις ενός βρόχου for στα νήματα της τρέχουσας παράλληλης περιοχής. Αντιστοιχεί στο μοντέλο Παραλληλισμού Δεδομένων (Data Parallelism).
SECTIONS - Διαμοιράζει την εργασία σε διακριτά δομικά blocks. Κάθε block εκτελείται από ένα νήμα. Αντιστοιχεί στο μοντέλο Συναρτησιακού Παραλληλισμού (Functional Parallelism). SINGLE - Σειριοποιεί ένα τμήμα του κώδικα ώστε να εκτελεστεί υποχερωτικά από ένα νήμα.
DO / for loop work-sharing construct SECTIONS work-sharing construct SINGLE work-sharing construct

Περιορισμοι:



Οδηγίες OpenMP

Οδηγίες Διαμοιρασμού Εργασίας
Οδηγία FOR

Σκοπός:

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

Μορφή:

Φράσεις:

     Αυτή η οδηγία χρησιμοποιείται μόνο για τον παραλληλισμό μιας εντολής for. Η φράση schedule ορίζει τον τρόπο με τον οποίο θα γίνει ο διαμοιρασμός των επαναλήψεων ανάμεσα στα νήματα. Το πρώτο όρισμα της φράσης μπορεί να είναι:

     Η φράση ordered εξηγείται παρακάτω. Τέλος, η φράση nowait, αν υπάρχει, αναιρεί το φράγμα που υπάρχει, όπως είπαμε νωρίτερα, στο τέλος της οδηγίαςFOR.

     Για να εκτελεστεί σωστά η οδηγία αυτή, πρέπει να ισχύουν κάποιες συνθήκες για την εντολή for της C/C++. H μεταβλητή βήματος πρέπει να αυξάνεται με μια σταθερή τιμή, και να μη προκύπτει ως τιμή μιας παράστασης που περιέχει μεταβλητές. Το ίδιο ισχύει για τη συνθήκη τερματισμού: πρέπει η τιμή που συγκρίνεται σε κάθε επανάληψη να είναι επίσης σταθερή. Γενικά ο βρόχος πρέπει να τερματίζει μετά από ένα πεπερασμένο αριθμό επαναλήψεων. Στην συνέχεια βλέπουμε ένα απλό παράδειγμα για πρόσθεση δύο διανυσμάτων. Η εντολή for είναι σε σωστή μορφή και οι επαναλήψεις χωρίζονται ανά 100. Η ανάθεση των τμημάτων γίνεται δυναμικά. Τέλος, τα νήματα δεν θα περιμένουν μετά την ολοκλήρωση των υπολογισμών, καθώς υπάρχει η φράση nowait.


Παράδειγμα: Οδηγία FOR



Οδηγίες OpenMP 

Οδηγίες Διαμοιρασμού Εργασίας
Οδηγία SECTIONS

Σκοπός:

      Η οδηγία sections είναι μια μη επαναληπτική περιοχή διαμοιρασμού εργασίας. Καθορίζει ότι τα εσωκλειώμενα τμήματα κώδικα θα διαμοιραστούν μεταξύ των νημάτων της ομάδας. Μια οδηγία sections μπορεί να περιέχει περισσότερες από μία, ανεξάρτητες, οδηγίες section. Κάθε τμήμα εκτελείται μια φορά από ένα νήμα της ομάδας, ενώ διαφορετικά τμήματα εκτελούνται από διαφορετικά νήματα. Η σύνταξη μιας οδηγίας sections φαίνεται παρακάτω:

Μορφή:

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

     Οι περιορισμοί που θα πρέπει να ισχύουν στις οδηγίες sections είναι οι εξής:


Παράδειγμα : Οδηγία SECTIONS 

  • C / C++ - sections Directive Example
    #include <omp.h>
    #define N 1000

    main ()
    {

    int i;
    float a[N], b[N], c[N], d[N];

    /* Some initializations */
    for (i=0; i < N; i++)
    a[i] = i * 1.5;
    b[i] = i + 22.35;

    #pragma omp parallel shared(a,b,c,d) private(i)
    {

    #pragma omp sections nowait
    {

    #pragma omp section
    for (i=0; i < N; i++)
    c[i] = a[i] + b[i];

    #pragma omp section
    for (i=0; i < N; i++)
    d[i] = a[i] * b[i];

    } /* end of sections */

    } /* end of parallel section */

    }


  • Οδηγίες OpenMP

    Οδηγίες Διαμοιρασμού Εργασίας
    Οδηγία SINGLE

    Σκοπός:

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

    Μορφή:

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

    Περιορισμοί:

         Οι περιορισμοί που ισχύουν είναι ότι απαγορεύεται μια διακλάδωση να εισέρχεται σε ένα block οδηγίας single ή να καταλήγει έξω από αυτό και ότι μόνο μία συνθήκη nowait μπορεί να χρησιμοποιηθεί σε κάθε οδηγία single.



    Οδηγίες OpenMP

    Συνδυασμένες Οδηγίες Διαμοιρασμού Εργασίας

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

    α. Οδηγία parallel for.
    β. Οδηγία parallel sections
    γ. Η οδηγία single δεν θα είχε νόημα αφού μιλάμε πάντα για παράλληλες περιοχές διαμοιρασμού εργασίας.

    Στην συνέχεια ακολουθεί δύο παραδείγματα κώδικα με τις οδηγίες parallel for και parallel sctions αντίστοιχα. Ο χαρακτήρας '\' επιτρέπει να γραφτεί η οδηγία σε παραπάνω από μία γραμμές χωρίς να λαμβάνεται ο χαρακτήρας αλλαγής γραμμής, που είναι υποχρεωτικός για τις οδηγίες στη C.


    Παράδειγμα: Οδηγία parallel for

    C / C++ - parallel for Directive Example
    #include <omp.h>
    #define N 1000
    #define CHUNKSIZE 100

    main () {

    int i, chunk;
    float a[N], b[N], c[N];

    /* Some initializations */
    for (i=0; i < N; i++)
    a[i] = b[i] = i * 1.0;
    chunk = CHUNKSIZE;

    #pragma omp parallel for \
    shared(a,b,c,chunk) private(i) \
    schedule(static,chunk)

    for (i=0; i < n; i++)
    c[i] = a[i] + b[i];
    }


    Οδηγίες OpenMP 

    Οδηγίες Συγχρονισμού

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



    Οδηγίες OpenMP 

    Οδηγίες Συγχρονισμού
    Οδηγία BARRIER

    Purpose:

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

    Μορφή:

    Περιορισμοί:

         Προκειμένου να εφαρμοστεί σωστά η οδηγία barrier πρέπει να ισχύουν οι ακόλουθοι περιορισμοί:

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




    Οδηγίες OpenMP 

    Οδηγίες Συγχρονισμού
    Οδηγία MASTER

    Σκοπός:

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

    Μορφή:

    Περιορισμοί:


    Παράδειγμα: Οδηγίες master και barrier

    Στο παράδειγμα που ακολουθεί η εκτύπωση γίνεται μόνο από το master thread. H οδηγία barrier αναγκάζει τα υπολοιπα νήματα να περιμένουν την εκτύπωση ώστε όλα μαζί να ξεκινήσουν το δεύτερο τμήμα των υπολογισμών.

    C / C++ - master and barrirer Directives Example
    #include <omp.h>
    #include <stdio.h>

    int main( )
    {
    int a[5], i;

    #pragma omp parallel
    {
    // Perform some computation.
    #pragma omp for
    for (i = 0; i < 5; i++)
    a[i] = i * i;

    // Print intermediate results.
    #pragma omp master
    for (i = 0; i < 5; i++)
    printf_s("a[%d] = %d\n", i, a[i]);

    // Wait.
    #pragma omp barrier

    // Continue with the computation.
    #pragma omp for
    for (i = 0; i < 5; i++)
    a[i] += i;
    }
    }



    Οδηγίες OpenMP 

    Οδηγίες Συγχρονισμού
    Οδηγία CRITICAL

    Σκοπός:

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

    Μορφή:

    Σημειώσεις:

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

    Περιορισμοί:


    Παράδειγμα: Οδηγία CRITICAL



    Οδηγίες OpenMP 

    Οδηγίες Συγχρονισμού
    Οδηγία ATOMIC

    Σκοπός:

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

    Μορφή:

    Περιορισμοί:


    Παράδειγμα: οδηγία ATOMIC

    C / C++ - atomic Directive Example
    #include <stdio.h>
    #include <omp.h>


    int main() {
    int count = 0;
    {
    #pragma omp atomic
    count++;
    }
    printf_s("Number of threads: %d\n", count);
    }



    Οδηγίες OpenMP 

    Οδηγίες Συγχρονισμού
    Οδηγία FLUSH

    Σκοπός:

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

    Μορφή:

    Σημειώσεις:

         Οι var1, var2, …είναι μια λίστα από μοιραζόμενες μεταβλητές οι οποίες θα εγγραφούν άμεσα στη μνήμη προκειμένου να αποφύγουμε να εγγράψουμε όλες τις μοιραζόμενες μεταβλητές. Αν κάποιο από τα var1, var2, …είναι δείκτης, τότε εγγράφεται ο δείκτης και όχι το αντικείμενο στο οποίο δείχνει.



    Οδηγίες OpenMP 

    Οδηγίες Συγχρονισμού
    Οδηγία ORDERED

    Σκοπός:

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

    Μορφή:

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

    Περιορισμοί:

         Οι περιορισμοί που πρέπει να ισχύουν σε μια οδηγία ordered είναι οι εξής:

    α. Μια οδηγία ordered  μπορεί να εμφανίζεται μόνο σε μια οδηγία for ή parallel for.
    β. Δεν επιτρέπεται η διακλάδωση εκτός ordered for βρόχου.
    γ. Μια επανάληψη ενός βρόχου δεν πρέπει να εκτελεί την ίδια ordered οδηγία παραπάνω από μια φορά και δεν πρέπει να εκτελεί πάνω από μία ordered οδηγίες.


    Παράδειγμα: οδηγία ORDERED

    C / C++ - ordered Directive Example
    #include <stdio.h>
    #include <omp.h>

    static float a[1000], b[1000], c[1000];

    void test(int first, int last)
    {
    int i;
    #pragma omp for schedule(static) ordered
    for (i = first; i <= last; ++i) {
    // Do something here.
    if (i % 2)
    printf("test() iteration %d\n", i);

    }
    }

    void test2(int iter)
    {
    #pragma omp ordered
    printf("test2() iteration %d\n", iter);
    }

    int main( )
    {
    int i;
    #pragma omp parallel
    {
    test(1, 8);
    #pragma omp for ordered
    for (i = 0 ; i < 5 ; i++)
    test2(i);
    }
    }



    Οδηγίες OpenMP 

    Οδηγία THREADPRIVATE

    Σκοπός:

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

    Μορφή:

    Notes:

    Περιορισμοί:

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

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



    Οδηγίες OpenMP 

    Φράσεις Εμβέλειας Δηλώσεων Δεδομένων


    Φράση PRIVATE

    Σκοπός:

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

    Μορφή:

    Σημειώσεις:

  • Σύγκριση μεταξύ της PRIVATE και THREADPRIVATE:

      PRIVATE THREADPRIVATE
    Δεδομένα C/C++: μεταβλητή
    C/C++: μεταβλητή
    Που Δηλώνεται
    Στην αρχή της παράλληλης περιοχής
    Στην αρχή του προγράμματος, μαζί με τις καθολικές μεταβλητές
    Διατήρηση τιμής? Οχι Ναι
    Εμβέλεια Τοπική μεταβλητή ή παράμετρος συνάρτησης Ουσιαστικά καθολική μεταβλητή
    Αρχικοποίηση Με FIRSTPRIVATE Με COPYIN


  • Φράση SHARED

    Σκοπός:

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

    Μορφή:


    Φράση DEFAULT

    Σκοπός:

         Η φράση αυτή χρησιμοποιείται για να ορίσει ένα προκαθoρισμένο τύπο πρόσβασης για όλες τις μεταβλητές σε μια παράλληλη περιοχή περιοχές του κώδικα. Για την C/C++ δεν υπάρχει η δυνατότητα για να οριστούν private μεταβλητές μέσω της default αλλά μπορεί κάποιες υλοποιήσεις να προσφέρουν τη δυνατότητα αυτή. Περιορισμένη χρήση.

    Μορφή:


    Φράση FIRSTPRIVATE Clause

    Σκοπός:

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

    Μορφή:

    Σημειώσεις:


    Φράση LASTPRIVATE

    Σκοπός:

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

    Μορφή:

    Σημείωση:


    Φράση COPYIN

    Σκοπός:

         Με την παράμετρο αυτή μπορούμε να δώσουμε αρχική τιμή σε threadprivate μεταβλητές. Όταν τα νήματα μπαίνουν σε μία παράλληλη περιοχή, οι threadprivate μεταβλητές δεν έχουν κάποια αρχική τιμή. Με την χρήση της παραμέτρου αυτής, μιαι threadprivate μεταβλητή αρχικοποιείται στην τιμή που έχει η μεταβλητή στο master thread.

    Μορφή:


    Φράση COPYPRIVATE

    Σκοπός:

    Μορφή:

    Παράδειγμα: χρήση φράσεων Διαμοιρασμού Δεδομένων:

         Στο παράδειγμα γίνεται χρήση διαφόρων φράσεων.

    C / C++ - data sharing Clauses Example
    #include <stdio.h>
    #include <stdlib.h>
    #include <omp.h>

    #define NUM_THREADS 4
    #define SLEEP_THREAD 1
    #define NUM_LOOPS 2

    enum Types {
    ThreadPrivate,
    Private,
    FirstPrivate,
    LastPrivate,
    Shared,
    MAX_TYPES
    };

    int nSave[NUM_THREADS][MAX_TYPES][NUM_LOOPS] = {{0}};
    int nThreadPrivate;

    #pragma omp threadprivate(nThreadPrivate)
    #pragma warning(disable:4700)

    int main() {
    int nPrivate = NUM_THREADS;
    int nFirstPrivate = NUM_THREADS;
    int nLastPrivate = NUM_THREADS;
    int nShared = NUM_THREADS;
    int nRet = 0;
    int i;
    int j;
    int nLoop = 0;

    nThreadPrivate = NUM_THREADS;
    printf("These are the variables before entry "
    "into the parallel region.\n");
    printf("nThreadPrivate = %d\n", nThreadPrivate);
    printf(" nPrivate = %d\n", nPrivate);
    printf(" nFirstPrivate = %d\n", nFirstPrivate);
    printf(" nLastPrivate = %d\n", nLastPrivate);
    printf(" nShared = %d\n\n", nShared);
    omp_set_num_threads(NUM_THREADS);

    #pragma omp parallel copyin(nThreadPrivate) private(nPrivate) shared(nShared) firstprivate(nFirstPrivate)
    {
    #pragma omp for schedule(static) lastprivate(nLastPrivate)
    for (i = 0 ; i < NUM_THREADS ; ++i) {
    for (j = 0 ; j < NUM_LOOPS ; ++j) {
    int nThread = omp_get_thread_num();

    if (nThread == SLEEP_THREAD)
    system ("sleep 1");
    nSave[nThread][ThreadPrivate][j] = nThreadPrivate;
    nSave[nThread][Private][j] = nPrivate;
    nSave[nThread][Shared][j] = nShared;
    nSave[nThread][FirstPrivate][j] = nFirstPrivate;
    nSave[nThread][LastPrivate][j] = nLastPrivate;
    nThreadPrivate = nThread;
    nPrivate = nThread;
    nShared = nThread;
    nLastPrivate = nThread;
    --nFirstPrivate;
    }
    }
    }

    for (i = 0 ; i < NUM_LOOPS ; ++i) {
    for (j = 0 ; j < NUM_THREADS ; ++j) {
    printf("These are the variables at entry of loop %d of thread %d.\n", i + 1, j);
    printf("nThreadPrivate = %d\n", nSave[j][ThreadPrivate][i]);
    printf(" nPrivate = %d\n", nSave[j][Private][i]);
    printf(" nFirstPrivate = %d\n", nSave[j][FirstPrivate][i]);
    printf(" nLastPrivate = %d\n", nSave[j][LastPrivate][i]);
    printf(" nShared = %d\n\n", nSave[j][Shared][i]);
    }
    }

    printf("These are the variables after exit from the parallel region.\n");
    printf("nThreadPrivate = %d (The last value in the master thread)\n", nThreadPrivate);
    printf(" nPrivate = %d (The value prior to entering parallel region)\n", nPrivate);
    printf(" nFirstPrivate = %d (The value prior to entering parallel region)\n", nFirstPrivate);
    printf(" nLastPrivate = %d (The value from the last iteration of the loop)\n", nLastPrivate);
    printf(" nShared = %d (The value assigned, from the delayed thread, %d)\n\n", nShared, SLEEP_THREAD);
    }



    Φράση REDUCTION

    Σκοπός:

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

    Μορφή:

    Παράδειγμα: REDUCTION - Εσωτερικό Γινόμενο Ανυσμάτων:

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

    C / C++ - reduction Clause Example
    #include <omp.h>

    main () {

    int i, n, chunk;
    float a[100], b[100], result;

    /* Some initializations */
    n = 100;
    chunk = 10;
    result = 0.0;
    for (i=0; i < n; i++)
    {
    a[i] = i * 1.0;
    b[i] = i * 2.0;
    }

    #pragma omp parallel for \
    default(shared) private(i) \
    schedule(static,chunk) \
    reduction(+:result)

    for (i=0; i < n; i++)
    result = result + (a[i] * b[i]);

    printf("Final result= %f\n",result);

    }

    Περιορισμοί: