Vous êtes sur la page 1sur 37

Ecole nationale d’ingénieur République du Mali

Abderhamane Baba Touré(ENI-ABT) UN PEUPLE-UN BUT-UNE FOI

Exposé : Open Multi-Processing

Les Membres du Groupe Prof chargé de cours :


Souleymane KODJO Dr. SIDIBE
Ouzairou DJIRE
Abdrahamane Idrissa DOUMBIA
PLAN DE PRÉSENTATION

I. Introduction

II. Concept de proccessus et de thread

III. Bibliothèque Open MP

IV. Avantage et Inconveniant

V. Conclusion

2
INTRODUCTION

 OpenMP (Open Multi-Processing) est une interface de programmation pour


le calcul parallèle sur architecture à mémoire partagée. Cette API est prise
en charge par de nombreuses plateformes, incluant GNU/Linux, OS
X et Windows, pour les langages de programmation C, C++ et Fortran. Il se
présente sous la forme d'un ensemble de directives, d'une bibliothèque
logicielle et de variables d'environnement.Il regroupe des directives de
compilation et des fonctions.

 Le compilateur gcc supporte OpenMP depuis la version 4.2, en ajoutant


simplement une option sur la ligne de commande et en incluant le fichier
d'en-têtes omp.h. La gestion des différentes fonctions est assurée par la
libraire libgomp.
INTRODUCTION

 OpenMP est portable et dimensionnable. Il permet de développer rapidement des applications


parallèles à petite granularité en restant proche du code séquentiel.

 La programmation parallèle hybride peut être réalisée par exemple en utilisant à la fois
OpenMP et MPI.

 Element de base

 Les éléments de base d'OpenMP sont les constructions pour la création de threads, la
distribution de la charge de travail (partage du travail), la gestion de l'environnement de
données, la synchronisation des threads, les routines d'exécution au niveau de l'utilisateur et
les variables d'environnement.
INTRODUCTION

 Le pragma omp parallel est utilisé pour bifurquer des threads supplémentaires pour effectuer
le travail inclus dans la construction en parallèle. Le thread d'origine sera désigné
comme thread maître avec l'ID de thread 0.

 Exemple (programme C) : Afficher "Hello, world" en utilisant plusieurs threads.


Conception

 OpenMP est une implémentation du multithreading , une méthode de


parallélisation dans laquelle un thread principal (une série d'instructions
exécutées consécutivement) bifurque un nombre spécifié de sous -threads et le
système divise une tâche entre eux. Les threads s'exécutent alors simultanément
, l' environnement d'exécution allouant des threads à différents processeurs. La
section de code destinée à s'exécuter en parallèle est marquée en conséquence,
avec une directive du compilateur qui entraînera la formation des threads avant
l'exécution de la section. [3] Chaque thread est associé à un identifiant qui peut
être obtenu à l'aide d'une fonction (appelée omp_get_thread_num()).
Conception

 L'identifiant du thread est un entier et le thread principal a un identifiant de 0 . Après l'exécution du


code parallélisé, les threads se rejoignent dans le thread principal, qui continue jusqu'à la fin du
programme. Par défaut, chaque thread exécute indépendamment la section de code parallélisée. Les
constructions de partage de travail peuvent être utilisées pour diviser une tâche entre les threads
afin que chaque thread exécute sa partie de code allouée.
Conception

 Le parallélisme des tâches et le parallélisme des


données peuvent être obtenus en utilisant OpenMP de
cette manière. L'environnement d'exécution alloue des
threads aux processeurs en fonction de l'utilisation, de
la charge de la machine et d'autres facteurs.
L'environnement d'exécution peut affecter le nombre
de threads en fonction des variables d'environnement
ou le code peut le faire à l'aide de fonctions. Les
fonctions OpenMP sont incluses dans un fichier d'en-
tête intitulé omp.h en C / C++
Éléments de base

 Les éléments de base d'OpenMP sont les constructions


pour la création de threads, la distribution de la charge de
travail (partage du travail), la gestion de l'environnement de
données, la synchronisation des threads, les routines
d'exécution au niveau de l'utilisateur et les variables
d'environnement. En C/C++, OpenMP utilise #pragmas .
Les pragmas spécifiques à OpenMP sont listés ci-dessous.
Création de fil

 Le pragma omp parallel est utilisé pour bifurquer des


threads supplémentaires pour effectuer le travail inclus
dans la construction en parallèle. Le thread d'origine sera
désigné comme thread maître avec l'ID de thread 0.

 Exemple (programme C) : Afficher "Hello, world". en


utilisant plusieurs threads
Création de fil
 Utilisez flag -fopenmp pour compiler à l'aide de GCC :

 Concept de Processus – thread

 Un processu est une instance en execution d’un programme

 La gestion de l’execution des processus est assurée par le système d’exploitation (OS)

 Un os est capable de de l’executter plusieur processus en meme temps : Multi-Tasking


Variant directives
 Les directives de variantes sont l'une des principales fonctionnalités introduites dans la spécification
OpenMP 5.0 pour aider les programmeurs à améliorer la portabilité des performances. Ils permettent
l'adaptation des pragmas OpenMP et du code utilisateur au moment de la compilation. La spécification
définit des traits pour décrire les constructions OpenMP actives, les dispositifs d'exécution et les
fonctionnalités fournies par une implémentation, des sélecteurs de contexte basés sur les traits et les
conditions définies par l'utilisateur, et des directives métadirectives et de déclaration permettant aux
utilisateurs de programmer la même région de code avec des directives variantes. basées sur des traits
qui définissent une condition ou un contexte OpenMP.
Variant directives

 La directive declare variant a une fonctionnalité similaire à la métadirective mais sélectionne


une variante de fonction sur le site d'appel en fonction du contexte ou des conditions définies par
l'utilisateur.

 Le mécanisme fourni par les deux directives de variantes pour sélectionner des variantes est plus
pratique à utiliser que le prétraitement C/C++ car il prend directement en charge la sélection de
variantes dans OpenMP et permet à un compilateur OpenMP d'analyser et de déterminer la
directive finale à partir des variantes et du contexte.
Clauses

Comme OpenMP est un modèle de programmation à mémoire partagée, la plupart des variables du
code OpenMP sont visibles par défaut pour tous les threads. Mais parfois, des variables privées
sont nécessaires pour éviter les conditions de concurrence et il est nécessaire de transmettre des
valeurs entre la partie séquentielle et la région parallèle (le bloc de code exécuté en parallèle), de
sorte que la gestion de l'environnement de données est introduite sous forme de clauses d'attribut de
partage de données en les ajoutant à la directive OpenMP. Les différents types de clauses sont :

 Clauses d'attribut de partage de données


Clauses

 partagé : les données déclarées hors d'une région parallèle sont partagées, c'est-à-dire visibles et
accessibles par tous les threads simultanément. Par défaut, toutes les variables de la région de
partage de travail sont partagées, à l'exception du compteur d'itérations de boucle. private : les
données déclarées dans une région parallèle sont privées pour chaque thread, ce qui signifie que
chaque thread aura une copie locale et l'utilisera comme variable temporaire. Une variable privée
n'est pas initialisée et la valeur n'est pas conservée pour une utilisation en dehors de la région
parallèle. Par défaut, les compteurs d'itération de boucle dans les constructions de boucle OpenMP
sont privés. default : permet au programmeur d'indiquer que la portée des données par défaut dans
une région parallèle sera soit shared , soit none pour C/C++, soit shared , firstprivate , private , ou
 firstprivate : comme private sauf initialisé à la valeur d'origine. lastprivate : comme
private sauf que la valeur d'origine est mise à jour après la construction. réduction :
un moyen sûr de joindre le travail de tous les threads après la construction.
Concept Processus –Concept Thread

 Un processus se compose de plusieurs threads. Un thread est la plus petite partie


du processus pouvant s’exécuter simultanément avec d’autres parties (threads) du processus.

 Un processus est parfois appelé tâche lourde. Un thread est souvent appelé processus léger.

 Un processus a son propre espace d’adressage. Un thread utilise l’espace d’adressage


du processus et le partage avec les autres threads de ce processus.

 Pour comprendre ce qu’est un thread, il est nécessaire de définir ce qu’est un processus


Concept Processus –Concept Thread

 un processus. Un processus est un programme en cours d’exécution au sein d’un environnement.


Le thread, ou fil d’exécution en français, est une unite d’un processus et il permet d’exécuter
des instructions de langage machine au sein du processeur. La spécificité du thread est qu’il
laisse la possibilité à deux instances en train d’interpréter le même programme de s’exécuter en
simultané au sein du même processeur.
Comparaison processus et Thread

 Les processus sont independant l’un independants l’un de l’autre , tandis que les threads
appartiennent au meme processus

 Les procesuus on t des adresses memoires diifenrentes, tandis que les threads partagent la
meme zone mémoire

 Les processus peuvent communinquer ebte eux a travers leur capacité d’enchanges de données,
tandisque les threads peuvent avoir access des direct aux ressource occupées par leur processus

 Tous les threads appartiennent à un processus partagent des descripteurs de fichiers communs,
une mémoire de tas et d’autres ressources,
Répartition des tâches

 Sans spécification particulière, les tâches sont réparties de manière égalitaire entre les différents
threads. Les threads traitant le centre de la fractale ont plus de calculs à traiter que les threads
chargés de l'extérieur de la fractale. Les threads les plus rapides attendent donc les threads les plus
lents avant la finalisation du programme. La répartition des tâches est configurable à la fin de la
directive omp parallel for avec la clause schedule. Le découpage peut se faire selon quatre
manières différentes.
Répartition statique

 Si rien n'est spécifié, la répartition statique est utilisée, elle peut être aussi explicitement
choisie en utilisant la clause schedule(static). Le nombre d'itérations de la boucle est
divisé par le nombre de threads, tous les threads traitent donc la même quantité de
données. Il est possible de fixer le nombre de données traité par chaque thread après la
déclaration static, par exemple : schedule(static,32).
Répartition dynamique

 Chaque thread traite une quantité de données spécifiée par la taille passée après dans la
déclaration dynamic. Dès qu'un thread a terminé son lot de données, il peut reprendre un lot
de données à traiter. Les threads n'ont pas d'ordre de passage, certains peuvent être plus longs
que d'autres.

 Par défaut, un seul élément est traité par chaque thread. Le nombre d'éléments traités peut être
fixé après la déclaration dynamic. Sauf si les calculs sont très longs, il doit être initialisé pour
prendre une valeur supérieure à la valeur par défaut : schedule(dynamic,32).
Répartition guidée

 Cette organisation des tâches est basée sur une taille décroissante des blocs traités. Au
départ, les threads traitent une grande quantité de données, puis le nombre d'éléments
traités décroît pour optimiser le temps de calcul. Le nombre minimal d'éléments à
traiter est fixé par la valeur passée en paramètre.
Répartition à l'exécution

 Il est possible de choisir la méthode de répartition des tâches lors de l'exécution du


programme. Pour obtenir ce comportement, on utilise la clause schedule runtime. La
répartition et ses paramètres sont alors configurés par la variable
système OMP_SCHEDULE. La répartition à l'exécution n'est utile que pour le
développement afin de tester différentes stratégies de répartition.
Avantages et inconvénients
❖ Avantages:
➢ Code multithreading portable (en C/C++ et dans d'autres langages, il faut généralement appeler
des primitives spécifiques à la plate-forme pour obtenir le multithreading).
➢ Simple : pas besoin de gérer la transmission de messages comme le fait MPI .
➢ La disposition et la décomposition des données sont gérées automatiquement par des directives.
➢ Évolutivité comparable à MPI sur les systèmes à mémoire partagée.
➢ Parallélisme incrémental : peut fonctionner sur une partie du programme à la fois, aucune
modification radicale du code n'est nécessaire.
➢ Code unifié pour les applications série et parallèles : les constructions OpenMP sont traitées
comme des commentaires lorsque des compilateurs séquentiels sont utilisés.
➢ Les instructions de code d'origine (série) n'ont généralement pas besoin d'être modifiées
lorsqu'elles sont parallélisées avec OpenMP. Cela réduit le risque d'introduire des bogues par
inadvertance.
Avantages et inconvénients

❖ Inconvénients:
➢ Risque d'introduire des bogues de synchronisation difficiles à déboguer et des conditions de
concurrence
➢ Nécessite un compilateur prenant en charge OpenMP.
➢ L'évolutivité est limitée par l'architecture de la mémoire.
➢ Pas de support pour compare-and-swap
➢ La gestion fiable des erreurs fait défaut.
➢ Manque de mécanismes précis pour contrôler le mappage thread-processeur
Cas pratique

 Pour commencer, un programme élémentaire va être pris comme exemple pour montrer
l'utilisation générale d'OpenMP. Ce programme est présenté ci-dessous, la boucle
représente le traitement de différents éléments de manière séquentielle.

La compilation se
fait avec la ligne :

Rien de bien surprenant ne se


produit à l'exécution
(normalement...). Les éléments
sont traités de manière
séquentielle, l'un après l'autre.
Premier programme
 Nous allons maintenant modifier ce programme pour que le traitement des différents éléments se
fasse de manière simultanée. La machine utilisée pour tester ce programme est un Core 2 Duo.
Elle est donc capable de traiter deux tâches de manière simultanée. Le programme modifié est le
suivant :

Compilation

Deux threads ont été automatiquement créés. L'ensemble des indices de la boucle a été séparé en deux parties : les
indices allant de 0 à 3 sont traités par le premier thread, les indices allant de 4 à 7 sont traités par le second thread.
Fixation du nombre de threads
 Dans l'exemple précédent, le nombre de threads a été automatiquement fixé par OpenMP. Il est
possible de fixer le nombre de threads de plusieurs manières. Il peut être configuré en utilisant la
variable système OMP_NUM_THREADS.

 La bibliothèque propose plusieurs fonctions pour gérer les threads. La


fonction omp_set_num_threads permet de fixer le nombre de threads dans le programme. D'autres
fonctions sont utiles comme :

 - omp_get_num_procs qui permet de connaître le nombre de processeurs équipant la machine.

 - omp_get_num_threads qui retourne le nombre de threads actuellement gérés par la librairie.

 Il est ainsi possible d'adapter le nombre de threads aux processeurs équipant la machine.
Exemple de programme réalisant le produit
de matrices avec OpenMP
 Le calcul du produit de deux matrices est une application typique (mais un peu
matheuse...) des problèmes de parallélisation. Chaque élément peut être calculé de
manière indépendante des autres, la tâche doit donc pouvoir se traiter avec OpenMP.
Le calcul du produit de matrices fait appel à trois boucles les unes dans les autres (pour
l'algorithme naïf, des versions optimisées existent).
 Partage des variables entre les threads
 Avant de présenter le détail du programme, nous allons nous attarder sur une option de
la directive omp parallel for utilisée dans le programme précédent.
 Par défaut, toutes les variables présentes dans la boucle sont partagées entre les
différents threads, sauf le compteur de boucles (chaque thread dispose d'une copie
qu'il est libre de modifier, sans conséquence pour les autres threads). Il est souvent
nécessaire de protéger des variables contre des accès simultanés (un thread écrit
pendant qu'un autre lit). Les variables à protéger sont listées dans la clause private.
Exemple de programme réalisant le produit
de matrices avec OpenMP

 Exemple de programme réalisant le produit de matrices avec OpenMP


 Ces différentes notions sont utilisées dans le programme de calcul du
produit de matrices. Le programme est présenté ci-dessous. Il permet de
mesurer les performances d'OpenMP avec un nombre de threads variable
(de 1 à 12). Le temps de calcul est mesuré avec la fonction gettimeofday
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
#include <sys/time.h>
#define SIZE 2000

double get_time() {
struct timeval tv;
gettimeofday(&tv, (void *)0);
return (double) tv.tv_sec + tv.tv_usec*1e-6;
}
Code du produit de matrices avec OpenMP

int main(int argc, char **argv){


int nb, i , j, k;
double t,start,stop;
double* matrice_A;
double* matrice_B;
double* matrice_res;
// Allocations
matrice_A = (double*) malloc(SIZE*SIZE*sizeof(double)) ;
matrice_B = (double*) malloc(SIZE*SIZE*sizeof(double)) ;
matrice_res = (double*) malloc(SIZE*SIZE*sizeof(double)) ;
Code du produit de matrices avec OpenMP

for(i = 0; i < SIZE; i++){


for(j = 0; j < SIZE; j++){
matrice_A[i*SIZE + j] = (double)rand()/(double)RAND_MAX;
matrice_B[i*SIZE + j] = (double)rand()/(double)RAND_MAX;
}
}
printf("Nb.threads\tTps.\n");
for(nb=1;nb<=12;nb++){
start = get_time();
#pragma omp parallel for num_threads(nb) private(j,k)
for(i = 0; i < SIZE; i++){
for(j = 0; j < SIZE; j++){
matrice_res[i*SIZE + j] = 0.0;
for(k = 0; k < SIZE; k++){
matrice_res[i*SIZE + j] += (matrice_A[i*SIZE + k]*matrice_B[k*SIZE + j]);
}
}
}
}
Code du produit de matrices avec OpenMP

stop=get_time();
t=stop-start;
printf("%d\t%f\n",nb,t);
}
// Libérations
free(matrice_A);
free(matrice_B);
free(matrice_res);
return EXIT_SUCCESS;
}
Résultats
 Pour tester les différents programmes qui vont suivre, nous avons utilisé une
machine équipée de deux processeurs Xeon 5060. Chaque processeur est
un double cœur capable de gérer l'hyperthreading. En théorie (ou selon
les commerciaux...), cette machine peut donc exécuter 8 threads
simultanément.
 Le système d'exploitation est Ubuntu Server 19.10 avec un
compilateur gcc dans sa version 4.3. Pour tous les essais, le système est
utilisé sans interface graphique pour minimiser le nombre de tâches liées au
système d'exploitation.
Résultats

 Fig. 1 : Gain en temps de calcul pour le produit de matrices en fonction du


nombre de threads utilisés. Le gain théorique est représenté par une ligne
pointillée rouge.
 La figure 1 présente le gain en temps de calcul pour le produit de matrice. On
constate 3 parties différentes sur le graphique :
 - Pour 1 à 4 threads, le gain en temps de calcul est très proche du gain
théorique, la parallélisation est excellente. Les threads sont tous traités par des
cœurs de processeur, le système se comporte comme un quadriprocesseur.
 - Entre 5 et 8 threads, la courbe ne suit plus la courbe théorique. L'augmentation
du nombre de threads est encore intéressante (gain de l'ordre de 4,8 avec 8
threads). A partir de 5 threads, la technologie hyperthreading est utilisée pour
gérer les threads supplémentaires.
 - Au-delà de 8 threads, le résultat est franchement mauvais. La gestion des
threads prend trop de temps et ralentit le programme.
Résultats

 Cet exemple simple nous a montré qu'il est facile d'utiliser tous les cœurs
d'un processeur sans modifier lourdement un programme. Dans la suite de
cet article, nous allons détailler deux autres cas concrets afin d'aborder le
partage des tâches et les conflits de mémoire.

Vous aimerez peut-être aussi