INF442 : Traitement des données massives
Transcription
INF442 : Traitement des données massives
INF442 : Traitement des données massives A4 : Algèbre linéaire distribuée Frank Nielsen X2013 6 mai 2015 Plan ◮ un peu de MPI ◮ produit matriciel sur la topologie du tore ◮ la généricité avec la bibliothèque C++ STL MPI : pas de mémoire globale ! → mémoire locale pour chaque processus, échange de messages Différent d’un fil de calcul (fork) avec mémoire globale partagée (INF431) i n t main ( i n t a r g c , c h a r ∗∗ a r g v ) { i n t ra ng , n , v a r ; i n t ∗ p t r=&v a r ; M P I I n i t (& a r g c , &a r g v ) ; MPI Comm size (MPI COMM WORLD, &n ) ; MPI Comm rank (MPI COMM WORLD, &r a n g ) ; ∗ p t r=r a n g ; ( ∗ p t r )++; p r i n t f ( ”P%d v a r=%d\n” , ra ng , v a r ) ; MPI Finalize () ;} P0 P2 P1 P3 var =1 var =3 var =2 var =4 #i n c l u d e <s t d i o . h> #i n c l u d e <mpi . h> i n t main ( i n t a r g c , c h a r∗∗ a r g v ) { i n t rang , p , a u t r e , t a g a =0, t a g b =1; d o u b l e a , b ; M P I S t a t u s s t a t u s ; MP I Re que st r e q u e s t ; M P I I n i t (& a r g c , &a r g v ) ; MPI Comm size (MPI COMM WORLD, &p ) ; MPI Comm rank ( MPI COMM WORLD, &r a n g ) ; i f ( p==2) { // Mémoire locale de chaque processus a u t r e=1−r a n g ; // l’autre processus a =0; b =1; p r i n t f ( ” P roc . %d a u t r e=%d a v a n t a=%f b=%f \n” , rang , a u t r e , a , b ) ; // double swap en utilisant une opération de communication sans variable locale tmp ! // on utilise en fait le buffer de communication pour tmp M P I I s e n d (&a , 1 , MPI DOUBLE , a u t r e , taga , MPI COMM WORLD, &r e q u e s t ) ; M P I I s e n d (&b , 1 , MPI DOUBLE , a u t r e , tagb , MPI COMM WORLD, &r e q u e s t ) ; p r i n t f ( ” A t t e n d o n s a v e c MPI WAIT que l e s m e s s a g e s s o i e n t b i e n p a r t i s . . . \ n” ) ; MPI Wait(& r e q u e s t , &s t a t u s ) ; // Reçoit dans a le message avec tagb (donc la valeur de b) MPI Recv(&a , 1 , MPI DOUBLE , a u t r e , tagb , MPI COMM WORLD, &s t a t u s ) ; // Reçoit dans b le message avec taga (donc la valeur de a) MPI Recv(&b , 1 , MPI DOUBLE , a u t r e , taga , MPI COMM WORLD, &s t a t u s ) ; p r i n t f ( ” P roc . %d a p r e s a=%f b=%f \n ” , rang , a , b ) ; } else i f ( r a n g ==0) p r i n t f ( ” E x e c u t e z a v e c m pi run −np 2 mpiswap442 . e x e ” ) ; MP I Fi nal i ze () ;} P0 P1 taga=0; tagb=1; a=0; b=1; Isend(a,P1,taga); Isend(b,P1,tagb); MPI Wait; 0, taga 1, tagb Recv(&a,tagb); Recv(&b,taga); mémoire locale P0 taga=0; tagb=1; a=0; b=1; Isend(a,P0,taga); Isend(b,P0,tagb); 0, taga 1, tagb MPI Wait; Recv(&a,tagb); Recv(&b,taga); mémoire locale P1 [ france ~] $ mpirun - np 2 mpiswap442 . exe Proc . 1 autre =0 avant a =0.000000 b =1.000000 Attendons avec MPI_WAIT que les messages soient bien partis ... Proc . 0 autre =1 avant a =0.000000 b =1.000000 Attendons avec MPI_WAIT que les messages soient bien partis ... Proc . 1 apres a =1.000000 b =0.000000 Proc . 0 apres a =1.000000 b =0.000000 Algèbre linéaire en parallèle : la régression Frank Nielsen 1.Les matrices en HPC-1.Régression A6-6 La régression linéaire Frank Nielsen ◮ ◮ ◮ ◮ ◮ on veut prédire ŷ = f (x) avec f (x) = β̂0 + Rd Pd i =1 β̂i xi . les observations (xi , yi ) sont dans × R. Pour des classes C0 et C1 (valeurs de y ), on peut encoder y = 0 ssi. xi ∈ C0 et y = 1 ssi. xi ∈ C1 on classifie avec la régression en évaluant yˆi = f (xi ) puis en seuillant : xi ∈ C0 ssi. yˆi < 12 et xi ∈ C1 ssi. yˆi ≥ 12 on peut augmenter l’espace des données en rajoutant une coordonnée x ← (x, 1) et Pd x0 = 1. Ainsi ⊤ f (x) = i =0 β̂i xi = xi β (d + 1 paramètres à évaluer) l’erreur que l’on veut minimiser est les moindres carrés ( Residual Sum of Squares , RSS) : n X (yi − xi⊤ β)2 β̂ = min β i =1 1.Les matrices en HPC-1.Régression A6-7 La régression linéaire et la classification Frontière de décision = hyperplan (espace affine de dimension d − 1 dans Rd ) Frank Nielsen 1.Les matrices en HPC-1.Régression A6-8 La régression linéaire ordinaire Soit X la matrice des données de dimension n × (d + 1), y le vecteur colonne de dimension n et β le vecteur paramètre de dimension d + 1. On a la somme des différences au carré : RSS(β) = n X i =1 (yi − xi⊤ β)2 = (y − X β)⊤ (y − X β) En prenant le gradient ∇β RSS(β), on trouve l’équation dite normale ( normal equation ) : X ⊤ (y − X β) = 0 Pour X ⊤ X non-singulière, on trouve β̂ minimisant les moindres carrés par la matrice pseudo-inverse (Penrose-Moore) : Frank Nielsen β̂ = (X ⊤ X )−1 X ⊤ y = X † y 1.Les matrices en HPC-1.Régression A6-9 La régression linéaire en Scilab rand ( ’ seed ’ , getdate ( ’s ’) ) x = -30:30; a =0.8; b =5; y = a *x+b; // on perturbe avec un bruit uniforme bruit = rand (1 ,61 , ’ uniform ’) -0.5; y = y +10* bruit ; // regression linéaire en scilab [ aa , bb ] = reglin (x , y ) ; plot (x , y , ’ r + ’ ) ; plot (x , a * x +b , ’ bo - ’) Frank Nielsen 1.Les matrices en HPC-1.Régression A6-10 La régression linéaire : ordinaire ou totale y y =a×x (x2 , y2 ) (x3 , y3 ) x (x1 , y1 ) ordinary regression vs. total regression Frank Nielsen 1.Les matrices en HPC-1.Régression A6-11 Comparaison de la classification par régression ou par k-PPV Classifieur sur un vecteur aléatoire = variable aléatoire ⇒ variance et biais Frank Nielsen 1.Les matrices en HPC-1.Régression A6-12 Comparaison de la classification par régression vs. k-PPV Frank Nielsen ◮ ◮ régression = bon pour interpoler et extrapoler mais modèle rigide avec l’hypothèse globale d’une fonction linéaire f (x) (faible complexité = d + 1 paramètres). ⇒ grand biais et petite variance k-PPV : modèle f (x) localement constant, flexible, mais grande complexité = d × n “paramètres”. ⇒ petit biais mais grande variance 1.Les matrices en HPC-1.Régression A6-13 Algèbre linéaire : les briques de base ◮ des vecteurs colonnes : v1 v = ... vl Frank Nielsen ◮ des matrices (square, skinny, ou fat) : m1,1 ... m1,c .. .. M = ... . . ml,1 ... ml,c ◮ plusieurs types de matrices avec leur stockage mémoire : matrices denses O(lc), matrices diagonales, matrices symétriques, matrices triangulaires, matrices creuses O(l + c). Algèbre multi-linéaire et tenseurs. 1.Les matrices en HPC-1.Régression A6-14 Les opérations/primitives en algèbre linéaire Soit l = c = d les dimensions des matrices et vecteurs. ◮ ◮ ◮ ◮ le produit scalaire v1 · v2 = v1⊤ × v2 : O(d) le produit matrice-vecteur M × v : O(d 2 ) le produit matrice-matrice M1 × M2 : O(d 3 ) la factorisation (décomposition) LU M = L × U (pour résoudre les systèmes linéaires), QR, etc. Toutes ces primitives sont implémentées dans la bibliothèque BLAS, Basic Linear Algebra Subroutines en plusieurs niveaux http://www.netlib.org/blas/ Frank Nielsen 1.Les matrices en HPC-1.Régression A6-15 La multiplication matricielle : un défi = problème ouvert ! Frank Nielsen ◮ même en séquentiel, on ne connait pas d’algorithme optimal ! ◮ borne inférieure : Ω(d 2 ), nombre d’entrées de la matrice carrée résultat. ◮ meilleur algorithme connu à ce jour : O(d 2.3728639 ) , analyse fine de l’algorithme de Coppersmith et Winograd. Le Gall, François (2014), “Powers of tensors and fast matrix multiplication,” Proceedings of the 39th International Symposium on Symbolic and Algebraic Computation (ISSAC 2014), arXiv:1401.7714 1.Les matrices en HPC-1.Régression A6-16 Différents motifs pour le parallélisme de données ◮ accès et transmissions des données M et v sur un cluster de machines : dépend de la topologie du réseau d’interconnexion ◮ dispositions bloc-colonnes et bloc-colonne cycliques → largeur b du bloc élémentaire (chaque bloc tient dans la mémoire locale) Idem si on prend les lignes (= colonnes de la matrice transposée) Différents motifs pour le parallélisme des données Motif 2D bloc ligne-colonne , et 2D bloc ligne-colonne cyclique Damier, échiquier Le produit matrice vecteur sur la topologie de l’anneau orienté Produit matrice-vecteur sur l’anneau : Bloc colonne 1D En BLAS, une opération de base : y ← y + Ax A(i ) = Ai × pn :(i +1)× pn −1,· : sous-matrice bloc ligne de dimension n × pn y (i ) ← y (i ) + A(i ) × x(i ) = y (i ) + X j A[i ][j] × x[j] ◮ initialement, A(i ), x(i ) et y (i ) sont stockés sur le processus Pi ◮ faire tourner les sous-vecteurs x(i ) sur la topologie de l’anneau orienté Regardons la situation pour y (1) P1 A1,1 A1,2 A1,3 A1,4 X1 P2 A2,1 A2,2 A2,3 A2,4 X2 P3 A3,1 A3,2 A3,3 A3,4 X3 P4 A4,1 A4,2 A4,3 A4,4 X4 A1,1 A1,2 A1,3 A1,4 X4 A2,1 A2,2 A2,3 A2,4 X1 A3,1 A3,2 A3,3 A3,4 X2 A4,1 A4,2 A4,3 A4,4 X3 Y1 = A1,1 × X1 Y1 = A1,4 × X4 + A1,1 × X1 En fond gris, les blocs qui servent aux produits locaux y (·) ← A(·, ·)x(·) + y (·) A1,1 A1,2 A1,3 A1,4 X3 A2,1 A2,2 A2,3 A2,4 X4 A3,1 A3,2 A3,3 A3,4 X1 A4,1 A4,2 A4,3 A4,4 X2 A1,1 A1,2 A1,3 A1,4 X2 A2,1 A2,2 A2,3 A2,4 X3 A3,1 A3,2 A3,3 A3,4 X4 A4,1 A4,2 A4,3 A4,4 X1 Y1 = A1,3 × X3 + A1,4 × X4 + A1,1 × X1 Y1 = A1,2 × X2 + A1,3 × X3 + A1,4 × X4 + A1,1 × X1 p r o d u i t M a t r i c e V e c t e u r (A , x , y ) { q = Comm rank ( ) ; // rang du processus p = Comm size ( ) ; // nombre de processus r = n/p ; // taille des blocs f o r ( s t e p =0; s t e p <p ; s t e p++) { // on envoie le bloc de x sur le prochain nœud de l’anneau s e n d ( x , r ) ; // communication non-bloquante // calcul local : produit matrice-vecteur bloc f o r ( i =0; i <r ; i ++) { f o r ( j =0; j <r ; j ++) { y [ i ] = y [ i ] + a [ i , ( q−s t e p mod p ) r + j ] ∗ x[ j ]; } } // on reçoit le bloc de x du processus précédent de l’anneau r e c e i v e ( temp , r ) ; x = temp ; } } Produit matriciel parallèle Les algorithmes parallèles vont dépendre : ◮ des motifs des données ◮ de la topologie du réseau d’interconnexion des machines ◮ des types d’opérations de communications utilisés Coût d’une communication entre deux nœuds voisins : Temps Message = Latence + #longeur × temps par unité de longeur Frank Nielsen Temps Message = α + τ l ◮ on mesure α et τ en équivalent FLOPS ◮ efficacité : temps séquentiel/(P × temps parallèle) ◮ speed-up optimal ⇔ efficacité = 1 1.Les matrices en HPC-4.Complexité des communications A6-24 Le produit matriciel sur un cluster de machines C =A×B ◮ les éléments des matrices n × n sont initialement distribués sur les P processus P1 , ..., PP−1 ◮ on échange par messages des matrices blocs (rappel MPI : pas de mémoire partagée globale) plusieurs motifs de décompositions : ◮ ◮ ◮ ◮ ◮ blocs de lignes blocs de colonnes blocs de damiers les décompositions sont en rapport avec les algorithmes et le réseau d’interconnexion (graphe complet, anneau, tore) Le tore 2D ◮ ◮ √ on considére P ∈ N le côté de la grille torique à √ √ P × P = P processeurs (NB : anneau = tore 1D) chaque processeur Pi peut communiquer avec ses 4 voisins : Nord, Sud, Est, Ouest Produit matriciel C = A × B sur le tore ◮ ◮ initialement, les matrices sont stockés par bloc avec le motif de damier (par bloc 2D) sur le tore. √ le processus Pi ,j pour i , j ∈ {1, ..., P} est responsable du calcul de √ P X C (i , j) = A(i , k) × B(k, j) k=1 Plusieurs façons de transmettre les matrices blocs A(·, ·), B(·, ·) et C (·, ·). → nous allons voir trois principaux algorithmes Produit matriciel : l’algorithme de Cannon Frank Nielsen 3.Produit matriciel-1.L’algorithme de Cannon A6-28 Algorithme de Cannon : vue générale Frank Nielsen ◮ ◮ nécessite des opérations de pre-skewing des matrices avant les calculs locaux et des opérations de post-skewing après ces calculs locaux les communications des sous-matrices A et B sont des rotations horizontales (←) et des rotations verticales (↑). 3.Produit matriciel-1.L’algorithme de Cannon A6-29 Frank Nielsen Initialisation Pre-processing : Preskewing étape 1 : Calculs locaux Rotations A0,0 A0,1 A0,2 B0,0 B0,1 B0,2 A0,0 B0,0 A0,1 B0,1 A0,2 B0,2 A1,0 A1,1 A1,2 B1,0 B1,1 B1,2 A1,0 B1,0 A1,1 B1,1 A1,2 B1,2 A2,0 A2,1 A2,2 B2,0 B2,1 B2,2 A2,0 B2,0 A2,1 B2,1 A2,2 B2,2 A0,0 A0,1 A0,2 B0,0 B1,1 B2,2 A0,0 B0,0 A0,1 B1,1 A0,2 B2,2 A1,2 A1,0 B1,0 B2,1 B0,2 A1,1 B1,0 A1,2 B2,1 A1,0 B0,2 A2,0 A2,1 B2,0 B0,1 B1,2 A2,2 B2,0 A2,0 B0,1 A2,1 B1,2 A0,0 B1,0 B2,1 B0,2 A0,1 B1,0 A0,2 B2,1 A0,0 B0,2 A1,1 A2,2 A0,1 étape 2: Calculs locaux Rotations A0,2 A1,2 A1,0 A1,1 B2,0 B0,1 B1,2 A1,2 B2,0 A1,0 B0,1 A1,1 B1,2 A2,0 A2,1 A2,2 B0,0 B1,1 B2,2 A2,0 B0,0 A2,1 B1,1 A2,2 B2,2 3.Produit matriciel-1.L’algorithme de Cannon A6-30 Frank Nielsen étape 3 : Calculs locaux Rotations Postprocessing: Post-skewing Configuration initiale ! A0,2 A0,0 A0,1 B2,0 B0,1 B1,2 A0,2 B2,0 A0,0 B0,1 A0,1 B1,2 A1,0 A1,1 A1,2 B0,0 B1,1 B2,2 A1,0 B0,0 A1,1 B1,1 A1,2 B2,2 A2,1 A2,2 A2,0 B1,0 B2,1 B0,2 A2,0 B0,0 A2,1 B1,1 A2,0 B0,2 A0,0 A0,1 A0,2 B0,0 B1,1 B2,2 A0,0 B0,0 A0,1 B1,1 A0,2 B2,2 A1,1 A1,2 A1,0 B1,0 B2,1 B0,2 A1,1 B1,0 A1,2 B2,1 A1,0 B0,2 A2,2 A2,0 A2,1 B2,0 B0,1 B1,2 A2,2 B2,0 A2,0 B0,1 A2,1 B1,2 A0,0 A0,1 A0,2 B0,0 B0,1 B0,2 A0,0 B0,0 A0,1 B0,1 A0,2 B0,2 A1,0 A1,1 A1,2 B1,0 B1,1 B1,2 A1,0 B1,0 A1,1 B1,1 A1,2 B1,2 A2,0 A2,1 A2,2 B2,0 B2,1 B2,2 A2,0 B2,0 A2,1 B2,1 A2,2 B2,2 3.Produit matriciel-1.L’algorithme de Cannon A6-31 // Pré-traitement des matrices A et B // Preskew ← : éléments diagonaux de A alignés verticalement sur la première colonne PreskewHorizontal(A); // Preskew ↑ : éléments diagonaux de B alignés horizontalement sur la première ligne PreskewVertical(B); // Initialise les blocs de C à 0 C = 0; √ pour k = 1 à P faire C ← C +ProduitsLocaux(A,B); // décalage vers la gauche ← RotationHorizontale(A); // décalage vers le haut ↑ RotationVerticale(B); fin // Post-traitement des matrices A et B : inverses du pré-traitement // Preskew → PostskewHorizontal(A); // Preskew ↓ PostskewVertical(B); Frank Nielsen opérations 3.Produit matriciel-1.L’algorithme de Cannon A6-32 Produit matriciel : algorithme de Fox Frank Nielsen 3.Produit matriciel-2.Algorithme de Fox A6-33 Algorithme de Fox ◮ initialement, les données ne bougent pas (= pas de pré-traitement) ◮ diffusions horitonzales des diagonales de A (décalées vers la droite) ◮ rotations verticales de B, de bas en haut ... appelé aussi algorithme broadcast-multiply-roll Frank Nielsen 3.Produit matriciel-2.Algorithme de Fox A6-34 Frank Nielsen A0,0 A0,0 B0,0 B0,1 B0,2 A0,0 B0,0 A0,0 B0,1 A0,0 B0,2 A1,1 A1,1 B1,0 B1,1 B1,2 A1,1 B1,0 A1,1 B1,1 A1,1 B1,2 A2,2 A2,2 A2,2 B2,0 B2,1 B2,2 A2,2 B2,0 A2,2 B2,1 A2,2 B2,2 A0,0 A0,0 A0,0 B1,0 B1,1 B1,2 A1,1 A1,1 B2,0 B2,1 B2,2 A2,2 A2,2 A2,2 B0,0 B0,1 B0,2 A0,1 A0,1 A0,1 B1,0 B1,1 B1,2 A0,1 B1,0 A0,1 B1,1 A0,1 B1,2 A1,2 A1,2 A1,2 B2,0 B2,1 B2,2 A1,2 B2,0 A1,2 B2,1 A1,2 B2,2 A2,0 A2,0 A2,0 B0,0 B0,1 B0,2 A2,0 B0,0 A2,0 B0,1 A2,0 B0,2 A0,0 étape 1 : Diffusion A (première diagonale) Calculs locaux étape 1’: Rotation verticale de B étape 2 : Diffusion A (deuxième diagonale) Calcul locaux A1,1 A1,1 3.Produit matriciel-2.Algorithme de Fox A6-35 Frank Nielsen étape 2’: Rotation verticale de B étape 3: Diffusion A (troisième diagonale) Calculs locaux étape 3’: Rotation verticale de B → état final A0,1 A0,1 A0,1 B2,0 B2,1 B2,2 A1,2 A1,2 A1,2 B0,0 B0,1 B0,2 A2,0 A2,0 A2,0 B1,0 B1,1 B1,2 A0,2 A0,2 A0,2 B2,0 B2,1 B2,2 A0,2 B2,0 A0,2 B2,1 A0,2 B2,2 A1,0 A1,0 A1,0 B0,0 B0,1 B0,2 A1,0 B0,0 A1,0 B0,1 A1,0 B0,2 A2,1 A2,1 A2,1 B1,0 B1,1 B1,2 A2,1 B1,0 A2,1 B1,1 A2,1 B1,2 B0,0 B0,1 B0,2 A0,0 B0,0 A0,1 B0,1 A0,2 B0,2 B1,0 B1,1 B1,2 A1,0 B1,0 A1,1 B1,1 A1,2 B1,2 B2,0 B2,1 B2,2 A2,0 B2,0 A2,1 B2,1 A2,2 B2,2 3.Produit matriciel-2.Algorithme de Fox A6-36 // Initialise les blocs de C à 0 C = 0; √ pour i = 1 à P faire // Broadcast Diffusion de la i -ième diagonale de A sur les lignes de processus du tore; // Multiply C ← C +ProduitsLocaux(A,B); // Roll // Rotation verticale : décalage vers le haut ↑ RotationVerticale(B); fin Frank Nielsen 3.Produit matriciel-2.Algorithme de Fox A6-37 Produit matriciel : algorithme de Snyder Frank Nielsen 3.Produit matriciel-3.Algorithme de Snyder A6-38 Produit matriciel : algorithme de Snyder Frank Nielsen ◮ ◮ initialement, on transpose B : B ← B ⊤ sommes globales (reduce) sur les lignes de processeurs ◮ accumulation des résultats sur les diagonales principales de C (décalées à chaque étape vers la droite) ◮ rotations verticales de bas en haut A0,0 A0,1 A0,2 troisième diagonale A1,0 A1,1 A1,2 deuxième diagonale A2,0 A2,1 A2,2 première diagonale 3.Produit matriciel-3.Algorithme de Snyder A6-39 Initialisation Pre-processing : Transpose B → B ⊤ A0,0 A0,1 A0,2 B0,0 B0,1 B0,2 A1,0 A1,1 A1,2 B1,0 B1,1 B1,2 A2,0 A2,1 A2,2 B2,0 B2,1 B2,2 A0,0 A0,1 A0,2 B0,0 B1,0 B2,0 A1,2 A1,0 B0,1 B1,1 B2,1 A2,2 A2,0 A2,1 B0,2 B1,2 B2,2 A0,0 A0,1 A0,2 B0,0 B1,0 B2,0 A1,2 A1,0 B0,1 B1,1 B2,1 A2,0 A2,1 B0,2 B1,2 B2,2 A1,1 étape 1: Calculs locaux et accumulation sur la première diagonale de C P P P Frank Nielsen A1,1 A2,2 B⊤ 3.Produit matriciel-3.Algorithme de Snyder C0,0 C1,1 C2,2 A6-40 étape 1’: Rotation verticale de B A0,0 A0,1 A0,2 B0,1 B1,1 B2,1 A1,2 A1,0 B0,2 B1,2 B2,2 A2,2 A2,0 A2,1 B0,0 B1,0 B2,0 A0,0 A0,1 A0,2 B0,1 B1,1 B2,1 A1,2 A1,0 B0,2 B1,2 B2,2 A2,2 A2,0 A2,1 B0,0 B1,0 B2,0 A0,0 A0,1 A0,2 B0,2 B1,2 B2,2 A1,2 A1,0 B0,0 B1,0 B2,0 A2,0 A2,1 B0,1 B1,1 B2,1 A1,1 P étape 2: Calculs locaux et accumulation sur P la deuxième diagonale de C étape 2’: Rotation verticale de B P étape 3: Calculs locaux et accumulation sur la troisième diagonale de C P P P Frank Nielsen A1,1 A1,1 A2,2 3.Produit matriciel-3.Algorithme de Snyder C0,1 C1,2 C2,0 C0,2 C1,0 C2,1 A6-41 // Preskewing Transpose B; // Phase de calcul √ for k = 1 to P do // Produit scalaire ligne par ligne sur A et B Calcule localement par bloc : C = A × B; // On calcule les matrices blocs définitives de C pour la k-ième diagonale // Somme globale équivaut au produit scalaire d’une ligne de A avec une ligne de B P Somme globale de C sur les processeurs lignes pour la k-ième diagonale de C ; Décalage vertical de B; end // On transpose B afin de retrouver la matrice initiale Transpose B; Frank Nielsen 3.Produit matriciel-3.Algorithme de Snyder A6-42 En résumé Le produit matriciel sur le tore : ◮ algorithme de Cannon (pré-processing) ◮ algorithme de Fox (broadcast-multiply-roll) ◮ algorithme de Snyder (sommes globales) Comparatif des trois algorithmes : Frank Nielsen Algorithme Cannon Fox Snyder prétraitement preskewing de A et B rien transposition B ← B ⊤ produits matriciels en place en place P mouvements A gauche → droite diffusion horizontale rien mouvements B bas → haut bas → haut bas → haut 3.Produit matriciel-3.Algorithme de Snyder sur les lignes PEs A6-43 La bibliothèque C++ STL : généricité Les classes génériques en C++ But de la généricité = produire du code indépendant des types (instanciés lors de l’usage): // returns 0 if equal, 1 if value1 is bigger, -1 otherwise i n t compare ( c o n s t i n t &v a l u e 1 , c o n s t i n t &v a l u e 2 ) { i f ( v a l u e 1 < v a l u e 2 ) r e t u r n −1; i f ( value2 < value1 ) return 1; return 0; } // returns 0 if equal, 1 if value1 is bigger, -1 otherwise i n t compare ( c o n s t s t r i n g &v a l u e 1 , c o n s t s t r i n g & value2 ) { i f ( v a l u e 1 < v a l u e 2 ) r e t u r n −1; i f ( value2 < value1 ) return 1; return 0;} ⇒ factorisation du code puis à la compilation, code polymorphique pour les divers types requis : génération des codes spécifiques pour les types demandés. #i n c l u d e <i o s t r e a m > #i n c l u d e <s t r i n g > // returns 0 if equal, 1 if value1 is bigger, -1 otherwise t e m p l a t e < c l a s s T> i n t compare ( c o n s t T &v a l u e 1 , c o n s t T &v a l u e 2 ) { i f ( v a l u e 1 < v a l u e 2 ) r e t u r n −1; i f ( value2 < value1 ) return 1; return 0; } // On est gentil ici pour le compilateur : // on indique explicitement les types demandés i n t main ( i n t a r g c , c h a r ∗∗ a r g v ) { s t d : : s t r i n g h ( ” h e l l o ” ) , w( ” w o r l d ” ) ; s t d : : c o u t << compare<s t d : : s t r i n g >(h , w) << s t d : : endl ; s t d : : c o u t << compare<i n t >(10 , 2 0 ) << s t d : : e n d l ; s t d : : c o u t << compare<d o u b l e > ( 5 0 . 5 , 5 0 . 6 ) << s t d : : endl ; return 0;} Inférence des types demandés par le compilateur #i n c l u d e <i o s t r e a m > #i n c l u d e <s t r i n g > // returns 0 if equal, 1 if value1 is bigger, -1 otherwise t e m p l a t e < c l a s s T> i n t compare ( c o n s t T &v a l u e 1 , c o n s t T &v a l u e 2 ) { i f ( v a l u e 1 < v a l u e 2 ) r e t u r n −1; i f ( value2 < value1 ) return 1; return 0; } // Le compilateur doit trouver le type demande ici : // inférence de types i n t main ( i n t a r g c , c h a r ∗∗ a r g v ) { s t d : : s t r i n g h ( ” h e l l o ” ) , w( ” w o r l d ” ) ; s t d : : c o u t << compare ( h , w) << s t d : : e n d l ; s t d : : c o u t << compare ( 1 0 , 2 0 ) << s t d : : e n d l ; s t d : : c o u t << compare ( 5 0 . 5 , 5 0 . 6 ) << s t d : : e n d l ; return 0;} Mécanisme de compilation ◮ le compilateur ne génére pas de code directement lorsqu’il rencontre une classe/fonction template parce qu’il ne connaı̂t pas encore quelles seront les types demandés. ◮ quand le compilateur rencontre une fonction template utilisée, il sait quel type est demandé : Il instancie alors le template et compile le code correspondant ⇒ les classes/fonctions templates doivent donc se trouver dans le fichier d’en-tête, header .h Le mécanisme de template ressemble donc a une macro expansion... fichier compare.h : #i f n d e f COMPARE H #d e f i n e COMPARE H t e m p l a t e < c l a s s T> i n t comp ( c o n s t T& a , c o n s t T& b ) { i f ( a < b ) r e t u r n −1; i f (b < a) return 1; return 0;} #e n d i f // COMPARE H fichier main.cpp : #i n c l u d e <i o s t r e a m > #i n c l u d e ” compare . h” u s i n g namespace s t d ; i n t main ( i n t a r g c , c h a r ∗∗ a r g v ) { c o u t << comp<i n t >(10 , 2 0 ) ; c o u t << e n d l ; return 0; } Lire un fichier dans un vector de la STL Vous avez déjà utilisé la classe vector de la STL ! (tableaux dynamiques) ifstream fin ; f i n . open ( ” f i c h i e r . t x t ” ) ; v e c t o r <s t r i n g > t e x t e ; s t r i n g mote ; w h i l e ( f i n >> mot ) { t e x t e . p u s h b a c k ( mot ) ; } fin . close ( ) ; ◮ La boucle while lit jusqu’à temps de rencontrer EOF (End Of File) ◮ Les données sont des chaı̂nes de caractères séparées par des délimiteurs (espace, tab, retour à la ligne, point virgule pour les fichiers CSV, Comma-Separated Values) STL : une collection de structures de données Le concept fondamental est le containeur avec son iterator , le tout en template ! Structure de données tableau dynamique liste chaı̂née pile file arbre binaire table de hachage tas ordonné nom STL vector list stack queue set map file de priorité Les #include sont à faire sans le .h #include <vector> <list> <stack> <queue> <set> <set> <queue> La STL : structures de données génériques s e t <s t r i n g > mots ; l i s t <E l e v e > PromoX2013 ; s t a c k < v e c t o r <i n t > > nombres ; À chaque container STL, on a un itérateur (iterator) associé de type container<T>::iterator s e t <s t r i n g > : : i t e r a t o r p=mots . f i n d ( ” c o u r s ” ) ; l i s t <E l e v e > : : i t e r a t o r p r e m i e r=PromoX2013 . b e g i n () ; s t a c k < v e c t o r <i n t > >:: i t e r a t o r f i n=nombres . end () ; On déreférence un itérateur comme pour un pointeur : *it Les containeurs stockent par valeur, pas par reférence ◮ quand on insére un objet, le containeur va en faire une copie ◮ quand le containeur doit réarranger les objets, il procéde en faisant des copies de ceux-ci. Par exemple, si on tri, ou si on insére sur un containeur map, etc. ◮ si on veut éviter cela, il faudra donc faire des containeurs de pointeurs ! C++11 a le mot clef auto pour inférer directemement les types et un “foreach” (pour les curieux !) : f o r ( v e c t o r <P r i n t e r > : : i t e r a t o r i t = v e c . b e g i n ( ) ; i t < v e c . end ( ) ; i t ++) { c o u t << ∗ i t << e n d l ; } f o r ( a u t o i t = v e c . b e g i n ( ) ; i t < v e c . end ( ) ; i t ++) { c o u t << ∗ i t << e n d l ; } s t d : : s t r i n g s t r ( ” B o n j o u r INF442 ” ) ; f o r ( a u t o c : s t r ) { s t d : : c o u t << c << e n d l ; } Fonctions membres communes à la STL Toutes les classes containeurs ont les fonctions membres : int size () i t e r a t o r begin () i t e r a t o r end ( ) b o o l empty ( ) Pour lister tous les éléments d’un containeur, on fait : l i s t <s t r i n g > : : i t e r a t o r i t =m a L i s t e . b e g i n ( ) ; w h i l e ( i t != m a L i s t e . end ( ) ) { c o u t << ∗ i t <<e n d l ; i t e r ++;} Notons que end() est un élément sentinel . On ne peut pas déreférencer end(). Différents accès aux éléments d’un containeur ◮ pour vector, on peut accéder aux éléments en utilisant un index [i ] : v e c t o r <i n t > vec442<d o u b l e >; vec442 [0]=280; ◮ ... mais les crochets ne peuvent pas être utilisés pour list<int> par exemple on peut rajouter un élément à la fin d’une liste ou d’un vecteur avec push back : monVecteur . p u s h b a c k ( 2 0 1 3 ) ; maListe . push back (2013) ; ... mais il n’ y a pas de push_back pour les ensembles (codés par des arbres binaires) : s e t <i n t > monEnsemble ; monEnsemble . p u s h b a c k ( 2 0 1 3 ) ; // Erreur !!! La liste (doublement chaı̂née) On peut ajouter à la tête ou à la queue d’une liste en temps constant : maListe . push back (2013) ; maListe . p u s h f r on t (2015) ; On peut insérer ou supprimer un élément avec un itérateur : l i s t <s t r i n g > : : i t e r a t o r p=m a L i s t e . b e g i n ( ) ; p=m a L i s t e . e r a s e ( p ) ; p=m a L i s t e . i n s e r t ( p , ”HPC” ) ; On peut avancer ou reculer dans une liste avec les opérateurs unaires ++ et -- : p++; p−−; // faire attention aux débordements possibles Seul bémol : on ne peut pas directement accéder i -ième élément (cela demande de parcourir la liste, pas de crochets). La liste doublement chaı̂née en STL Voir INF311/INF411 NULL C++ HPC MPI NULL q=it-- q=it++ list<string>::iterator it=liste.find("HPC") Les piles et les files ◮ Piles ( stacks ) et files ( queues ) sont des sous-classes de la classe deque ◮ Une pile est une liste chaı̂née avec la propriété Dernier Arrivé Premier Sorti, DAPS (LIFO : Last In First Out). ◮ Une file est une liste chaı̂née avec la propriété Premier Arrivé Premier Sorti, PAPS (FIFO : First In First Out). ◮ On accéde au dernier élèement au sommet de la pile ou au premier élément d’une file avec les primitives push et pop ◮ Pour les piles, on a aussi top, et pour les files front et back Les piles : illustration s t a c k <s t r i n g > S ; S . push ( ”A” ) ; S . push ( ”B” ) ; S . push ( ”C” ) ; S . pop ( ) ; Q. pop ( ) ; S . push ( ”D” ) ; Q. push ( ”D” ) ; c o u t << S . t o p ( ) ; Les files : illustration queue<s t r i n g > Q; Q. push ( ”A” ) ; Q. push ( ”B” ) ; Q. push ( ”C” ) ; Q. pop ( ) ; Q. push ( ”D” ) ; c o u t << Q. f r o n t ( ) << Q. ba ck ( ) ; Les files de priorité On doit définir un operator < . La plus grande valeur est sur le haut (max-heap, top). p r i o r i t y q u e u e <i n t > Q; Q. push ( 2 3 ) ; Q. push ( 1 2 ) ; Q. push ( 7 1 ) ; Q. push ( 2 ) ; c o u t << Q. t o p ( ) ; Q. pop ( ) ; c o u t << Q. t o p ( ) ; pour la plus petite valeur (min-heap), il faut donc changer le sens sémantique de l’opérateur < ... http://en.cppreference.com/w/cpp/language/operator_comparison On peut trier facilement avec une file de priorité... #i n c l u d e <queue> #i n c l u d e <i o s t r e a m > u s i n g namespace s t d ; s t r u c t comparator { bool operator () ( int i , int j ){ return i < j ;} }; i n t main ( i n t a r g c , c h a r c o n s t ∗ a r g v [ ] ) { p r i o r i t y q u e u e <i n t , s t d : : v e c t o r <i n t >, c o m p a r a t o r> minHeap ; minHeap . push ( 1 0 ) ; minHeap . push ( 5 ) ; minHeap . push ( 1 2 ) ; minHeap . push ( 3 ) ; minHeap . push ( 3 ) ; minHeap . push ( 4 ) ; w h i l e ( ! minHeap . empty ( ) ) { c o u t << minHeap . t o p ( ) << ” ” ; minHeap . pop ( ) ; } r e t u r n 0 ; } // 12 10 5 4 3 3 Les ensembles : set (arbres binaires équilibrés) On doit définir operator <. Toutes les valeurs sont uniques (sinon, utiliser un multiset). insert(value), erase(value), erase(iterator), iterator find(value) s e t <s t r i n g > s ; s . i n s e r t (” Ecole ”) ; s . i n s e r t (” Polytechnique”) ; s . erase (” Ecole ”) ; c o u t << ∗( s . f i n d ( ” P o l y t e c h n i q u e ” ) ) ; Le hachage (map) ◮ Différence entre hachage fermé (tableau) et hachage ouvert (tableau de pointeurs sur des listes). ◮ Templates pour la clef et le type de données map<K,T>. ◮ On doit définiroperator < pour le type K. map<i n t , s t r i n g > monHachage ; monHachage [ 2 3 1 2 1 9 8 1 ] = ” A n n i v e r s a i r e Toto ” ; monHachage [ 0 5 0 3 1 9 5 3 ] = ” A n n i v e r s a i r e T i t i ” ; ... map<s t r i n g , i n t > monHachageRev ; monHachageRev [ ” Toto ” ] = 2 3 1 2 1 9 8 1 ; monHachageRev [ ” T i t i ” ] = 0 5 0 3 1 9 5 3 ; Le hachage (map) Les fonctions membres pour la classe STL map : erase(iterator), erase(K clef), map_name(K key) map<s t r i n g , i n t > M; M[ ”A” ] = 2 3 ; M[ ”B” ] = 1 2 ; M[ ”C” ] = 7 1 ; M[ ”D” ] = 5 ; M. e r a s e ( ”D” ) ; c o u t << M[ ”B” ] ; La classe STL paire à la rescousse map<s t r i n g , i n t > maMap ; p a i r <s t r i n g , i n t > p a i r e ( ” Tutu ” , 606) ; maMap . i n s e r t ( p a i r e ) ; ... // on créé un nouvel enregistrement en faisant aussi : maMap [ ” Tata ” ] = 7 0 7 ; ⇒ opérateur crochet [K] Les temps d’accés aux structures de données Pour un containeur à n éléments : Insérer/supprimer Rechercher vecteur list set map O(n) O(n) O(1) O(n) O(log n) O(log n) Õ(1) Õ(1) Voir INF311/INF411. Les itérateurs Chaque containeur est equippé d’un itérateur : c o n t a i n e r <T> : : i t e r a t o r i t ; i t =C . b e g i n ( ) ; ◮ ++ et -- pour avancer ou reculer ◮ * pour déreférencer ◮ == et =! pour les tests de comparaisons Seulement dans la classe vector, on peut bouger de p éléments (arithmétique) en faisant v e c t o r <T> : : i t e r a t o r i t ; i t = i t +p ; i t =i t −p ; Les itérateurs : premier et dernier éléments Le dernier élément est une sentinelle : c o u t << ∗( L . b e g i n ( ) ) ; // oui, si pas vide ! c o u t << ∗( L . end ( ) ) ; // toujours non ! l i s t <s t r i n g > : : i t e r a t o r p = L . end ( ) ; p−−; c o u t << ∗p ; // ok, si pas vide ! La classe STL algorithm Procédures (pas des méthodes de classe) : find, remove, count, shuffle, replace, sort, for each, min element, binary search, transform, copy, swap : i t e r = f i n d ( L . b e g i n ( ) , L . end ( ) , ” Cours INF442 ” ); i n t x = c o u n t ( L . b e g i n ( ) , L . end ( ) , ” i n s c r i t en INF442 ” ) ; r e p l a c e ( L . b e g i n ( ) , L . end ( ) , ”DEP442” , ” INF442 ” ); if : prend une fonction booléene utilisateur : r e p l a c e i f ( L . b e g i n , L . end ( ) , a p p a r t i e n t 4 4 2 S , ” Tutorat ”) ; La bibliothèque Boost Boost ◮ un ensemble de bibliothèques qui se comportent bien avec la STL : http://www.boost.org/ ◮ liste des bibliothèques de Boost : http://www.boost.org/doc/libs/ Graph BGL MPI Rational Thread uBlas Xpressive generic graph components MPI interface in Boost style rational number class Portable multi-threading linear algebra for vector/matrix regular expression Installé dans le répertoire /usr/local/boost-1.56.0 Boost : la bibliothèque uBLAS #i n c l u d e <b o o s t / n u m e r i c / u b l a s / m a t r i x . hpp> #i n c l u d e <b o o s t / n u m e r i c / u b l a s / i o . hpp> u s i n g namespace s t d ; u s i n g namespace b o o s t : : n u m e r i c : : u b l a s ; i n t main ( ) { m a t r i x <d o u b l e > m ( 3 , 3) ; f o r ( u n s i g n e d i = 0 ; i < m. s i z e 1 ( ) ; ++ i ) f o r ( u n s i g n e d j = 0 ; j < m. s i z e 2 ( ) ; ++ j ) m (i , j ) = i + j∗j ; c o u t << m << e n d l ; } Boost : la bibliothèque uBLAS alias mpiboost = ’/ usr / local / openmpi -1.8.3/ bin / mpic ++ -I / usr / local / boost -1.56.0/ include / -L / usr / local / boost -1.56.0/ lib / l b o o s t _ m p i - l b o o s t _ s e r i ali za ti on ’ mpiboost m a t r i c e 4 4 2. cpp -o m a t r i c e 4 4 2. exe mpirun - np 1 m a t r i c e 4 4 2. exe [3 ,3]((0 ,1 ,4) ,(1 ,2 ,5) ,(2 ,3 ,6) ) http://www.boost.org/doc/libs/1_58_0/libs/numeric/ublas/doc/ # i n c l u d e <b o o s t / n u m e r i c / u b l a s / m a t r i x . hpp> # i n c l u d e <b o o s t / n u m e r i c / u b l a s / i o . hpp> # i n c l u d e <b o o s t / n u m e r i c / u b l a s / m a t r i x . hpp> u s i n g namespace b o o s t : : n u m e r i c : : u b l a s ; u s i n g namespace s t d ; i n t main ( ) { m a t r i x <d o u b l e > myMat ( 3 , 3 , 2 . 5 ) ; myMat ( 0 , 0 )= myMat ( 2 , 2 ) = 1 . 0 ; myMat ( 0 , 2 )= − 3 .6 ; myMat ( 2 , 0 ) = 5 . 9 ; c o u t << ”My Mat : ” << myMat << e n d l ; c o u t << ”Num Rows : ” << myMat . s i z e 1 ( ) << e n d l ; c o u t << ”Num C o l s : ” << myMat . s i z e 2 ( ) << e n d l ; c o u t << ”My Mat T ransp : ” << t r a n s ( myMat ) << e n d l ; c o u t << ”My Mat R e a l P a r t : ” << r e a l (myMat ) << endl ; myMat . r e s i z e ( 4 , 4 ) ; c o u t << ”My R e s i z e d Mat : ” << myMat << e n d l ; return 0;} m a t r i x <d o u b l e > myMat (3 , 3 , 2 . 5 ) ; myMat (0 , 0 )= myMat (2 , 2 ) = 1 . 0 ; myMat (0 , 2 )= − 3.6; myMat (2 , 0 ) = 5 . 9 ; mpirun - np 1 m a t r i c e f u n 4 4 2. exe My Mat :[3 ,3]((1 ,2.5 , -3.6) ,(2.5 ,2.5 ,2.5) ,(5.9 ,2.5 ,1) ) Num Rows :3 Num Cols :3 My Mat Transp :[3 ,3]((1 ,2.5 ,5.9) ,(2.5 ,2.5 ,2.5) ,( -3.6 ,2.5 ,1)) My Mat Real Part :[3 ,3]((1 ,2.5 , -3.6) ,(2.5 ,2.5 ,2.5) ,(5.9 ,2.5 ,1) ) My Resized Mat :[4 ,4]((1 ,2.5 , -3.6 ,3.57355e -115) ,(2.5 ,2.5 ,2.5 ,2.02567e -322) ,(5.9 ,2.5 ,1 ,0) ,(0 ,0 ,0 ,0) ) Résumé A4 X la classification par régression linéaire (et comparaison avec le classifieur k-PPV) X le produit matrice-vecteur sur l’anneau orienté X produits matriciels sur le tore : algorithmes de Cannon (pre-processing), de Fox (broadcast-multiply-roll) et de Snyder (sommes globales) X la généricité avec la bibliothèque C++ STL X la bibliothèque Boost uBLAS Pour la prochaine fois : lire le chapitre 5 du polycopié