Vous êtes sur la page 1sur 13

‫الجمــــهوريــــة الجــــزائريــــة الديــمقـراطـيـــــــــــة الشـــعبيـــــــــة‬

REPUBLIQUE ALGERIENNE DEMOCRATIQUE ET POPULAIRE


‫وزارة التعــلـيــم الـعـالـــي و الـبـحـث الـعـلــمي‬
MINISTERE DE L’ENSEIGNEMENT SUPERIEUR ET DE LA RECHERCHE SCIENTIFIQUE
‫جـامـعــة ع ّمــــار ثـلـيــــجـــي باألغـــــواط‬
UNIVERSITE AMAR TELIDJI LAGHOUAT
‫كلــيـــة العـــلــوم‬
FACULTE DES SCIENCES

DEPARTEMENT D’INFORMATIQUE
Domaine : Mathématiques et Informatique
Filière : Informatiques

Par:
Isra Bendjemaa Cherifi Katia Ines
THEME

TP OPENMP

Proposé par : Mme Cherroune Hadda

Année Universitaire 2021/2022


Sommaire

Introduction ………………………………………………………………………….
Partie 1 ……………………………………………………………………………….
OpenMP…………………………………………………………………………...
Les Spécifications ……………………………………………………………….
Concept généraux……………………………………………………………….
Limitations ……………………………………………………………………….
Installation ………………………………………………………………………
Partie 2 ………………………………………………………………………………….
Quelque exemples…………………………………………………………………
Les applications des nombres premiers…………………………………………
Algorithme/programme (version naïve) ……………………………………….
Optimisation séquentiel………………………………………………………….
Optimisation par parallélisation ………………………………………………….
Conclusion ………………………………………………………………………………
Bibliographie……………………………………………………………………………

1
Introduction :
La parallélisation multithread existait depuis longtemps chez certains constructeurs (ex.
CRAY, NEC, IBM, ...), mais aujourd’hui elle est devenue plus simple avec l’Interface de
programmation Open Multi-Processing, qui est Supportée par de nombreux OS et
Repose sur l’utilisation de processus système légers (threads).
Partie 1 :
OpenMP :
Est un outil de programmation multithread mis au point par les constructeurs (de machines),
permettant d’implémenter le parallélisme en mémoire partagée. Parmi Les langages qui
supportés l’openmp : Fortran et C/C++
Son principe est simple : mettre à disposition des développeurs un ensemble des mots clés
pour permettre au compilateur à paralléliser un programme. Lorsque on veut paralléliser
tout ou partie d’un programme c/c++, on doit d’abord en inclue la bibliothèque
<omp.h>, puis en identifier les blocs d’instructions qu’l’on veut parallélisé en utilisant
certains directives par exemple si on veut parallélise une boucle for : en ajoute la
directive #pragma omp parallel for qui indique que le contenu de la boucle for doit être
exécuté en parallèle, et ainsi la directive #omp_set_num_threads(4) qui spécifie le
nombre de threads .
Lorsque le nombre d'itérations d'une boucle est petit, on peut considérer qu'il n'est pas
nécessaire d'utiliser le parallélisme
Les spécifications :
• Une version OpenMP-2 a été finalisée en novembre 2000. Elle apporte surtout des
extensions relatives à la parallélisassions de certaines constructions Fortran 95.
• La version OpenMP-3 datant de mai 2008 introduit essentiellement le concept de tache.
• La version OpenMP-4 de juillet 2013 apporte de nombreuses nouveautés, avec
notamment le support des accélérateurs, des d´dépendances entre les taches, la
programmation SIMD (vectorisation) et l’optimisation du placement des threads.
• L’OpenMP permet de la Gestion de « threads » transparente et portable aussi Facilité la
programmation
Concepts généraux :
Modèle d’exécution :
• A son lancement, un programme OpenMP est séquentiel. Il est constitué d’un processus
unique, qui active des processus légers (threads) à l'entrée d'une région parallèle ( c’est à
dire des portions de code destinées à être exécutées en parallèle).
• chaque thread exécute sa tache implicite qui est composée d'un ensemble d’instructions.
en vue de se partager le travail et de s’exécuter concurremment.

2
• Un programme OpenMP est une alternance de régions séquentielles et de régions
parallèles.
• Une région parallèle peut être exécute par plusieurs tâches à la fois.
Processus légers (threads) :
• le système d’exploitation qui choisit l’ordre d’exécution des processus (légers ou non), il
les affecte aux unités de calcul disponibles (cœurs des processeurs).
• Il n’y a aucune garantie sur l’ordre global d’exécutions des instructions d’un programme
parallèle.
• Les taches d’un même programme partagent l’espace mémoire de la tache initiale
(mémoire partagée) mais disposent aussi d’un espace mémoire local : la pile (ou stack).
• Pendant l'exécution d'une tâche, une variable peut être lue et/ou modifiée en mémoire, Il
est ainsi possible de définir des variables partagées (stockées dans une mémoire partagée
par tous les processus légers) ou des variables privées (stockées dans la pile de chacune
des tâches).
Limitations de la programmation OpenMP
• On ne peut pas localiser les données avec OpenMP.
• Mémoire partagée mais non hiérarchique
• Les surcoûts causés par le travail et lors de la création/gestion des threads et régions
parallèles.
• Passage à l’échelle limité (efficacité sur plusieurs cœurs) les codes ont une extensibilité
limitée même lorsqu’ils sont bien optimisés. Plus la granularité du code est fine, plus ce
phénomène s’augmente.
• Efficacité non garantie
Installation :

➢ $ sudo apt install libgomp1


On compile utilsant :
➢ $ g++ helloworld.cpp -fopenmp -o helloworld

3
Partie 2:

1. Quelques exemples :

P1 P2
include<stdio.h> #include <iostream>
#include<stdlib.h> #include <omp.h>
#include<omp.h> int main() {
int main (int argc, char const #pragma omp parallel //declare
*argv[]){ la partie de programm
int n; parallel
#pragma omp parallel for {
for(n=0;n<8;n++){ Int
printf("Element %d traité id=omp_get_thread_num();//num de
par le thread %d processus , variable int est
\n",n,omp_get_thread_num()); partagé
} printf("hello(%d)",id);
return EXIT_SUCCESS; printf("world(%d)\n",id);
} }
}

L’Exécution :

2. Les applications des nombres premiers :


•Les nombres premiers permet de simplifier les calculs fractionnaires, ainsi que
simplifier des formules.
•des systèmes de cryptographie qui permet de chiffrer un message ou des données
pour assurer leur protection. Ils sont basés sur les propriétés des nombres premiers par
exemple dans la Cryptographie à clé publique : le système de cryptographie
asymétrique (nommé d'après leurs initiales RSA) base sur les propriétés des nombres

4
premiers et de la factorisation. Dans la cryptographie rsa deux grands nombres
premiers p et q de plus de 80 chiffres sont utilisé pendant le chiffrement

3. L’algorithme et programme c++ des nombres premiers :


#include <stdio.h> Algorithme premier;
#include <stdlib.h> Var I,j,n_diviseur: entier ;
#include <iostream> Debut
#include <math.h> Ecrire( ‘donner un nombre’) ;
Lire(n) ;
Pour i=1 ; i<=n ;i++
using namespace std; Faire
n_diviseurs=0 ;
int main() Pour j=2 ;j<=sqrt(i) ;j++
{ Faire
Int i,n,j,n_diviseurs; Si (i mod j = 0) alors
cout << "donner un nombre " <<
endl; n_diviseurs=n_diviseurs+1 ;
cin >>n; fsi ;
fpour ;
for(i=1;i<=n;i++) si(n_diviseurs==0) alors
ecrire(i, ‘ est premier’) ;
{ fsi ;
n_diviseurs=0; fpour ;
fin ;
for(j=2;j<=sqrt(i);j++)
{

if(i%j==0)
n_diviseurs=n_diviseurs+1;

}
if(n_diviseurs==0)
printf("%d est premier\n",i);
}
return 0;
}

Complexité : O(n√𝒏)
4. Le temps d’exécutions pour les n données :
N 𝟏𝟎𝟐 𝟏𝟎𝟒 𝟏𝟎𝟑 𝟏𝟎𝟓
Temps(s) 6.36 13.54 18.58 128.63

5. Optimisation séquentielle

5
• L’algorithme du crible d’Ératosthène est très simple. Prenons un tableau
contenant les entiers de 2 a n (si l’on désire connaitre tous les nombres premiers
inférieurs à n) que l’on suppose tous premiers. Ensuite il suffit, pour chaque
élément du tableau, de supprimer tous ses multiples. Le pseudo code pourrait
donc être le suivant :
Algorithme :
Crible(entier n):
Tableau = entiers de 2 a n
Pour chaque i dans Tableau
supprimer_multiples de i dans Tableau
Fin Pour Fin Crible

C++ :
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <math.h>
using namespace std;

int main(int argc, char **argv)


{
int n=8;
bool supprimes[n+1];

supprimes[0] = true; // 0 n'est pas


premier
supprimes[1] = true; // 1 n'est pas
premier
float temps;
clock_t t1, t2;
t1 = clock();
for (int i=2; i<n+1; i++)
supprimes[i] = false;

for (int i=2; i<n+1; i++)


if (!supprimes[i]) {
int multiple = 2 * i;
while (multiple < n+1) {
supprimes[multiple] = true;
multiple += i;
}
} t2 = clock();
temps = (float)(t2-t1)/CLOCKS_PER_SEC;

for (int i=0; i<n+1; i++)


if (!supprimes[i])
cout << i << " ";
printf("temps = %f\n", temps);
cout << endl;
return 0;
}

6
a) L’optimisation observée est de déclarer un tableau de n cases qui peuvent
chacune contenir les nombres premiers et non premiers en supprimant les nons
premiers avec ses multiples à la fois. Nous allons voir qu’avec cette technique
nous allons pouvoir nous passer complètement de comparaisons et de tests ,ce
qui va diminuer grandement le temps de calcul.

b) Complexité : O(N log log N)

1. Si l'on suppose que le temps nécessaire pour marquer un nombre comme composite
est constant, alors le nombre de fois que la boucle s'exécute et en prenant n commun
de l'équation est égale à :
2. Il peut être prouvé comme ci-dessous à l'aide de la progression harmonique de la
somme des nombres premiers :
• Preuve de la progression harmonique de la somme des nombres

premiers: En mettant x = 1 dans l'équation


• À partir de la formule produit d'Euler ,et après certaines simplifications , on

obtient :
3. En substituant cela dans l'équation, nous obtenons la complexité temporelle comme :

• Par conséquent, la complexité en temps du tamis d'Eratosthène est n * log (log


(n))

Temps d’exécution :

N 𝟏𝟎𝟐 𝟏𝟎𝟑 𝟏𝟎𝟒 𝟏𝟎𝟓


Temps(s) 0.015 0.033 0.075 0.087

Observation : d’après le test des valeurs de n sur la version séquentielle et


séquentielle optimisée .note que le temps de calcul est diminué sur l’optimisée par
rapport au séquentielle.
6. Optimisation par parallélisation :

a) algorithme qui parallélise la version naïve :


#include <stdio.h>#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <math.h>
#include <omp.h>
#include <time.h>
using namespace std;

int main()

7
{
int i,j,n,n_diviseurs;
float temps;
clock_t t1, t2;
cout << "donner un nombre " << endl;
cin >>n;

t1 = clock();
#pragma omp parallel for
for(i=1;i<=n;i++)

{ n_diviseurs=0;

for(j=2;j<=sqrt(i);j++)
{
if(i%j==0)

n_diviseurs=n_diviseurs+1;

}
if(n_diviseurs==0)

printf("nombre premier %d traité par le thread


%d \n",i,omp_get_thread_num());
}
t2 = clock();
temps = (float)(t2-t1)/CLOCKS_PER_SEC;
printf("temps = %f\n", temps);
return 0;
}

L’exécution :

8
b) Un algorithme qui parallélise la version utilisant la crible d' Eratosthenes:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <math.h>
#include <chrono>

#include<omp.h>
using namespace std;

int main(int argc, char **argv)


{
int n;
cout << "donner une valeur : " <<endl;
cin>>n;

bool supprimes[n+1];

supprimes[0] = true; // 0 n'est pas premier


supprimes[1] = true; // 1 n'est pas premier
double start;
// temps courant, avant l'execution
std::chrono::high_resolution_clock::time_point a=
std::chrono::high_resolution_clock::now();
#pragma omp parallel for
for (int i=2; i<n+1; i++)
supprimes[i] = false;
#pragma omp parallel for
for (int i=2; i<n+1; i++)
if (!supprimes[i]) {
int multiple = 2 * i;
while (multiple < n+1) {

9
supprimes[multiple] = true;
multiple += i;
}
}

} // temps courant, apres l'execution


std::chrono::high_resolution_clock::time_point b=
std::chrono::high_resolution_clock::now();
#pragma omp parallel for
for (int i=0; i<n+1; i++)
if (!supprimes[i])
cout << i << " ";

cout << endl;


// mesurer la difference, et l'exprimer en
microsecondes
unsigned int time=
std::chrono::duration_cast<std::chrono::microsecon
ds>(b - a).count();

return 0;
}
L’execution :

c) les temps d'exécution en temps CPU en parallèle : ( nombre de cœurs =4)


Pour naïve parallèle :
N 𝟏𝟎𝟐 𝟏𝟎𝟑 𝟏𝟎𝟒 𝟏𝟎𝟓
Temps(s) 0.028 0.024 0.48 9.11

Pour crible parallèle :

N 𝟏𝟎𝟐 𝟏𝟎𝟑 𝟏𝟎𝟒 𝟏𝟎𝟓


Temps(s) 3.6*𝟏𝟎−𝟓 0.000105 0.0010 0.0020

d) Comparaison avec les versions séquentielles respectives :

Naïve parallèle vs naïve séquentielle

10
N 𝟏𝟎𝟐 𝟏𝟎𝟑 𝟏𝟎𝟒 𝟏𝟎𝟓 N 𝟏𝟎𝟐 𝟏𝟎𝟑 𝟏𝟎𝟒 𝟏𝟎𝟓
Temps(s) 0.028 0.024 0.48 9.11 Temps(s) 6.36 13.54 18.58 128.63

Crible parallèle vs Crible séquentielle

N 𝟏𝟎𝟐 𝟏𝟎𝟑 𝟏𝟎𝟒 𝟏𝟎𝟓 N 𝟏𝟎𝟐 𝟏𝟎𝟑 𝟏𝟎𝟒 𝟏𝟎𝟓


Temps(s) 3.6*10^- 0.000105 0.0010 0.0020 Temps(s) 0.015 0.033 0.075 0.087
5

On observe qu’Il y a une vaste différence entre les deux versions exactement sur le
temps d’exécution. Donc :
𝐓𝐞𝐦𝐩𝐬𝐬𝐞𝐪𝐮𝐞𝐧𝐭𝐢𝐞𝐥 > 𝐭𝐞𝐦𝐩𝐬𝐩𝐚𝐫𝐚𝐥𝐥𝐞𝐥𝐞

Conclusion :
Les temps de calcul en séquentiel sont extrêmement élevés :
• Problème de taille de données et la difficulté de calcul sur un seul cœur (en mode
séquentiel) même en exploitant toute la mémoire vive.
• Des résultats dans des délais non raisonnables.
• La durée d’occupation des centres de ressources de calcul pendant des semaines, voire
des mois.
Après la comparaison faite sur ce TP entre le calcul séquentiel et parallèle, on conclue
que la seule solution, pour des raisons techniques ou économiques, reste la
parallélisassions qui est offerte plus simplement avec l’outil de OpenMp.

11
Bibliographie :
https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-122/Decouverte-de-la-
programmation-parallele-avec-OpenMP

https://www.scriptol.fr/programmation/crible.php#cpp

12

Vous aimerez peut-être aussi