High Performance Computing
M1 Informatique – C. Renaud
Version 1.4.3 du 25/06/2021
Introduction
• Définition
Algorithmes numériques
Programmation parallèle
Applications complexes
HPC
Introduction
• Quelques domaines d'application
Astrophysique : simulation de l'évolution
du medium interstellaire pour la formation des première galaxies – Gauss Center for Supercomputing
Biologie : recherches de molécules efficaces contre
le covid-19 - TEXAS ADVANCED COMPUTING
CENTER
Intelligence artificielle : Alpha Go
Introduction
• Quelques domaines d'application
Risques naturels : simulation d'un tsunami (01/04/14 - Chili – PTWC)
Risques industriels : simulation d'épandage de GNL sur le site de Dunkerque
(pas de simulation : 1cm) LISIC
Média et divertissement : effets spéciaux, films en
images de synthèse
24 000 coeurs–100 millions d'heures de calcul
Objectifs
• Comprendre les problématiques associées au HPC
• Niveaux de parallélismes
• Architectures parallèles
• Programmation parallèle
• Expérimenter sur quelques problématiques "gourmandes"
• Contrôle :
• Notation de quelques TPs
• Notation d'un exposé
• Examen final
Architectures parallèles
• Systèmes distribués
• Réseau de machines hétérogènes
• Calcul, stockage
• Offres de services hétérogènes
• Ex. Services web (http, Bases de données, traitements, …)
• Nombreuses contraintespar rapport aux "clients"
• Fiabilité, capacité de stockage, support d'une charge importante, temps de réponse, ...
Architectures parallèles
• Calculateurs massivement parallèles
• Nombre important de processeurs
• Différentes configurations
• Ordinateurs en réseau
• Clusters : regroupement local de serveurs vu comme un calculateur virtuel
• Grilles de calcul : réseau de clusters
• Supercalculateurs
• Nécessaires pour le calcul sur des gros volumes de données
• Prévisions météo, simulations physiques, synthèse d'images, …
• Besoins croissants au niveau mondial dans de nombreux domaines
• Sciences, Design, santé, construction
Noeuds de calcul
Noeuds de stockage
Noeuds fontaux
Grid 5000
Architectures parallèles
• Systèmes multi-processeurs
• Carte équipée de plusieurs processeurs
• CPUs, GPUs
• Brique de base pour les architectures massivement parallèles
• Avantages:
• Mémoire partagée
• Connexions à très haute bande passante
• Limites
• Nombre de processeurs limités
Noeud Dell DSS8440 :
• 2 Intel xéon Gold – 20 coeurs
• 10 Nvidia V100
• 768 Go RAM Noeud Dell R7525 :
• 2 AMD EPYC 7H12 – 64 coeurs
• 2 TB of RAM
Architectures parallèles
• Processeurs multi-coeurs
• Plusieurs coeurs indépendants au sein d'un unique processeur
• Spécifique par coeur :
• Unités de calcul et de contrôle
• Mémoire cache
• Partagé entre les coeurs
• Mémoire
• Unité de contrôle commune
Architectures parallèles
• Processeurs vectoriels
• Calcul d'une instruction sur plusieurs données en même temps
• Registres 128 bits : 4 valeurs 32 bits simultanément
• Registres 256 bits : 8 x 32 bits ou 4 x 64 bits
• Processeurs X86
• Instructions SSE et AVX
instructions
Données parallèles
Résultats parallèles
Architectures parallèles
• Architectures pipeline
• Réaliser une instruction machine
nécessite plusieurs étapes (micro-code par exemple)
• Sans pipeline : attendre la fin de
l'instruction avant de démarrer la suivante
• Avec pipeline : chaque étape terminée
permet de démarrer l'étape correspondante d'une autre instruction
• Utilisé dans la plupart des processeurs
• Exemple
• Architecture intel Skylake
• Longueur 14 à 19
Définitions
• Taxonomy de Flynn (1966)
• Spécifie les modes de fonctionnement parallèle
• S'appuie sur les notions de flots de données / d'instructions
• SISD : Single Instruction / Single Data
• Processeur classique (Von Neumann)
• SIMD : Single Instruction / Multiple Data
• Machines vectorielles, GPU
• MISD : Multiple Instruction / Single data
• peu utilisé
• MIMD : Multiple Instruction / Multiple Data
• Multiprocesseurs, clusters de machines
Définitions
• Speed-up :
• Facteur d'accélération obtenu par parallélisation d'une application S(n) = T(1)
T(n)
• Cas idéal : S(n) = n
• Cas général :
• Coûts additionnels
• Communications
• Synchronisation
• Équilibrage de charge
S(n) <= n
, avec n le nombre d'unités de calculs et T le temps d'exécution
Définitions
• Equilibrage de charge
• Dans l'idéal chaque tâche requiert la même quantité de calcul
• Faux dans la pratique :
• Certaines tâches peuvent s'arrêter plus rapidement
• Découpage statique des tâches :
• Des unités de calcul peuvent avoir plus de calcul à effectuer => diminution du speed-up
• Découpage dynamique des tâches
• Granularité plus fine
• Équilibrage envisageable
• Exemple :
• Répartition des calculs en lancer de rayons
• Equilibrage de charge
• Dans l'idéal chaque tâche requiert la même quantité de calcul
• Faux dans la pratique :
• Certaines tâches peuvent s'arrêter plus rapidement
• Découpage statique des tâches :
• Des unités de calcul peuvent avoir plus de calcul à effectuer => diminution du speed-up
• Découpage dynamique des tâches
• Granularité plus fine
• Équilibrage envisageable Zone sans objet : arrêt rapide
Zone avec réflexions : calculs plus complexes
Définitions
• Passage à l'échelle (scalability)
• Loi d'Amdahl: exprime le facteur d'accélération théorique pouvant être atteint
• Hypothèses :
• une proportion a de l'application est parallélisable
• une proportion (1 – a) doit être séquentielle Alors : T(n) = a . T(1) +(1-a). T(1)
n 1 2 ... n
Partie n-parallélisable Partie séquentielle
a (1 – a)
S(n) = T(1) =
T(n) a
n + (1-a) 1
Loi d'Amdahl
Définitions
• Loi d'Amdahl : illustration
S(n) = a
n + (1-a) 1
La fraction séquentielle (1-a) limite le gain qui peut être obtenu, quelle que soit le nombre n de processeurs
impliqués
Définitions
• Mesures de puissance
• FLOPS
• Floating-point Operations Per Second
• Le nombre d'opérations en virgule flottante par seconde effectué par le co- processeur FPU
• Unités "classiques"
Unité Valeur en FLOPS
GFLOPS (Giga FLOPS) 109
TFLOPS (Terra FLOPS) 1012
PFLOPS (Peta FLOPS) 1015
EFLOPS (Exa FLOPS) 1018
• Calcul : FLOPS = nbCoeurs x fréquence x FLOP/cycle
• En 2011 : 4 FLOPs par cycle en moyenne (source wikipédia)
Définitions
• Mesures de puissance
• Exemples:
processeur Puissance en GFLOPS
Intel Core i9-10900KF 460,8 (16 coeurs)
AMD Ryzen 5 4600H 392,3 (6 coeurs)
Intel Core i5-10300H 280,9 (4 coeurs)
AMD Ryzen 9 5950X 967,3 (12 coeurs)
AMD Ryzen Threadripper 3960X 1 898 (24 coeurs)
• Remarques :
• Puissances de crêtes - en pratique, performances inférieures
• Mesures dépendantes du problème testé
• Utiliser des "indices" de puissance comparatives sur des problèmes variés (Cinebench, geekbenck, antutu, ...) Source : https://gadgetversus.com/processor
669,760
Définitions
• Mesures de puissance
• Supercalculateurs (novembre 2020 - https://top500.org - tests avec linpack)
rang Rmax/Rpeack
(PFLOPS) nom pays Config
1 442,010 / 537,212 Fugaku Japon A64FX 48C 2.2GHz - 7,630,848 coeurs - 5,087,232 Go RAM
2 148,600 / 200,795 Summit USA IBM POWER9 22C 3.07GHz, NVIDIA Volta GV100 - 2,414,592 coeurs - 2,801,664 Go RAM 3 94.640 / 125.712 Sierra USA IBM POWER9 22C 3.1GHz - 1,572,480 coeurs - 1,382,400 Go RAM
4 93,02 / 125,44 Sunway TaihuLight Chine Sunway SW26010 260C 1.45GHz - 10,649,600 coeurs - 1,310,720 Go RAM 5 63.460 / 79.215 Selene USA AMD EPYC 7742 64C 2.25GHz - 555,520 coeurs - 1,120,000 Go RAM 6 61.45 / 100.68 Tianhe-2A Chine Intel Xeon E5-2692v2 12C 2.2GHz - 4,981,760 coeurs - 2,277,376 Go RAM 7 44.120 / 70.980 Juwels Allemagne AMD EPYC 7402 24C 2.8GHz - 449,280 coeurs - 628,992 Go RAM
8 35.450 / 51.721 HPC5 Italie Xeon Gold 6252 24C 2.1GHz - 669,760 coeurs - 349,440 Go RAM 9 35.450 / 51.721 Frontera USA Xeon Platinum 8280 28C 2.7GHz - 448,448 coeurs - 1,537,536 Go RAM
10 22.400 / 55.424 DAMMAM-7 Arabie Saoudite Xeon Gold 6248 20C 2.5GHz, NVIDIA Tesla V100, 672,520 coeurs - 506,368 GBo RAM
669,760
Exposé
• Étude d'une question générale liée au HPC
• Attribution d'une question à chaque binôme
• 5 minutes de présentation + questions
• 2 slides minimum
669,760
Mesure du temps (1/3)
• Nécessaire pour évaluer l'accélération
• Nombreuses possibilités
• Commande externe : time
• Syntaxe : time commande paramètres
• Sortie par défaut :
• Real : temps total écoulé pour l'exécution du processus
• User : temps CPU utilisé par le processus en mode utilisateur
• Sys : temps CPU utilisé par le processus en mode "noyau"
• Diverses autres possibilités
• Indications globales
• Pas d'analyse possible sur des parties du processus
renaud@ellana:~/Documents/Cours/Fac/M1/HPC/Avx$ time ./ps
…
…
…
real 0m20,943s user 0m20,902s sys 0m0,016s
669,760
Mesure du temps (2/3)
• Bibliothèque <ctime>
• Inclut diverses fonctions de gestion du temps et des dates
•Fonction
clock_t clock()• Retourne le nombre de "ticks" d'horloge depuis le lancement du processus
• Mesure du temps dépend du nombre de "ticks" par seconde
• Défini par : CLOCKS_PER_SEC
• Transformation : float t = clock() / (float) CLOCKS_PER_SEC;
• Évaluation du temps pour une portion de code :
clock_t start = clock();
// portion de code à mesurer
float t = (clock()-start)/(float)CLOCKS_PER_SEC;
669,760
Mesure du temps (3/3)
• Bibliothèque <chrono>
• Inclut diverses fonctions liées au temps (durée, instant, horloge)
• Espace de nommage : std::chrono
• Exemple :
auto début = std::chrono::system_clock::now();
// portion de code à mesurer
auto fin= std::chrono::system_clock::now();
// calcul du temps écoulé en secondes (par défaut)
std::chrono::duration<double> nbSecondes = fin- début;
// accès à la valeur du temps écoulé double t = nbSecondes.count();
std::chrono::time_point<std::chrono::system_clock>
669,760
Instructions vectorielles (1/8)
• Calculs séquentiels
• Une opération à la fois
a b +
a+b
=
X = a + b;
Y = c + d;
Z = a + d;
T = e + b; 3 additions supplémentaires
• Calculs vectoriels
• N opérations simultanées
• Optimiser l'utilisation des registres
e a c a
b d d b
e+b a+d c+d a+b
+
=
Découper les registres en N parties
Appliquer la même opération sur chacune des N parties
669,760
Instructions vectorielles (2/8)
• Historique
• Introduites en 1997 par Intel sur Pentium MMX
• MMX = MultiMedia Extensions
• 57 instructions machines vectorielles
• Ne sont plus supportées par les processeurs 64 bits
• 3D Now en 1998 par AMD
• Abandonnées en 2010 pour les SSE
• SSE (Streaming SIMD Extensions) en 1999 par Intel
• Plusieurs générations (SSE, SSE2, SSE3, SSE4)
• SSE5 en 2011 par AMD
• AVX (Advanced Vector Extensions) par Intel en 2008
• Passage des registres de 128 à 256 bits
• AVX2 en 2011
• AVX-512 en 2013 (architectures Xeon Phi x200 et Skylake-X- registres 512 bits)
669,760
Instructions vectorielles (3/8)
MMX SSE SSE2 AVX AVX2 AVX-512
__m64 Entiers 16,32 et 64bits
__m128 Réels 32 bits
__m128i Entiers 16, 32
et 64 bits
__m128d Réels 64 bits
__m256 Réels 32 bits
__m256i Entiers 8, 16,
32 et 64 bits
__m256d Réels 64 bits
__m512 Réels 32 bits
__m512i Entiers 8, 16,
32 et 64 bits
__m512d Réels 64 bits
Types de données manipulées par les instructions vectorielles
indisponible disponible
669,760
Instructions vectorielles (4/8)
• Les intrinsics
• Jeu d'instructions vectorielles
• Simplification assembleur pour C/C++
• Différentes versions
• SSE, SSE2, …, AVX, AVX2, …
• Voir : Intel Intrinsic Guide
• https://software.intel.com/sites/landingpage/IntrinsicsGuide/
• Bibliothèque :
<immintrin.h>__m256 v;
…
v = _mm256_setzero_ps();
…
256 bits
8 float
v
0 0 0
8 float
v
669,760
Instructions vectorielles (5/8)
• Les intrinsics
• Quelques exemples
Addition
__m256 sum, a, b;
…
sum = _mm256_add_ps(a, b);
…
a7 a6 a0 a
b7 b6 b0 b
a7+b7 a6+b6 a0+b0 sum
+
=
Opération Intrinsic
Addition _mm256_add_ps(__m256 a, __m256 b)
Soustraction _mm256_sub_ps(__m256 a, __m256 b)
Multiplication _mm256_mul_ps(__m256 a, __m256 b)
Division _mm256_div_ps(__m256 a, __m256 b)
Et logique _mm256_and_ps(__m256 a, __m256 b)
Comparaison _mm256_cmp_ps (__m256 a, __m256 b, const int op)
Remarques :
_ps float
_pd double
_epi8, _epi16, epi32, epi64, ... int
669,760
Instructions vectorielles (6/8)
• Traitement de vecteurs longs
• __m256 : 8 floats maximum
• Comment traiter des vecteurs de longueur > 8 ?
Parcours et recopie Parcours par pointeur
__m256 buf;
buf = _mm256_load_ps(v);
float *v;
8 float 8 float 8 float
buf = _mm256_load_ps(v+8);
buf = _mm256_load_ps(v+...);
… = _mm256_XXX_ps(buf, …)
… = _mm256_XXX_ps(buf, …)
… = _mm256_XXX_ps(buf, …)
float *v;
8 float 8 float 8 float
__m256 *pv;
pv = (__m256*)v;
pv++;
pv++;
… = _mm256_XXX_ps(*pv, …)
… = _mm256_XXX_ps(*pv, …)
… = _mm256_XXX_ps(*pv, …)
669,760
Instructions vectorielles (7/8)
• Accès aux données d'un type vectoriel
8 float __m256 buf
float *p = (float*)&buf;
p[1] ou *(p+1) p[7] ou *(p+7)
• Compilation
g++ -std=c++11 –mavx –mavx2 … -O2 ...
Selon les instructions à utiliser
g++ -std=c++11 –march=native -O2...
Détermination automatique
À utiliser systématiquement si on cherche la performance !!!
669,760
Instructions vectorielles (8/8)
• Alignement mémoire
• Sur 16 bits pour les SSE
• Sur 32 bits pour les AVX
• Variables statiques AVX
• Qui doivent être utilisées en vectoriel : alignas(32)
• Types de base (
__mxxx) : alignement automatique
• Allocations dynamiques
• _mm_malloc(nb octets, alignement)
• _mm_free(&)
…
alignas(32) int *v;
…
v= (int*)_mm_malloc(16*sizeof(int), 32);
…
669,760
Instructions vectorielles
• TP
669,760
Parallélisation multi-threads
• Rappels sur les threads
• Processus "léger"
• Ne peuvent exister qu'au sein d'un processus "lourd"
• Partagent les ressources du processus lourd
• Disposent de ressources propres
• Nombreuses utilisations
• Assurer la réactivité d'une application
• Interface utilisateur
• Réponses d'un serveur
• Parallélisme d'une application
• Etc.
669,760
Parallélisation multi-threads
• Intérêt en parallélisation
• Processeurs modernes multi-coeurs
• Possibilité d'exécuter plusieurs parties de l'application en parallèle
• Identifier les parties parallèles
• Créer un thread par partie
• Mémoire partagée
• Partage des données de l'application
• Pas de duplication
• Communication entre processus
• Contrainte : gérer les accès concurrents
Mémoire partagée
thread Mémoire
privée
thread Mémoire
privée
thread Mémoire
privée
thread thread thread
Mémoire privée
Mémoire
privée Mémoire
privée
669,760
Parallélisation multi-threads
• Les threads en C++11
• Inclus dans une bibliothèque du langage
• <thread>
• Compilation : -std=c++11 -pthread
• Code portable
• Utilise le système de threads du système hôte
• Constructeurs (non bloquants) :
std::thread()
std::thread(class fn, class ...args)
Construit un objet thread "vide"
(sans code d'exécution)
Construit un objet thread qui appelle la fonction fn et lui passe les paramètres args
669,760
Parallélisation multi-threads
• Les threads en C++11
• Quelques méthodes :
Récupération de l'id du thread courant :
std::this_thread::get_id()
Attendre la fin d'un thread (bloquant) :
join()
Nombre de coeurs :
std::thread::hardware_concurrency()
#include <thread>
#include <iostream>
void mafonction(int a, float b){
…
std::cout << std::this_thread::get_id();
… }
int main(int argc, char *argv[]){
std::thread t1;
t1 = std::thread(mafonction, 5, 3.14);
std::thread t2(mafonction, 3, 2.71);
…
t1.join();
t2.join();
return 0;
}
669,760
Parallélisation multi-threads
• Exécution
#include <thread>
#include <iostream>
void mafonction(int a, float b){
…
std::cout << std::this_thread::get_id();
… }
int main(int argc, char *argv[]){
std::thread t1;
t1 = std::thread(mafonction, 5, 3.14);
std::thread t2(mafonction, 3, 2.71);
// calculs processus principal t1.join();
t2.join();
return 0;
}
join()
point de synchronisation
t1 t2 processus principal
temps
669,760
Parallélisation multi-threads
• Accès concurrents
• Communication entre threads via mémoire partagée
• Accès en lecture : pas de soucis
• Accès en écriture :
• protéger la donnée le temps de l'écriture
• Protection par sémaphore d'exclusion mutuelle
• Bibliotèque <mutex>
• Création : std::mutex mon_mutex;
• Verrouillage : lock()
• Déverrouillage : unlock()
std::mutex mon_mutex;
…
mon_mutex.lock();
// section critique mon_mutex.unlock();
Accès par un seul thread à la fois Les autres threads sont mis en attente
669,760
Parallélisation multi-threads
• Remarque importante
• Passage de paramètre par référence à un thread
• Déréférencement et création d'une copie !!!
• Passage de paramètre par copie :
• Pas de modification possible par le thread
• Risque de désallocations multiples en fin de thread
• Appel du destructeur
The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g., with std::ref orstd::cref).
Sources :
-https://en.cppreference.com/w/cpp/thread/thread/thread - https://shawnliu.me/post/passing-reference-with-std-ref-in-c++/
669,760
Parallélisation multi-threads
• Exemple :
#include <thread>
#include <iostream>
void mafonction(Tab &t){
… }
int main(int argc, char *argv[]){
Tab tableau(200);
std::thread t1(mafonction, tableau);
std::thread t2(mafonction, tableau);
// calculs processus principal t1.join();
t2.join();
return 0;
}
class Tab { private :
int *valeurs;
int nbElts;
public:
Tab(){n=0; valeurs=nullptr;}
Tab(int dim){n = dim; valeurs = new int[dim];}
~Tab(){if(valeurs!=nullptr) delete valeurs;}
… }
mafonction(Tab &t) ne compile pas ...
mafonction(const Tab &t) compile mais risque d'erreur à l'exécution free(): double free detected in tcache 2
Abandon (core dumped)
669,760
Parallélisation multi-threads
• Solution :
• Utiliser un pointeur vers le paramètre
• Utiliser std::ref
• Crée un objet reference_wrapper contenant la référence de son paramètre
• Il peut être converti en référence au moment de l'exécution de la fonction à laquelle il est passé
void mafonction(Tab &t){
… }
int main(int argc, char *argv[]){
Tab tableau(200);
std::thread t1(mafonction, std::ref(tableau));
std::thread t2(mafonction, std::ref(tableau));
t1.join();
t2.join();
return 0;
}
669,760
Parallélisation multi-threads
• TP
669,760
OpenMP
• Introduction
• API pour le Multi-Processing (http://www.openmp.org)
• Disponible pour C/C++ et Fortran
• Cible les architectures parallèles à mémoire partagée
• Utilisation
• Bibliothèque de fonctions (<omp.h>)
• Directives de compilation
• Introduite par #pragma
• Compilation
• g++ …. -fopenmp
669,760
OpenMP
• Avantages
• Facilité d'utilisation
• Standard supporté par de nombreux compilateurs
• Portable sur de nombreuses architectures
• Code proche du code séquentiel
• Inconvénients
• Limité aux machines à mémoire partagée
• Pas de notion de communications par messages
• Réglages fins souvent plus complexes
669,760
OpenMP
• Modèle d'éxécution
• Basé sur le multi-threading de type "fork and join"
section
séquentielle section
séquentielle section
séquentielle section
parallèlle section
parallèlle
Processus principal
les threads peuvent eux-mêmes "forker"
669,760
OpenMP
• Définir une section parallèle
• Directive parallel
• Spécifier le nombre de threads
• Par défaut, utilisation de tous les threads
• clause num_threads : spécifier le nombre de threads
#pragma omp parallel { // équivalent à "fork"
std::cout << "Hello OpenMP World !!!" << std::endl;
} // équivalent à "join" section de code exécutée par tous les threads
#pragma omp parallel num_threads(2)
{ std::cout << "Hello OpenMP World !!!" << std::endl;
}
Hello OpenMP World ! Hello OpenMP World !
…
Hello OpenMP World !
Hello OpenMP World ! Hello OpenMP World !
669,760
OpenMP
• Boucles parallèles
• Découpage manuel
#define N 1000 float tab[N];
#pragma omp parallel {
int nbthreads = omp_get_num_threads();
int id = omp_get_thread_num();
int deb = id*N/nbthreads;
int fin = (id+1)*N/nbt -1;
for(int i=deb; i<fin; i++){
tab[i] = sqrt[i]*log[i];
} }
#pragma omp parallel
- Nombre maximal de threads selon l'architecture
#pragma omp parallel num_threads(2)
- 2 threads
Nombre de threads lancés
Numéro du thread (dans [0, nbthreads-1])
669,760
#define N 1000 float tab[N];
#pragma omp parallel {
#pragma omp for
for(int i=0; i<N; i++){
tab[i] = sqrt[i]*log[i];
} }
OpenMP
• Boucles parallèles
• Découpage automatique
Répartition automatique des intervalles de calcul selon le nombre de threads
Forme condensée si le code parallèle ne contient qu'une boucle
#define N 1000 float tab[N];
#pragma omp parallel for for(int i=0; i<fin; i++){
tab[i] = sqrt[i]*log[i];
}
669,760
#define N 1000 float tab[N];
#pragma omp parallel num_thread(4) {
Code1();
#pragma omp for
for(int i=0; i<N; i++){
tab[i] = sqrt[i]*log[i];
}
Code2();
}
OpenMP
• Boucles parallèles
• remarque
Code1() Code1() Code1() Code1()
Code2() Code2() Code2() Code2()
Boucle
[0, N/4-1] Boucle
[N/4, 2N/4-1] Boucle
[2N/4, 3N/4-1] Boucle [3N/4, N-1]
Thread principal
Thread principal forme obligatoire quand il existe
une autre partie de code parallèle
669,760
#define N 1000 float mat[N][N];
#pragma omp parallel for collapse (2) for(int i=0; i<N; i++)
for(int j=0; j<N; j++){
mat[i][j] = sqrt[i]*log[j];
}
OpenMP
• Boucles parallèles
• Boucles imbriquées
Spécifie le nombre de boucles
imbriquées à paralléliser Ne s'applique que sur des boucles
"parfaitement" imbriquées
!
Pas de code "intermédiaire" au code présent dans la boucle la plus imbriquée
669,760
OpenMP
• Boucles parallèles
• Ordonnancement (schedule)
• 3 modes principaux
• static (défaut) :
• répartition basée uniquement sur le nombre de threads et le nombre d'itérations
• à utiliser quand la charge de calcul est la même pour chaque tour de boucle
• dynamic :
• itérationsassignées aux threads inoccupés
• à utiliser quand chaque itération a une durée imprévisible
• guided
• Commence par affecter des "gros" blocs
• Puis diminue progressivement la taille des blocs affectés à chaque thread
669,760
OpenMP
• Boucles parallèles
• Ordonnancement (schedule)
#pragma omp parallel for schedule(...) for(int i=0; i<N; i++){
… }
static ou dynamic ou guided
• Possibilité de préciser une taille de blocs
#pragma omp parallel for schedule(mode, taille) for(int i=0; i<N; i++){
…
} Thread 0 : blocs [0,399] et [800,999]
Thread 1 : bloc [400,799]
#pragma omp parallel for schedule(static, 400) num_threads(2) for(int i=0; i<1000; i++){
… }
Exemple
669,760
OpenMP
• Boucles parallèles
• Restrictions
• Nécessité de pouvoir calculer le nombre de tours de boucles à la compilation
• L'index de boucle doit être incrémenté par un pas fixé
• L'index de boucle est privé et ne peut pas être changé dans le corps de la boucle
• Les boucles ne peuvent pas contenir de
break,
returnou
exit• Le
continueest autorisé
• Pas de boucle while parallèle
• Il faut la transformer en boucle for ...
669,760
OpenMP
• Protection des sections critiques
• Directive critical : protection d'une section de code
#pragma omp parallel {
…
#pragma omp critical {
std::cout << "…" << std::endl;
} }
Le code est exécuté en exclusion mutuelle : un seul thread y a accès à un instant t
• Directive atomic : protection de la mise à jour d'une unique variable
#pragma omp atomic
somme += z*k; La mise à jour est exécutée en exclusion mutuelle
utilisation d'un verrou logiciel
utilisation d'un verrou matériel
669,760
OpenMP
• Synchronisation des threads
• Nécessaire quand la poursuite des calculs implique que tous les threads aient
terminé leurs tâches
• Différents type de synchronisation
• Implicite
#pragma omp parallel {#pragma omp for
for(int i=0; i<N; i++) a[i] = …
#pragma omp for
for(int i=0; i<N; i++) b[i] = 2*a[(i+1)%N];
}
Les valeurs a[i] doivent être toutes connues avant de démarrer la seconde boucle
Un point de synchronisation est implicte à la suite de chaque pragma impliquant du parallélisme
• Explicite
#pragma omp parallel
{int id = omp_get_thread_num();
x[id] = fonction();
#pragma omp barrier
y[id] = x[id] + Y[(id+1)%omp_get_thread_num()];
}
point de synchronisation explicite #pragma omp parallel {#pragma omp for nowait
for(int i=0; i<N; i++) a[i] = …
#pragma omp for
for(int i=0; i<N; i++) b[i] = 2*a[i];
}
Supression de la synchronisation implicite (si pas de dépendances aux données inter-threads)
669,760
OpenMP
• Opérations de réduction
float resultat = 0;
#pragma omp parallel {
float resultat_local;
resultat_local = fonction();
#pragma omp critical
resultat += resultat_local;
}
float resultat = 0;
#pragma omp parallel reduction(+:resultat) {
resultat = fonction();
}
Opérateurs arithmétiques : + , *, -, max, min Opérateurs logiques : & , &&, |, ||, ^
Une opération de réduction
doit être appliquée Opérateur = somme
Variable sur laquelle porte la réduction
Variable locale initialisée en fonction de l'opérateur
En sortie, la variable globale contient la réduction des variables locales
669,760
OpenMP
• Variables d'environnement
• Réglage du comportement du parallélisme
• Dans les directives :
num_threads(),
schedule(), …
• Au lancement de l'application : via des variables d'environnement
• OMP_NUM_THREADS
• Spécifie le nombre de threads à utiliser
• Sans effet si présence de
num_threads()• OMP_SCHEDULE
• Spécifie le mode de répartition des calculs
• Requiert :
#pragma omp for schedule(runtime)export OMP_SCHEDULE="static,100"
export OMP_NUM_THREADS=8
Restent actifs pour toutes les applications utilisant openMP ...
!
unset OMP_SCHEDULE
OMP_NUM_THREADS=8 OMP_SCHEDULE="static, 100" ./appli Valable uniquement pour l'exécution lancée
669,760
OpenMP
• Bibliothèque OpenMP
• Nombreuses fonctions définies dans <omp.h>
• Selon la fonction, utilisation dans les threads ou le processus principal
fonction rôle
omp_set_num_threads Spécifie le nombre de threads à utiliser
omp_get_num_threads Retourne le nombre de threads actifs dans une région parallèle omp_get_max_threads Retourne le nombre maximum de threads demandés
omp_get_num_procs Retourne le nombre de cœurs disponibles omp_get_thread_num Retourne le numéro du thread (dans [0, N-1]) omp_in_parallel Teste si on se trouve dans unerégion parallèle ...
669,760
OpenMP
• Mesure du temps
double omp_get_wtime(void); Retourne le nombre de seconde depuis le "début" de l'application
double debut, fin;
debut = omp_get_wtime();
//code à mesurer
fin = omp_get_wtime();
cout << "tps = " << fin-debut << "secondes" << endl;
669,760
OpenMP
• TP
669,760
MPI
• Introduction
• MPI = Message Passing Interface
• Norme conçue en 1993/1994 - Version MPI-2 disponible depuis 1997
• Développée par une communauté d'universitaires et d'entreprises
• Devenue un standard pour programmes parallèles sur architectures distribuées
• Très répandue en calcul intensif
• Adaptée à l'échange de messages entre :
• Machines distantes (mémoire distribuée)
• Machines multi-processeurs (mémoire partagée)
• Langages :
• C/C++ (C++ déprécié depuis version 2)
• Fortran
• Python
669,760
MPI
• Modèle de programmation
• SPMD : Single Program Multiple Data
• Une même application est exécutée par N processus identiques
• Permet de gérer :
• l’environnement d’exécution
• les groupes de processus
• les communications point à point
• les communications collectives
• ...
669,760
MPI
• Initialisations
#include <iostream>
#include <mpi.h>
int main(int argc, char* argv[]){
std::cout << "hello world avant !!!" << std::endl;
// initialiser mpi
MPI_Init(&argc, &argv);
std::cout << "hello world pendant !!!" << std::endl;
// terminer les instructions MPI MPI_Finalize();
std::cout << "hello world après !!!" << std::endl;
return 0;
}
Bibliothèque de la librairie
Initialise l'environnement d'exécution de MPI Doit être appelée avant toute autre fonction MPI Nettoie l'environnement d'exécution MPI
Toutes les communications doivent être finies avant l'appel
669,760
MPI
• Compilation
• Exécution
• Utilisation du script mpirun :
mpic++ -02 fichier.cpp -o executable
mpirun [ –n nbprocess ] <chemin/nom_executable> [ paramètres exécutable ]
les scripts/commandes dépendent de l'environnement(système, matériel, version installée)
!
669,760
MPI
• Exécution
• Exemple
#include <iostream>
#include <mpi.h>
int main(int argc, char* argv[]){
std::cout << "hello world avant !!!" << std::endl;
// initialiser mpi
MPI_Init(&argc, &argv);
std::cout << "hello world pendant !!!" << std::endl;
// terminer les instructions MPI MPI_Finalize();
std::cout << "hello world après !!!" << std::endl;
return 0;
}
mpirun –n 3 essai
hello world avant !!!
hello world avant !!!
hello world avant !!!
hello world pendant !!!
hello world pendant !!!
hello world pendant !!!
hello world après !!!
hello world après !!!
hello world après !!!
Chaque processus exécute l'intégralité
du programme
669,760
MPI
• Communicateurs
• Groupe de processus travaillant/communiquant ensemble
• "Communicators"
• Type prédéfini : MPI_Comm
• Communicateur par défaut : MPI_COMM_WORLD
• L'ensemble des processus créés au lancement de l'application
• La plupart des instructions MPI requièrent d'identifier le communicateur concerné
• Possibilité de créer des sous-communicateurs
• Permet de restreindre l'exécution des instructions à certains processus
669,760
MPI
• Communicateurs
• Exemple
#include <mpi.h>
…
int nbProcess;
MPI_Comm_size(MPI_COMM_WORLD, &nbProcess);
MPI_Comm groupe= MPI_COMM_WORLD;
int myId;
MPI_Comm_rank(groupe, &myId);
Récupération du nombre de processus dans le groupe par défaut
Récupération de l'identifiant du processus dans le
groupe (dans [0,n-1])
Groupe par défaut
Variable de type groupe
669,760
MPI
• Communicateurs
• Séparations en sous communicateurs
int MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm )
Communicateur initial Nouveau communicateur
à initialiser
Id dans le nouveau communicateur
"filtre" de répartition
669,760
MPI
• Communicateurs
• Séparations en sous communicateurs
int myId;
MPI_Comm_rank(MPI_COMM_WORLD, &myId);
int color = myId % 2; // différencier processus pairs et impairs int key = myId; // conserver l'id
MPI_Comm newComm;
MPI_Comm_split(MPI_COMM_WORLD, color, key, &newComm);
0, 1, … , N-1 MPI_COMM_WORLD
0, 1, … , N-1 MPI_COMM_WORLD
0, 1, … , N-1 MPI_COMM_WORLD
0, 1, … , N-1 MPI_COMM_WORLD
P1 P0
P2
PN-1
0, 1, … , N-1 MPI_COMM_WORLD
0, 1, … , N-1 MPI_COMM_WORLD
0, 1, … , N-1 MPI_COMM_WORLD
0, 1, … , N-1 MPI_COMM_WORLD
0, 2, 4, ...
1, 3, 5, ...
0, 2, 4, ...
1, 3, 5, ...
newComm newComm
newComm
newComm
P1 P0
P2
PN-1
669,760
MPI
• Types de données
• Utilisation des types de données du langage cible
• Restrictions à certains types pour les communications
MPI_CHAR, MPI_UNSIGNED_CHAR MPI_INT, MPI_UNSIGNED
MPI_LONG, MPI_UNSIGNED_LONG MPI_FLOAT, MPI_DOUBLE
...
• Possibilité de créer ses propres types de données
669,760
MPI
• Communications point-à-point : MPI_Send
• Envoi de données à un autre processus
• Appel bloquant ...
int MPI_Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
adresse du buffer contenant le
message / les données à envoyer Nombre d'entrées dans
le buffer d'émission Type des données à envoyer
Id du processus
récepteur Tag du message
(généralement 0) Communicateur
669,760
MPI
• Communications point-à-point : MPI_Recv
• Réception de données depuis un autre processus
• Appel bloquant
int MPI_Recv(void *buf, int count, MPI_Datatype datatype,
int source, int tag, MPI_Comm comm, MPI_Status *status)
adresse du buffer devant contenir
le message / les données reçu(es) Nombre d'entrées dans
le buffer de réception Type des données à recevoir
Id du processus
émetteur Tag du message
(généralement 0) Communicateur Informations
sur le message
669,760
MPI
• Communications point-à-point
• Exemples
int buffer[N];
int numproc=…;
MPI_Recv(buffer, N, MPI_INT, numproc-1, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
…
MPI_Send(buffer, N, MPI_INT, numproc+1, 0, MPI_COMM_WORLD);
Paramètre "tag" non utilisé Paramètre "status" non utilisé Émetteur connu
récepteur connu
669,760
MPI
• Communications point-à-point
• Exemples
int buffer[N];
MPI_Status mesgStatus;
MPI_Recv(buffer, N, MPI_INT, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD,
&mesgStatus);
…
MPI_Send(buffer, N, MPI_INT,
mesgStatus.MPI_SOURCE, 0, MPI_COMM_WORLD);
Paramètre "status" utilisé Émetteur inconnu
Récupération du récepteur dans le message
typedef struct _MPI_Status { int count;
int cancelled;
int MPI_SOURCE;
int MPI_TAG;
int MPI_ERROR;
} MPI_Status;
669,760
MPI
• Communications collectives
broadcast
scatter
3 1 2 5
11
reduction
gather
669,760
MPI
• Communications collectives : MPI_Reduce
• Combine des valeurs issues des différents processus vers un processus maître
int MPI_Reduce(const void *sendbuf, void *recvbuf, int count,
MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)
MPI_SUM, MPI_PROD MPI_MIN, MPI_MAX,
MPI_MINLOC, MPI_MAXLOC MPI_LAND, MPI_LOR, MPI_LXOR MPI_BAND, MPI_BOR, MPI_BXOR adresse du buffer des
valeurs à combiner adresse du buffer devant
contenir le résultat Nombre d'entrées dans le buffer des valeurs à combiner
Type des valeurs à récupérer Opérateur de combinaison Id du maître Groupe des processus concernés
669,760
MPI
• Communications collectives : MPI_Allreduce
• Combine des valeurs issues des différents processus
• Redistribue le résultats aux différents processus
int MPI_Allreduce(const void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
adresse du buffer des
valeurs à combiner adresse du buffer devant
contenir le résultat Nombre d'entrées dans le buffer des valeurs à combiner
Type des valeurs à récupérer Opérateur de combinaison Groupe des processus concernés
Pas de "maître" car redistribution
!
669,760
MPI
• Communications collectives : MPI_Bcast
• Diffuse des données du processus maître vers les autres processus du groupe
int MPI_Bcast(void *buffer, int count,
MPI_Datatype datatype, int root, MPI_Comm comm)
Le paramètre "buffer" existe dans tous les processus :
! Le maître "initialise" sa(ses) valeur(s) et la(les) diffuse
Les autres processus reçoivent la(les) valeur(s) et mettent à jour leur buffer
adresse du buffer des
valeurs à diffuser Nombre d'entrées dans le
buffer des valeurs à diffuser Groupe des processus concernés
Type des données du buffer Id du processus maître
669,760
MPI
• Communications collectives : MPI_Scatter
• "Eparpille" des données du processus maître vers les autres processus
int MPI_Scatter(const void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype,
int root, MPI_Comm comm)
Adresse du buffer des valeurs à envoyer
(sur le maître)
Nombre d'entrées du buffer des
valeurs à envoyer vers chaque processus Type des données des buffers
Adresse du buffer des valeurs à réceptionner (sur les autres processus)
Id du processus maître Groupe des processus concernés
Nombre d'entrées dans le buffer des valeurs à réceptionner
669,760
MPI
• Communications collectives : MPI_Scatter
• Exemple
int valeur, *tabvaleurs;
if (procno==root)
tabvaleurs = (int*) malloc( nprocs*sizeof(int) );
// initialisation des entrées dans tabvaleurs
// "éparpillement" des informations de chaque processus
MPI_Scatter(tabvaleurs,1,MPI_INT,&valeur,1,MPI_INT,root,comm);
valeur valeur
valeur
valeur tabvaleurs
P0
P1
P2
Pn
669,760
MPI
• Communications collectives : MPI_Gather
• Collecte des données des processus vers le processus maître
int MPI_Gather(const void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype,
int root, MPI_Comm comm)
adresse du buffer des valeurs à envoyer (sur les processus)
Nombre d'entrées dans le
buffer des valeurs à envoyer Type des données des buffers
Nombre d'entrées à réceptionner dans le buffer des valeurs depuis
chaque processus adresse du buffer des
valeurs à réceptionner (sur le maître)
Id du processus maître Groupe des processus concernés
669,760
MPI
• Communications collectives : MPI_Gather
• Exemple
int valeur, *tabvaleurs;
if (procno==root)
tabvaleurs = (int*) malloc( nprocs*sizeof(int) );
// récolte des informations de chaque processus
MPI_Gather(&valeur,1,MPI_INT,tabvaleurs,1,MPI_INT,root,comm);
valeur valeur
valeur
valeur tabvaleurs
P0
P1
P2
Pn
669,760
MPI
• Mesure du temps : MPI_Wtime()
• Exemple :
double t0 = MPI_Wtime();
...
double t1 = MPI_Wtime();
...
std::cout << "time = " << t1 - t0 << std::endl;
669,760
MPI
• TP
• Exemples
• Simulation d'épandage de GPL
• Simulations LOG climat
• Autres : films en SI (trouver des chiffres), météo
• Research labs. HPC is used to help scientists find sources of renewable energy, understand the evolution of our universe, predict and track storms, and create new materials.
• Media and entertainment. HPC is used to edit feature films, render mind-blowing special effects, and stream live events around the world.
• Oil and gas. HPC is used to more accurately identify where to drill for new wells and to help boost production from existing wells.
• Artificial intelligenceand machine learning. HPC is used to detect credit card fraud, provide self-guided technical support, teach self-driving vehicles, and improve cancer screening techniques.
• Financial services. HPC is used to track real-time stock trends and automate trading.
• HPC is used to design new products, simulate test scenarios, and make sure that parts are kept in stock so that production li nes aren’t held up.
• HPC is used to help develop cures for diseases like diabetes and cancer and to enable faster, more accurate patient diagnosis.
• Voir https://eurohpc-ju.europa.eu/discover-eurohpc#ecl-inpage-14