• Aucun résultat trouvé

High Performance Computing

N/A
N/A
Protected

Academic year: 2022

Partager "High Performance Computing"

Copied!
86
0
0

Texte intégral

(1)

High Performance Computing

M1 Informatique – C. Renaud

Version 1.4.3 du 25/06/2021

(2)

Introduction

• Définition

Algorithmes numériques

Programmation parallèle

Applications complexes

HPC

(3)

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

(4)

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 coeurs100 millions d'heures de calcul

(5)

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

(6)

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, ...

(7)

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

(8)

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

(9)

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

(10)

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

(11)

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

(12)

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

(13)

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

(14)

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

(15)

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

(16)

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

(17)

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)

(18)

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

(19)

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

(20)

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

(21)

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

(22)

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;

(23)

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>

(24)

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

(25)

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)

(26)

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

(27)

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

(28)

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

(29)

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, …)

(30)

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 !!!

(31)

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);

(32)

669,760

Instructions vectorielles

• TP

(33)

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.

(34)

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

(35)

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

(36)

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;

}

(37)

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

(38)

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

(39)

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++/

(40)

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)

(41)

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;

}

(42)

669,760

Parallélisation multi-threads

• TP

(43)

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

(44)

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

(45)

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"

(46)

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 !

(47)

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])

(48)

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];

}

(49)

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

(50)

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

(51)

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

(52)

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

(53)

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

,

return

ou

exit

• Le

continue

est autorisé

• Pas de boucle while parallèle

• Il faut la transformer en boucle for ...

(54)

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

(55)

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)

(56)

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

(57)

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

(58)

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 ...

(59)

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;

(60)

669,760

OpenMP

• TP

(61)

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

(62)

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

• ...

(63)

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

(64)

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)

!

(65)

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

(66)

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

(67)

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

(68)

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

(69)

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

(70)

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

(71)

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

(72)

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

(73)

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

(74)

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;

(75)

669,760

MPI

• Communications collectives

broadcast

scatter

3 1 2 5

11

reduction

gather

(76)

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

(77)

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

!

(78)

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

(79)

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

(80)

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

(81)

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

(82)

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

(83)

669,760

MPI

• Mesure du temps : MPI_Wtime()

• Exemple :

double t0 = MPI_Wtime();

...

double t1 = MPI_Wtime();

...

std::cout << "time = " << t1 - t0 << std::endl;

(84)

669,760

MPI

• TP

(85)

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

(86)

• 3 composantes principales

• Noeuds de calculs

• Réseau de communication

• Espace de stockage

• Le réseau doit supporter

• Les echanges entre noeuds de calculs le plus rapidement possible

• Les E/S dans l'espace de stockage en temps réel

• Sous peine de reduire l'efficacité de l'architecture

Références

Documents relatifs

On fait cela tant qu’il y a plusieurs ´el´ements dans le tableau.. Si le tableau est de taille impaire, son ´el´ement central

Avec cmovl on a if(*t&lt;m) m=*t; et la fonction calcule le plus petit ´el´ement.. Avec cmova la comparaison est

On oublie donc les s ` a la fin de vingt, cent, million

int MPI_Sendrecv (void *sendbuf,int sendcount,MPI_Datatype sendtype, int dest,int sendtag,void *recvbuf,int recvcount,MPI_Datatype recvtype,int source,int recv

Because the design of the glass elements of CHARACTER COUPLES is borrowed from the porcelain pieces, the combination of the two gives rise to a clear design rhythm.. Each

ARTÍCULOS PARA ALIMENTOS FINGERFOOD SCENARIO GN hace del bufé un entretenido espectáculo: las bandejas Gastronorm se complementan con un original concepto de platos y

int MPI_Sendrecv (const void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, int

public void Scatter(Object sendbuf, int sendoffset, int sendcount, Datatype sendtype, Object recvbuf, int recvoffset, int recvcount, Datatype recvtype, int root)