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é

Documents pareils