Δημοσιεύτηκε: 14 Αύγ 2008, 17:54
από Dimitris
Όλο και πιο συχνά ακούγεται στις μέρες μας ο όρος parallel computing, παράλληλοι υπολογισμοί, και έχει γίνει πλέον και στους προσωπικούς υπολογιστές πραγματικότητα με επεξεργαστές διπλών και τρίδιπλων (όπως το λαϊκό) επεξεργαστών. Αλλά ποιος ο λόγος και υπάρχει πράγματι κέρδος από τους επεξεργαστές με δύο πυρήνες ή είναι απλώς ένα εμπορικό παιχνίδι των εταιριών για να αυξήσουν τα κέρδη τους; Αυτή την ερώτηση δυστυχώς αδυνατώ να απαντήσω αλλά θα σας δώσω μια απλή γεύση του πως μπορούμε να εκμεταλλευτούμε αυτή τη δυνατότητα.

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

Ας συνεχίσουμε λοιπόν με shared memory υπολογιστές. Για τα παρακάτω θα χρειαστούν:
Κώδικας: Επιλογή όλων
sudo apt-get install build-essential libgomp

build-essential περιέχει όλα τα απαραίτητα πακέτα για προγραμματισμό και αναπτυξη κώδικα
libgomp περιέχει τη βιβλιοθήκη για shared memory parallel programming

Aναλυτικές οδηγίες για την gnu openMP μπορούν να βρεθούν http://gcc.gnu.org/onlinedocs/libgomp/

Θα ξεκινήσω με ένα απλό hello world πρόγραμμα σε C. Δημιοργούμε καταρχήν ένα φάκελο
Κώδικας: Επιλογή όλων
mkdir ~/testgomp
cd ~/testgomp

Ανοίγουμε μέσα στο φάκελο testgomp με το gedit το αρχείο hello.c
Κώδικας: Επιλογή όλων
gedit hello.c &

Kαι εισάγουμε τα παρακάτω:
Κώδικας: Επιλογή όλων
int main()
{
printf("Hello world\n");
}

To μεταγλωττίζουμε και δημιουργούμε το εκτελέσιμο με:
Κώδικας: Επιλογή όλων
gcc -c hello.c
gcc -o serialtest hello.o

Eκτελούμε το πρόγραμμα που δημιουργήθηκε με
Κώδικας: Επιλογή όλων
./serialtest

και βλέπουμε ότι εκτυπώνεται στην οθόνη Hello world μόνο μία φορά.

Tώρα προσθέτουμε τη γραμμή #pragma omp parallel δηλαδή το αρχείο θα πρέπει να φαίνεται:
Κώδικας: Επιλογή όλων
int main()
{
#pragma omp parallel
printf("Hello world\n");
}

To μεταγλωττίζουμε (τώρα χρειάζεται να συνδέσουμε επίσης την αντίστοιχη βιβλιοθήκη) και δημιουργούμε το εκτελέσιμο με:
Κώδικας: Επιλογή όλων
gcc -c -fopenmp hello.c
gcc -o paralleltest hello.o

Εκτελούμε τώρα το παράλληλο πρόγραμμα με
Κώδικας: Επιλογή όλων
./paralleltest

και βλέπουμε να τυπώνεται δύο φορές ενώ υπάρχει μόνο μία φορά στον κώδικά μας και δεν υπάρχει φυσικά κανένας βρόγχος. (Φυσικά αν έχουμε επεξεργαστή με ένα μόνο πυρήνα δε θα δούμε διαφορά)
Η γραμμή #pragma omp parallel αντιλαμβάνεται από το μεταγλωττιστή gcc σαν σχόλιο αν δεν υπάρχει η επιλογή -fopenmp κατά τη μεταγλώττιση, πράμα που επιτρέπει ένα πρόγραμμα που γράφουμε να είναι αρκετά portable.

Φυσικά αυτό δεν είναι και πολύ χρήσιμο γιατί επαναλαμβάνει την ίδια δουλειά δύο φορές. Εμείς χρειαζόμαστε κάτι που να μοιράζει το φόρτιο εργασίας στους δύο επεξεργαστές.
Aς δημιουργήσουμε τώρα ένα άλλο αρχείο hello2.c που θα περιέχει τα εξης:
Κώδικας: Επιλογή όλων
#define N 100000
int main()
{
  int i, a[N];
  #pragma omp parallel for
  for (i=0;i<N;i++)
     a[i]= 2*i;
  return 0;
}

Και μεταγλώττιση όπως πάντα με
Κώδικας: Επιλογή όλων
gcc -c -fopenmp hello2.c
gcc -o paralleltest2 hello2.o

Το συγκεκριμένο παράδειγμα μοιράζει αυτόματα το array a[N] στα δύο threads που δημιουργούνται. Τα αποτελέσματα είναι σωστά γιατί το δεξί μέλος της ισότητας δεν εξαρτώνται από το αριστερό.

Aν είχαμε όμως κάτι όπως αυτό:
Κώδικας: Επιλογή όλων
#define N 100000
int main()
{
  int i, a[N];
  a[0]=0;
  #pragma omp parallel for
  for (i=1;i<N;i++)
     a[i]= a[i-1]+1;
  return 0;
}

δε θα λειτουργούσε γιατι κάθε επόμενο στοιχείο του a θα εξαρτάται από το προηγούμενο, αρα για να υπολογισει το στοιχειο 50000 στο δεύτερο thread θα χρειάζεται το 49999 από το πρώτο thread το οποίο δεν έχει υπολογιστεί ακομη.

Σε distributed memory υπολογιστές όπου τα δεδομένα, το a στο παράδειγμά μας, δεν είναι σε κοινή μνήμη θα πρέπει να μεταφέρονται από επεξεργαστή σε επεξεργαστή (για περισσότερες πληροφοριές δες http://en.wikipedia.org/wiki/Message_Passing_Interface) Yπάρχουν διάφορα εργαλεία για να στηθεί ένα cluster (distributed memory υπολογιστής) φυσικά και σε linux.

Βιβλιογραφία
1. http://openmp.org/wp/
2. http://gcc.gnu.org/onlinedocs/libgomp/
3. http://en.wikipedia.org/wiki/OpenMP
4. http://en.wikipedia.org/wiki/Parallel_computing

Creative Commons License
Η εργασία υπάγεται στην άδεια Creative Commons Αναφορά-Παρόμοια διανομή 3.0 Ελλάδα