You are on page 1of 14

Εργαστήριο Παράλληλης Κατανεμημένης Επεξεργασίας

0 Βασική χρήση του περιβάλλοντος MPI

01. Σύνδεση με το cluster


Η επικοινωνία του χρήστη με το περιβάλλον μεταγλώττισης και εκτέλεσης προγραμμάτων MPI
γίνεται μέσω του front-end του cluster, το οποίο συνήθως παίζει και το ρόλο του master node κατά
την εκτέλεση των προγραμμάτων. Όλες οι επικοινωνίες (με τις ροές stdin, stdout, stderr) αλλά και
οι λειτουργίες διαχείρισης αρχείων (fopen, fclose, fread, frwite κλπ) συνήθως εκτελούνται στο
front-end.

Στη περίπτωσή μας, είτε χρησιμοποιούμε έναν από τους υπολογιστές του εργαστηρίου ή ένα
απομακρυσμένο σύστημα, συνδεόμαστε ως mpiuser με το front-end του cluster με ssh

$ssh mpiuser@195.251.209.11

To password ακολουθεί τη γνωστή σύμβαση του PdPLab. T

o συγκεκριμένο ip αναφέρεται στο cluster με front-end τον υπολογιστή με το όνομα eleni.


Υπάρχουν και άλλα διαθέσιμα clusters, όπως το electra (ip 195.251.222.239), cuda (ip
195.251.222.226) και το blade (ip 195.251.222.194). Όλα έιναι ομογενή αλλά τo καθένα έχει
ιδιαίτερα χαρακτηριστικά (αριθμό και τύπο συστημάτων). Σε γενικές γραμμές οι ακόλουθες οδηγίες
ισχύουν για όλα τα συστήματα, όμως υπάρχουν προφανείς διαφοροποιήσεις στα ονόματα
συστημάτων, καταλόγων κλπ.

Συνδεόμαστε στο home directory του mpiuser, ο οποίος, αποτελεί τμήμα ενός καταλόγου mirror
που προσαρτάται (mounted) μέσω nfs σε όλους τους κόμβους που ανήκουν στο cluster. Επομένως,
ως mpiuser, από οποιοδήποτε σύστημα και αν συνδεθείτε θα καταλήξετε στο ίδιο home directory,

mpiuser@eleni:~$ pwd 
/mirror/mpiuser 

Τη πρώτη φορά που θα συνδεθούμε δημιουργούμε έναν υποκατάλογο με όνομα τον login name μας
(itXXXX ή maiXXXX). Από εδώ και στο εξής εργαζόμαστε σε αυτό το κατάλογο. Δεν αφήνουμε
'σκουπίδια' στο home directory, ούτε διαγράφουμε αρχεία που υπάρχουν εκεί.

Αν στο ssh προσθέσουμε την επιλογή -Χ τότε η σύνδεση επιτρέπει και τη χρήση απομακρυσμένων
εφαρμογών γραφικών. Έτσι μπορούμε να δουλέψουμε σε απομακρυσμένο editor ή άλλο
προγραμματιστικό περιβάλλον (πχ απομακρυσμένο eclipse). Γενικά η λύση αυτή δε συνιστάται σε
απομακρυσμένες συνδέσεις (εκτός εργαστηρίου).

Αν ο τοπικός μας υπολογιστής έχει λειτουργικό σύστημα MS-Windows τότε μια λύση είναι η
χρήση putty για ssh client. H χρήση απομακρυσμένων γραφικών (σε αντιστοιχία με το ssh -X)
απαιτεί χρήση xming σε συνδυασμό με putty.

Μια άλλη λύση (πιθανότατα η πιο βολική) είναι η χρήση τοπικού editor σε συνδυασμό με
απομακρυσμένη αποθήκευση και εκτέλεση. Για το σκοπό αυτό μπορούμε να χρησιμοποιήσουμε
λογισμικά που συνδυάζουν sftp και ssh. Στην ουσία συνδεόμαστε μέσω ssh στον
απομακρυσμένο/δικτυακό κατάλογο mirror και στη συνέχεια, με χρήση τοπικών εφαρμογών,
γράφουμε το πρόγραμμά μας, το αποθηκεύουμε στο απομακρυσμένο σύστημα αρχείων και το
εκτελούμε εκεί. Τέτοιες λύσεις προσφέρουν εφαρμογές όπως το filezilla, gftp αλλά και η 'Σύνδεση
με απομακρυσμένο εξυπηρετητή' στους σταθμούς εργασίας Ubuntu.

02. Έλεγχος περιβάλλοντος


Πριν χρησιμοποιήσουμε το MPI ελέγχουμε δύο πράγματα:

(i) Αν υπάρχουν συνδεδεμένοι χρήστες που εργάζονται

mpiuser@eleni:~$ who 
mpiuser  pts/0        2010­10­11 19:19 (pdp01) 
mpiuser  pts/1        2010­10­12 09:07 (kmarg­laptop.local) 
mpiuser  pts/2        2010­10­11 19:19 (pdp10) 
mpiuser  pts/5        2010­10­11 19:21 (pdp12) 
mpiuser  pts/6        2010­10­11 19:21 (pdp11) 
mpiuser  pts/7        2010­10­11 19:30 (pdp04) 
mpiuser  pts/9        2010­10­11 19:22 (pdp09) 

Εδώ βλέπουμε αρκετούς 'παλιούς' χρήστες, που σημαίνει οτι μάλλον ξεχάσαν να αποσυνδεθούν με
exit, αλλά μπορούμε να ελέγξουμε τι εκτελούν με

mpiuser@eleni:~$ ps ­u mpiuser 
  PID TTY          TIME CMD 
19947 ?        00:00:00 sshd 
19948 pts/0    00:00:00 bash 
20137 ?        00:00:00 sshd 
20138 pts/2    00:00:00 bash 
20431 ?        00:00:00 sshd 
20432 pts/5    00:00:00 bash 
20534 ?        00:00:00 sshd 
20535 pts/6    00:00:00 bash 
20814 ?        00:00:00 sshd 
20815 pts/9    00:00:00 bash 
20883 ?        00:00:01 python2.6 
21006 ?        00:00:00 sshd 
21007 pts/7    00:00:00 bash 
21123 pts/7    00:00:00 nano 
24528 ?        00:00:00 sshd 
24529 pts/1    00:00:00 bash 
24591 pts/1    00:00:00 ps 

Διαπιστώνουμε οτι κανείς δε κανει χρήση του MPI, άρα είναι παλιές συνδέσεις.. Δε τους κλείνουμε,
αν θέλουμε ενημερώνουμε το διαχειριστή (kmarg@uom.gr) να τους 'καθαρίσει'.

Ηθικό δίδαγμα: όταν τελειώνουμε τη δουλειά μας αποσυνδεόμαστε από το front-end με exit.

(ii) Ελέγχουμε αν ο MPI daemon είναι σε λειτουργία. O δαίμονας λέγεται mpd, και ο έλεγχος
γίνεται με τη εντολή

mpiuser@eleni:~$ mpdtrace 
eleni 
titan
ikaros
pdp01 
pdp04 
pdp10 
pdp11 
pdp12 
pdp09 
pdp03 
pdp02 
pdp08 
pdp07 
pdp05 
pdp06 

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

mpiuser@eleni:~$mpdtrace 
mpdtrace: cannot connect to local mpd (....); possible causes: 
  1. no mpd is running on this host 
  2. an mpd is running but was started without a "console" (­n option) 
In case 1, you can start an mpd on this host with: 
    mpd & 
and you will be able to run jobs just on this host. 
For more details on starting mpds on a set of hosts, see 
the MPICH2 Installation Guide. 

Σε αυτή τη περίπτωση ξεκινούμε το δαίμονα με την εντολή

mpiuser@eleni:~$ mpdboot ­n 15

που σημαίνει οτι συνδέουμε 15 υπολογιστές στο cluster μας, το fronr-end eleni και 14 nodes, τα
titan, ikaros και από pdp01 έως pdp12. Το αρχείο mpd.hosts που βρίσκεται στο home directory του
mpiuser δηλώνει τα συστήματα όπου θα εκτελέσουν τον mpd δαίμονα (θα είναι στο mpd ring). Σε
πεπίπτωση που κάποιο σύστημα παρουσιάσει βλάβη το 'ακυρώνουμε' απλά θέτοντας ένα #
μπροστά από το όνομά του στο αρχείο mpd.hosts. Στη συνέχεια εκτελούμε

mpiuser@eleni:~$ mpdallexit

για να τερματίσουμε το δαίμονα σε όλα τα συστήματα και μετά

mpiuser@eleni:~$ mpdboot ­n 14

ώστε να ξανα-ξεκινήσει σε όλα τα συστήματα εκτός από αυτό που είχε πρόβλημα.

Η εντολή mpdhelp δίνει κάποιες απλές πληροφορίες για τη διαχείριση του mpd

mpiuser@eleni:~$ mpdhelp 

The following mpd commands are available.  For usage of any specific one, 
invoke it with the single argument ­­help . 

mpd           start an mpd daemon 
mpdtrace      show all mpd's in ring 
mpdboot       start a ring of daemons all at once 
mpdringtest   test how long it takes for a message to circle the ring 
mpdexit       remove one mpd from the ring 
mpdallexit    take down all daemons in ring 
mpdcleanup    repair local Unix socket if ring crashed badly 
mpdlistjobs   list processes of jobs (­a or ­­all: all jobs for all users) 
mpdkilljob    kill all processes of a single job 
mpdsigjob     deliver a specific signal to the application processes of a job 
mpiexec       start a parallel job 

Each command can be invoked with the ­­help argument, which prints usage 
information for the command without running it. 

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

03. Συγγραφή, μεταγλώττιση και εκτέλεση προγράμματος MPI


Για editing χρησιμοποιούμε τον διορθωτή nano

mpiuser@eleni:~/kmarg$ nano hello.c 

GNU nano 2.2.2               File: hello.c                          Modified  

#include "mpi.h" 
#include <stdio.h> 

int main(argc,argv) 
int argc; 
char *argv[]; { 
  int  numtasks, rank, rc; 

  rc = MPI_Init(&argc,&argv); 
  if (rc != MPI_SUCCESS) { 
    printf ("Error starting MPI program. Terminating.\n"); 
    MPI_Abort(MPI_COMM_WORLD, rc); 
  } 

  MPI_Comm_size(MPI_COMM_WORLD,&numtasks); 
  MPI_Comm_rank(MPI_COMM_WORLD,&rank); 
  printf ("Number of tasks= %d My rank= %d\n", numtasks,rank); 

  /*******  do some work *******/ 

  MPI_Finalize(); 

^G Get Help  ^O WriteOut  ^R Read File ^Y Prev Page ^K Cut Text  ^C Cur Pos 
^X Exit      ^J Justify   ^W Where Is  ^V Next Page ^U UnCut Text^T To Spell 

Δέχεται copy-paste από άλλες εφαρμογές, σώζει (χωρίς να τερματίσει με ^0, τερματίζει με ^Χ.
Μπορείτε να διορθώνετε το πρόγραμμα στο προσφιλές σας περιβάλλον (τοπικά) και να το
μεταφέρετε με copy-paste ή με sftp στο front-end μόνο για την μεταγλώττιση και εκτέλεση.
Προτεινόμενες λύσεις Οι εφαρμογές putty, filezilla ή winscp.

Για τη μεταγλώττιση εισάγουμε


mpiuser@eleni:~/kmarg$ mpicc ­o hello hello.c 

Για να παραχθεί εκετελέσιμο με όνομα hello. Η εντολή mpicc ακολυθεί τις συμβάσεις της gcc. Για
παράδειγμα αν το πρόγραμμά μας χρησιμοποιεί τη βιβλιοήκη math.h τότε η μεταγλώττιση πρέπει
να έχει τη μορφή
mpiuser@eleni:~/kmarg$ mpicc ­lm ­o hello hello.c 
Για την εκτέλεση σε 4 κόμβους γράφουμε
mpiuser@eleni:~/kmarg$ mpiexec ­n 4 ./hello

Προσοχή στο ./ για να αναζητήσει το εκτελέσιμο στο τρέχοντα κατάλογο. Παρατίθεται σύντομη
βοήθεια για το mpiexec.
mpiuser@eleni:~$ mpiexec ­­help 

usage: 
mpiexec [­h or ­help or ­­help]    # get this message 
mpiexec ­file filename             # (or ­f) filename contains XML job 
description 
mpiexec [global args] [local args] executable [args] 
   where global args may be 
      ­l                           # line labels by MPI rank 
      ­bnr                         # MPICH1 compatibility mode 
      ­machinefile                 # file mapping procs to machines 
      ­s <spec>                    # direct stdin to "all" or 1,2 or 2­4,6 
      ­1                           # override default of trying 1st proc locally 
      ­ifhn                        # network interface to use locally 
      ­tv                          # run procs under totalview (must be ...
      ­tvsu                        # totalview startup only 
      ­gdb                         # run procs under gdb 
      ­m                           # merge output lines (default with gdb) 
      ­a                           # means assign this alias to the job 
      ­ecfn                        # output_xml_exit_codes_filename 
      ­recvtimeout <integer_val>   # timeout for recvs to fail (e.g. from mpd ..
      ­g<local arg name>           # global version of local arg (below) and... 
      ­n <n> or ­np <n>            # number of processes to start 
      ­wdir <dirname>              # working directory to start in 
      ­umask <umask>               # umask for remote process 
      ­path <dirname>              # place to look for executables 
      ­host <hostname>             # host to start on 
      ­soft <spec>                 # modifier of ­n value 
      ­arch <arch>                 # arch type to start on (not implemented) 
      ­envall                      # pass all env vars in current environment 
      ­envnone                     # pass no env vars 
      ­envlist <list of env var names> # pass current values of these vars 
      ­env <name> <value>          # pass this value of this env var 
mpiexec [global args] [local args] executable args : [local args] executable... 
mpiexec ­gdba jobid                # gdb­attach to existing jobid 
mpiexec ­configfile filename       # filename contains cmd line segs as lines 
  (See User Guide for more details) 

Examples: 
   mpiexec ­l ­n 10 cpi 100 
   mpiexec ­genv QPL_LICENSE 4705 ­n 3 a.out 

   mpiexec ­n 1 ­host foo master : ­n 4 ­host mysmp slave 

Επισημαίνουμε οτι τυπικά μόνο το front-end έχει επικοινωνία με το χρήστη. Διαπιστώνουμε οτι
υπάρχουν πολλοί εναλλακτικοί τρόποι εκτέλεσης αλλά ξεπερνούν την απλή χρήση του MPI. Ένα
ενδεικτικό machine file με όνομα mf (ή mfcore) μπορείτε να βρείτε στο home directory του
mpiuser. Ο αριθμός δίπλα στο όνομα του μηχανήματος ενημερώνει το σύστημα εκτέλεσης για τον
αριθμό επεξεργαστών ανά σύστημα.
mpiuser@eleni:~$ cat mf 
eleni:4 
titan:4
ikaros:16
pdp01:2 
pdp02:2 
pdp03:2 
pdp04:2 
pdp05:2 
pdp06:2 
pdp07:2 
pdp08:2 
pdp09:2 
pdp10:2 
pdp11:2 
pdp12:2 

εναλλακτικά μπορεί να διαπιστώσετε οτι το όνομα του υπολογιστή επαναλαμβάνεται για τόσες
γραμμές όσοι είναι οι επεξεργαστές του (αρχείο mfm). Μια κυκλική ανάθεση των εργασιών (μια
ανά υπολογιστή, κατ' αρχήν) μπορεί να επιτευχθεί με το αρχείο mfsingle. Το αρχείο mf32 αποκλείει
τα titan, ikaros που έχουν λειτουργικό 64 bits. Παρακάτω, στον υβριδικό προγραμματισμό,
συζητούμε τη χρήση του machinefile.

Σε περίπτωση που θέλουμε να σώσουμε αποτελέσματα πειραμάτων (πχ μετρήσεις χρόνου)


μπορούμε να γράψουμε

mpiuser@eleni:~/kmarg$ mpiexec ­machinefile mf32 ­n 4 ./hello > hello.results

ώστε να κατευθύνουμε τα αποτελέσματα σε ένα αρχείο αντί στο stdout. Ανάλογα μπορούμε να
ανακατευθύνουμε το stdin. Στις παρακάτω εντολές η επιλογή -machinefile mf32 παραλείπεται για
λόγους συντομίας.
mpiuser@eleni:~/kmarg$ mpiexec ­n 4 ./hello < hello.input > hello.results

Αν θέλουμε να εκτελέσουμε σειρά πειραμάτων μπορούμε να δημιουργήσουμε ένα shell script


γι'αυτο το σκοπό. Πχ γράφουμε στο nano ένα κείμενο
#!/bin/bash
mpiexec ­n 1 ./hello < hello.input > hello.results.1
mpiexec ­n 2 ./hello < hello.input > hello.results.2
mpiexec ­n 4 ./hello < hello.input > hello.results.4
mpiexec ­n 8 ./hello < hello.input > hello.results.8

το οποίο σώζουμε ως hello.experinemnt και το μετατρέπουμε σε εκτελέσιμο


mpiuser@eleni:~/kmarg$ chmod u+x hello.experiment

Στη συνέχεια το εκτελούμε ως


mpiuser@eleni:~/kmarg$ ./hello.experiment

1 Βασική χρήση του OpenMP

10 Σύνδεση
Για τη χρήση του OpenMP δεν απαιτείται ιδιαίτερη υποδομή, παρά μόνο ένα τουλάχιστο διπύρηνο
σύστημα με σύγχρονο μεταγλωττιστή C. Επιμένως μπορούμε να συνδεθούμε απομακρυσμένα είτε
ως pdpuser είτε ως mpiuser στο 195.251.209.11 (eleni). Φυσικά αν συνδεθούμε ως pdpuser θα
βλέπουμε το home directory του pdpuser, άρα καλό είναι να δημιουργήσουμε πάλι έναν
υποκατάλογο με το login name μας.

Για τοπική σύνδεση, αν συνδεθούμε ως pdpuser θα έχουμε πρόσβαση σε έναν από τους διπύρηνους
κόμβους pdp01 έως pdp12 και όχι τετραπύρηνο στο eleni. Επομένως, αν θέλετε να σώσετε στο
τέλος τη δουλειά σας θα πρέπει να την αποθηκεύσετε κάπου με κάπως περισσότερη ασφάλεια, πχ
στον υποκατάλογό σας στο eleni.

11 Έλεγχος OpenMP
Ο μόνος έλεγχος που απαιτείται είναι να βεβαιωθούμε οτι ο μεταγλωττιστής C υποστηρίζει
OpenMP. Στη περίπτωσή μας, για τη gcc

pdpuser@eleni:~$ man gcc | grep [oO]pen[mM][pP] 
           ­fopenmp ­fms­extensions ­trigraphs  ­no­integrated­cpp 
           ­fopenmp 
           Enable handling of OpenMP directives "#pragma omp" in C/C++ and 
           "!$omp" in Fortran.  When ­fopenmp is specified, the compiler 
           generates parallel code according to the OpenMP Application Program 
           Interface v2.5 <http://www.openmp.org/>.  This option implies 

ή ελέγχουμε η έκδοση να είναι 4.2 και ανώτερη

pdpuser@eleni:~$ gcc ­v 
Using built­in specs. 
Target: i486­linux­gnu 
Configured with: ../src/configure ­v ­­with­pkgversion='Ubuntu 4.4.3­4ubuntu5' 
­­with­bugurl=file:///usr/share/doc/gcc­4.4/README.Bugs ­­enable­languages=c,c+
+,fortran,objc,obj­c++ ­­prefix=/usr ­­enable­shared ­­enable­multiarch ......
Thread model: posix 
gcc version 4.4.3 (Ubuntu 4.4.3­4ubuntu5) 

12 Συγγραφή, μεταγλώττιση και εκτέλεση προγράμματος OpenMP

Όλη η διαδικασία είναι ίδια με τη συμβατική διαδικασία ανάπτυξης προγράμματος σε C. Η μόνη


διαφορά είναι οτι στα includes πρεπει να προσθέσουμε το #include <omp.h> και στη μεταγλώττιση
πρέπει να συνδέσουμε τη βιβλιοθήκη OpenMP, θέτοντας την επιλογή -fopenmp

pdpuser@eleni:~$ gcc ­o hello ­fopenmp hello.c 

Παρακάτω δίνεται ένα ενδεικτικό πρόγραμμα για έλεγχο λειτουργίας

# include <stdlib.h> 
# include <stdio.h> 
# include <omp.h> 

int main ( int argc, char *argv[] ) 

  int nthreads, size, rank; 
  
  size = omp_get_num_procs ( ); 
  printf (  "There are %d processors\n", size); 
  size = omp_get_max_threads ( ); 
  printf (  "By default there are %d threads\n", size); 
  nthreads = 4; 
  omp_set_num_threads ( nthreads ); 
    
  #pragma omp parallel private ( rank ) 
  { 
rank = omp_get_thread_num ( ); 
      printf ( "  This is thread %d\n", rank ); 
  } 

  return 0; 
}

Κατα τα λοιπά ισχύουν όσα ήδη αναφέρθηκαν για ανακατεύθυνση αρχείων και shell scripts.

13 Υβριδικός παράλληλος προγραμματισμός σε MPI / OpenMP


Μπορούμε να συνδυάσουμε ΜPI και OpenMP στο ίδιο πρόγραμμα αν στα includes προσθέσουμε
και τις δύο βιβλιοθήκες ενώ στη μεταγλώττιση γράψουμε

pdpuser@eleni:~$ mpicc ­o hello ­fopenmp hello.c 

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

# include <stdlib.h> 
# include <stdio.h> 
# include <omp.h> 
# include "mpi.h" 
 
int main ( int argc, char *argv[] ) 

  int thread_size, thread_rank, proc_size, proc_rank; 
  
  MPI_Init( &argc, &argv ); 
  MPI_Comm_size( MPI_COMM_WORLD, &proc_size ); 
  MPI_Comm_rank( MPI_COMM_WORLD, &proc_rank ); 
   
  thread_size = omp_get_max_threads ( ); 
  omp_set_num_threads ( thread_size ); 
    
  #pragma omp parallel private ( thread_rank ) 
  { 
      rank = omp_get_thread_num ( ); 
      printf( "Hello world from process %d of %d\n. This node has %d cores. This 
is thread %d", proc_rank, proc_size, thread_size, thread_rank); 
  } 

  MPI_Finalize(); 
  return 0; 
}

Η εκτέλεση επιτυγχάνεται ακριβώς όπως και στη περίπτωση προγραμμάτων MPI, δηλαδή με κλήση
της mpiexec.
14 Χρήση machinefile
Σε αυτό το σημείο είναι ενδιαφέρον να σχολιαστούν οι εναλλακτικές δυνατότητες υβριδικού
παραλληλισμού που μπορούν να επιτευχθούν από το συνδυασμό ΜPI, OpenMP και συστήματος
εκτέλεσης του MPI (δηλαδή του mpd δαίμονα).

Όπως έχει εξηγηθεί παραπάνω, τo αρχείο mpd.hosts ενημερώνει το δαίμονα mpd για τους
διαθέσιμους πυρήνες ανά υπολογιστή. Όταν εκτελούμε μια εργασία MPI με την mpiexec οι
πληροφορίες του δαίμονα mpd, περνούν στο σύστημα εκτέλεσης. Έτσι η απεικόνιση εργασιών
γίνεται τυχαία, με βάση τις πληροφορίες του αρχείου mpd.hosts. Μπορεί επομένως, εν επιλέξουμε
mpiexec -n 4 να γίνει ανάθεση 2 εργασιών ΜPI στο eleni και απο μια σε άλλα δύο συστήματα κλπ.
Κάθε εκτέλεση μπορεί να έχει και διαφορετική ανάθεση εργασιών.

Μέχρι τώρα χρησιμοποιήσαμε την επιλογή -machinefile mf32 για να αποφύγουμε την ετερογένεια
των λειτουργικών συστημάτων (32 και 64 bits). Για να χρησιμοποιήσουμε αυτά τα συστήματα
πρέπει να κάνουμε static linking ώστε το εκτελέσιμο να μην αναζητά ανύπαρκτη βιβλιοθήκη 32 bits
σε συστήματα των 64 bits. Παρακάτω παρατίθεται ένα ενδεικτικό Makefile το οποίο αντικαθιστά
την εντολή mpicc -fopenmp.

CC = gcc
TARGET = exec
OBJS= main.o first.o second.o
CPPFLAGS=­DHAVE_CONFIG_H ­DHAVE_STRING_H  ­I. ­I/mirror/mpich2/include ­Wall ­O2 
­funroll­loops ­fopenmp
LDFLAGS=­L/mirror/mpich2/lib ­L/mirror/mpich2/lib ­lopa ­lpthread ­lrt ­static 
­lmpich

all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CPPFLAGS)  $(OBJS) ­o $(TARGET) $(LDFLAGS)
main.o: main.c
$(CC) $(CPPFLAGS) ­c main.c
first.o: first.c
$(CC) $(CPPFLAGS) ­c first.c
second.o: second.c
$(CC) $(CPPFLAGS) ­c second.c
clean:
rm ­f *.o *.d $(TARGET) core

Αν θέλουμε να είμαστε ακριβέστεροι στην απεικόνιση εργασιών, μπορούμε να χρησιμοποιήσουμε


την επιλογή -machinefile σε συνδυασμό με εξειδικευμένο αρχείο. Σε αυτή τη περίπτωση, αντί για
τη τυχαία επιλογή από το mpd.hosts, η mpiexec επιλέγει αυστηρά με τη σειρά του αρχείου μας.
Εκτός από τα αρχεία mf ή mfcore και mf32 που έχουν ήδη παρουσιαστεί, το αρχείο mfm έχει
πολλαπλές σειρές για κάθε σύστημα (μια γραμμή για κάθε πυρήνα) και το αρχείο mfsingle έχει
πάλι πολλαπλές γραμμές αλλά σε κυκλική διάταξη, ώστε να εξαντλούνται οι αναθέσεις σε όλα τα
συστήματα από μια φορά πριν πάμε σε δεύτερη επιλογή. Έτσι μπορούμε να έχουμε τριών
διαφορετικών τύπων απεικονίσεις: τυχαία (με mfcores), μέγιστη ανάθεση σε κάθε υπολογιστή
(mfm) και κυκλικλη ανάθεση (mfsingle).

Η κλήση της mpiexec έχει τη μορφή

mpiexec ­machinefile mfm ­n 8 ./hello 

Παρατηρούμε οτι με τη βοήθεια του machinefile mfm μπορούμε να πετύχουμε αποτελέσματα


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

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

2 Στοιχειώδης εκσφαλμάτωση παράλληλων προγραμμάτων


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

Τόσο η σχεδίαση όσο και η εκσφαλμάτωση της εφαρμογής καλό είναι να χωρίζεται σε επι μέρους
επίπεδα. Εδώ παρουσιάζεται μια προσέγγιση top-down.

Γενικά το πρόγραμμα συνήθως έχει τη μορφή:

Αρχικοποίηση παράλληλου περιβάλλοντος
Εισαγωγή, Προ­επεξεργασία δεδομένων από το Κυρίως Πρόγραμμα

/* Αρχή παράλληλης εκτέλεσης και μετρήσεων */
Διανομή ή Ανάθεση Δεδομένων στις Παράλληλες Εργασίες από το Κυρίως Πρόγραμμα
Εκτέλεση Υπολογισμών (μπορεί να περιλαμβάνουν επικοινωνία ή επαναλήψεις)
Συλλογή Δεδομένων από τις Παράλληλες Εργασίες προς το Κυρίως Πρόγραμμα
/* Τέλος παράλληλης εκτέλεσης και μετρήσεων */

Μετά­επεξεργασία και Εξαγωγή δεδομένων από το Κυρίως Πρόγραμμα
Κλείσιμο του παράλληλου περιβάλλοντος

21 Αρχικός σχεδιασμός
Πρώτα πρέπει να αποφασίσουμε

Επιμερισμός
• Ποιούς υπολογισμούς θα εκτελεί το Κυρίως Πρόγραμμα και ποιούς οι Εργασίες;
• Ποια δεδομένα είναι διαμοιραζόμενα (καθολικά) και ποια ιδιωτικά (τοπικά);
• Ποια δεδομένα θα αποσταλούν / ανατεθούν;
• Με ποιες δομές δομές δεδομένων τα αποθηκεύει το Κυρίως Πρόγραμμα και με ποιες οι
Εργασίες;

Επικοινωνία
• Ποιες τεχνικές διανομής / ανάθεσης δεδομένων θα χρησιμοποιηθούν;
• Ποιες τεχνικές συλλογής δεδομένων θα χρησιμοποιηθούν;
• Ποιές τεχινικές επικοινωνίας και συγχρονισμού απαιτούνται κατά την εκτέλεση των
υπολογισμών;
22 Υπολογισμοί
Οι υπολογισμοί μπορούν να ελεγχθούν σε ακολουθιακό περιβάλλον, αφού στον πυρήνα της
εφαρμογής μας υπάρχει ένας ακολουθιακός κώδικας που εκτελείται από κάθε Εργασία, όμοιος ή
διαφορετικός. Δημιουργούμε το κατάλληλο περιβάλλον με απλές αναθέσεις τιμών σε δομές
δεδομένων ώστε να βρισκόμαστε στην ίδια ακριβώς κατάσταση που θα βρεθούμε μετά από την
διανομή/ανάθεση δεδομένων. Κατόπιν εκτελούμε τους υπολογισμούς και ελέγχουμε το αποτέλεσμα
που θα πρέπει να συλλεχθεί. Ελέγχουμε διάφορους πιθανούς συνδυασμούς τιμών (πχ για διάφορες
τιμές rank, size, tid, num_threads, μέγεθος προβλήματος) ώστε να είμαστε σίγουροι οτι δε θα
προκύψουν δείκτες εκτός ορίων κλπ.

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

24 Αρχικοποιήσεις
Είναι σημαντικό να αρχικοποιούμε δομές δεδομένων πριν τις χρησιμοποιήσουμε σε επικοινωνία
ώστε να μην έχουμε null pointers ή περίεργες αριθμητικές τιμές.

25 Comment-Out και Printf


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

26 Διαχείριση Αρχείων
Σε περίπτωση εκτέλεσης προγράμματος μόνο με OpenMP σε τοπικό κατάλογο, ισχύουν αυτά που
γνωρίζουμε από το συμβατικό προγραμματισμό. Σε περίπτωση χρήσης MPI ή υβριδικού
προγράμματος, οι αναγνώσεις / εγγραφές σε αρχεία, εφ' όσον βρισκόμαστε στο κατάλογο mirror,
εκτελούνται με τη βοήθεια του nfs, δηλαδή φαίνονται ως 'τοπικές' για όλους τους κόμβους του
cluster. Αυτό σίγουρα επιβαρύνει τη διαδικασία ανάγνωσης εγγραφής, αλλά είναι μάλλον λιγότερο
επιβαρυντικό από τη ρητή χρήση διανομής και συλλογής μέσω του προγράμματος. Η χρήση
πραγματικά τοπικών αρχείων σε κάθε κόμβο μπορεί να επιτευχθεί με χρήση απολύτου ονόματος
διαδρομής (πχ /home/pdpuser/file_name). Φυσικά θα πρέπει να υπάρχουν τα κατάλληλα
δικαιώματα και τα αρχεία να έχουν προ-αποθηκευτεί.
27 Διαχείριση μνήμης
Συνήθως τα δεδομένα που χρησιμοποιούμε είναι αρκετά μεγάλα ώστε δοκιμάζουν τα όρια αντοχής
των συστημάτων. Για παράδειγμα ένα αρχείο 2Gbytes αν φορτωθεί σε πίνακα στη RAM θα
καταλάβει μεγάλο μέρος της διαθέσιμης RAM. Ένας δι-διάστατος πίνακας 10000 αριθμών διπλής
ακρίβειας απαιτεί 8 * 10^8 bytes ή περίπου 2^3 * 2^28 = 2 Gbytes!! Αν σκεφτούμε οτι το address
space σε σύστημα 32bit είναι 4 Gbytes καταλαβαίνουμε οτι αυτό αποτελεί και ένα λογικό – και
φυσικό όριο.

Η στατική ανάθεση μνήμης, της μορφής int a[N]; περιορίζεται από το μέγεθος του τμήματος
δεδομένων που εκχωρεί το σύστημα σε μια διεργασία.

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

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

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

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

3 Απεικόνιση αποτελεσμάτων
Συνήθως τα αποτελέσματα εκτέλεσης των παράλληλων προγραμμάτων περιλαμβάνουν μετρήσεις
απόδοσης. H εφαρμογή gnuplot μπορεί να βοηθήσει στην γρήγορη παραγωγή γραφικών
απεικονίσεων των αποτελεσμάτων αυτών.

31 Προετοιμασία
Πρώτα ελέγχουμε τα αποτελέσματα στην οθόνη. Τα αποτελέσματα πρέπει να εμφανίζονται στην
οθόνη σε στήλες, με τους αριθμούς χωρισμένους με κενά ή tab, χωρίς άλλα επεξηγηματικά σχόλια
μεταξύ των αριθμών. Κάθε στήλη πρέπει να αντιστοιχεί σε μια ποσότητα, συνήθως η πρώτη στήλη
θα απεικονιστεί στον άξονα Χ του διαγράμματος ενώ οι άλλες στήλες αντιστοιχούν στις ποσότητες
που θα απεικονιστούν στον άξονα Υ. Σχόλια επιτρέπονται σε ξεχωριστές γραμμές. Οι γραμμές
αυτές ξεκινούν με #. Παράδειγμα
# Παράδειγμα αρχείου αποτελεσμάτων για N = 1000 P =1
# P Total T Comp T Comm T
  1 10       10 0
# Παράδειγμα αρχείου αποτελεσμάτων για N = 1000 P =2
# P Total T Comp T Comm T
  2 9       7 2
# Παράδειγμα αρχείου αποτελεσμάτων για N = 1000 P =4
# P Total T Comp T Comm T
  4 6                 3           3
# Παράδειγμα αρχείου αποτελεσμάτων για N = 1000 P =8
# P Total T Comp T Comm T
  8   6                 2           4

Ένα τέτοιο αρχείο θα μπορούσε να παραχθεί από ένα shell script της μορφής

#!/bin/bash
mpiexec ­n 1 ./hello < hello.input > hello.results
mpiexec ­n 2 ./hello < hello.input >> hello.results
mpiexec ­n 4 ./hello < hello.input >> hello.results
mpiexec ­n 8 ./hello < hello.input >> hello.results

το οποίο έχουμε αποθηκεύσει ως hello.run

32 Χρήση gnuplot
To gnuplot μπορεί να κληθεί από τη γραμμή εντολών. Απαντά με τη προτροπή του:

gnuplot>

στη προτροπή μπορούμε να γράψουμε κάτι σαν

gnuplot> plot "hello.results" using 1:3 title 'Computation' with lines 

το οποίο θα παράγει το επιθυμητό γράφημα, για τις στήλες 1 στον άξονα Χ, 3 στον άξονα Υ και με
χρήση γραμμής, και τίτλό Computation. Για απεικόνιση περισσότερων στηλών

gnuplot>        plot "hello.results " using 1:2 title 'Total' with lines,\ 
>                    "hello.results" u 1:3 t 'Comp' w l,\ 
>                    "hello.results" u 1:4 t 'Comm' w l 
gnuplot>

οπου τα u t w l είναι συντμήσεις των αντίστοιχων λέξεων.

Για να σώσουμε ένα γράφημα σε αρχείο:


gnuplot>  set terminal gif
gnuplot>  set output "hello.gif"
gnuplot>  plot "hello.results" using 1:3 title 'Computation' with lines 

Η απεικόνισή μας έχει αποθηκευτεί στο αρχείο hellp.gif. Αντί για gif θα μπορούσαμε να διαλέξουμε
jpeg, png, postscript, svg κλπ, ανάλογα με την εφαρμογή στην οποία θέλουμε να εισάγουμε τη
γραφική απεικόνιση.

Η έξοδος από το gnuplot γίνεται με exit.

Οι δυνατότητες του gnuplot είναι πολλές. Σύντομες εισαγωγές στο gnuplot μπορείτε να βρείτε εδώ

http://www.it.uom.gr/teaching/gnuplot/

αλλά και στο Διαδίκτυο. Επίσης υπάρχουν γραφικές διεπαφές, όπως το PlotDrop που διευκολύνουν
τη χρήση του gnuplot με ποντίκι και μενού.

33 Gnuplot scripts
Οι εντολές του gnuplot είναι ουσιαστικά σενάρια που εκτελούνται. Επομένως μπορoύμε να
εκτελέσουμε την ίδια εργασία με τη βοήθεια ενός σεναρίου gnuplot. Πρώτα ελέγχουμε το path της
εφαρμογής

mpiuser@eleni:~$ which gnuplot 
/usr/bin/gnuplot 

Κατόπιν στο nano γράφουμε ένα script της μορφής

#!/usr/bin/gnuplot 
set terminal gif
set output "hello.gif" 
plot "hello.results" using 1:2 title 'Total' with lines,\ 
     "hello.results" u 1:3 t 'Comp' w l,\ 
     "hello.results" u 1:4 t 'Comm' w l 

το οποίο αποθηκεύουμε ως hello.plt και μετατρέπουμε σε εκτελέσιμο.

Έτσι αν τελικά εκτελέσουμε τα δύο scripts


mpiuser@eleni:~$ ./hello.run
mpiuser@eleni:~$ ./hello.plt

έχουμε παράγει τα επιθυμητά αποτελέσματα.

Τα παραπάνω σενάρια μπορούν εύκολα να αντιγραφούν, να τροποποιηθούν ή και να


παραμετροποιθούν με τη βοήθεια ορισμάτων.

You might also like