• Aucun résultat trouvé

Algorithmes parallèles avec partitionnement bidimensionnel de la matrice 114

Les trois algorithmes utilisés ici sont des variantes des algorithmes classiques, dans lesquels les calculs sont restructurés de manière à exprimer des opérations sur des sous-matrices blocs plutôt que sur des scalaires.

L’intérêt de réorganiser les calculs des algorithmes d’algèbre linéaires denses mais également creux (voir le chapitre 8), de manière à utiliser les BLAS de niveau 3 [33], n’est plus à démontrer. Cette technique est largement utilisée en séquentiel comme par exemple dans la librairie LAPACK [4] mais également en parallèle [49]. Elle permet d’exploiter au mieux les mémoires à plusieurs niveaux ou mémoires caches des machines actuelles. Sur ces machines, seul le niveau de cache le plus élevé est capable de fournir les données aux unités de calculs à la fréquence du processeur. Sur une opération BLAS de niveau 3 utilisantn

2

mots mémoire, il y aO (n 3

)calculs à réaliser. En choisissant la taille des blocs de manière à ce que lesn

2

éléments logent dans le cache mémoire de niveau le plus élevé, les O (n

3

)calculs peuvent être réalisés en exploitant pleinement la puissance du processeur.

Cette réorganisation des calculs pour se ramener à des opérations sur des sous-matrices est également utile dans les versions distribuées de ces algorithmes. Une certaine distribu-tion des données par bloc, appelée placement par bloc cyclique bidimensionnel [79], tend à s’imposer, de par ses propriétés d’extensibilité, d’équilibrage de charge et de minimisa-tion des communicaminimisa-tions, comme méthode générale de distribuminimisa-tion des données pour les algorithmes distribués d’algèbre linéaire dense. Ce type de placement est par exemple uti-lisé dans les bibliothèques parallèles d’algèbre linéaire ScaLapack [22] et PLAPACK [1]. Pour réaliser ce placement cyclique bidimensionnel, la ou les matrices de taillesnn

à traiter sont partitionnées dans les deux dimensions enNN blocs de dimensionskk. Pour des raisons de simplicité, on supposera dans la suite quekest un diviseur den.

En réorganisant les boucles des algorithmes on peut alors réécrire ceux-ci, non plus en terme d’opérations sur des scalaires mais en opérations sur les blocs de la matrice partitionnée. Les algorithmes obtenus pour le produit de matrices et les factorisationsLU

et LL t

sont décrites dans les figures 6, 5 et 6. Le parallélisme de ces trois algorithmes, exprimé au niveau des opérations sur les blocs, est aisément identifiable.

Pour réaliser le placement par blocs cycliques bidimensionnels les blocs des matrices sont distribués de la manière suivante. En supposant que q

2

=pprocesseurs sont dispo-nibles, le blocs d’indice(i;j)est placé sur le processeur d’indice(imodq)q+(j modq). La figure 7.1 montre un exemple de placement cyclique bidimmensionel des blocs sur 4 processeurs. Les opérations sur les blocs de matrice, sont alors réalisées sur le site où est localisé le bloc modifié par l’opération. On montre alors que le volume total de communication de ces trois algorithmes parallèles avec ce placement des données est en

O (n 2

p

Algorithmes parallèles avec partitionnement bidimensionnel de la matrice 7.2

Algorithme 4 : Produit de matrices, avec partitionnement bidimensionnel.

Entrée : deux matrices AetB de taillenn , partitionnées dans les deux dimensions en N N blocs ;A

i;j référence un bloc de la matriceA, B

i;j référence un bloc de la matriceB.

Sortie : C =ABouC

i;j référence un bloc de la matriceC.

pourk =1jusqu’àN faire [en parallèle] pouri=1jusqu’àN faire [en parallèle]

pourj =1jusqu’àN faire [en parallèle]

C i;j =C i;j +A i;k B k ;j

Algorithme 5 : Factorisation LU d’une matrice dense par élimination de Gauss, avec partitionnement bidimensionnel de la matrice.

Entrée : matriceAde taillennpartitionnée dans les deux dimensions enNNblocs ;

A

i;j référence un bloc de la matrice.

Sortie : LetU tel queLU =A(LetU sont stockés en place dansA).

pourk =1jusqu’àN faire

FactorisationLU en place du blocA k ;k

pouri=k+1jusqu’àN faire

A i;k =A i;k A 1 k ;k

pourj =k+1jusqu’àN faire [en parallèle]

A k ;j =A k ;j A 1 k ;k

pourj =k+1jusqu’àN faire [en parallèle] pouri=k+1jusqu’àN faire [en parallèle]

A i;j =A i;j A i;k A k ;j

Algorithme 6 : Factorisation de Cholesky LL t

d’une matrice dense symétrique définie positive, avec partitionnement bidimensionnel de la matrice.

Entrée : matriceAde taillennsymétrique, partitionnée dans les deux dimensions en

N N blocs ; A

i;j référence un bloc de la matrice ; les blocs situés au dessus de la diagonale de sont pas stockés (A est symétrique).

Sortie : LL t

tel queLL t

=A(stocké en place dansA).

pourk =1jusqu’àN faire

FactorisationLL t

en place du blocA k ;k

pouri=k+1jusqu’àN faire [en parallèle]

A i;k =A i;k A 1 k ;k

pourj =k+1jusqu’àN faire [en parallèle] pouri=j jusqu’àN faire [en parallèle]

A i;j =A i;j A i;k A t j;k

2 3 4 4 2 2 1 1 2 1 2 1 3 4 3 4 4 1 2 1 1 3 4 3 4 3 4 2 1 2 1 2 3 3 3 4

Figure 7.1 – Placement cyclique bidimmensionel sur4processeurs des blocs d’une matrice

partitionnée en66blocs.

7.3 Écriture en Athapascan-1

Nous présentons dans cette section l’écriture en Athapascan-1 des trois algorithmes précédemment présentés. Seul le code du programme Athapascan-1 réalisant l’algorithme 5 page 115 de factorisationLU est fourni dans la figure 7.2. Les trois autres programmes sont très similaires.

L’écriture de ces algorithmes se compose de trois étapes. La première consiste à choi-sir la granularité des tâches et des objets partagés. Ici, cette granularité est naturellement définie au niveau des blocs de la matrice partitionnée. Les blocs de la matrice seront alors des objets partagés et les tâches seront associées aux opérations sur ces blocs. Notons que la taille des blocs peut être choisie à l’exécution.

Une fois que la granularité des tâches et des objets partagés est choisie, la seconde étape consiste à définir les types d’objets partagés et les types de tâches. Ici un seul type d’objet partagé est utilisé dans les programmes. Ce type noté bloc et représentant un bloc de matrice, contient un tableau des éléments d’un bloc de la matrice partitionnée.

Pour chacun des algorithmes, un type de tâche est ensuite défini pour chaque type d’opération réalisée sur les blocs. Par exemple, pour la factorisationLU cinq opérations sont utilisées donc cinq types de tâches sont définis dans le programme. Les 25 premières lignes du programme de la figure 7.1 définissent ces cinq types de tâches.

Chaque type de tâche prend en paramètre des références contraintes sur des objets par-tagés de type bloc. Les contraintes d’accès sur ces références sont ajoutées en fonction du type d’accès réalisé dans la tâche sur les objets référencés.

On peut noter l’utilisation d’une référence avec un droit d’accès en accumulation

Shared cw<add,bloc> pour le paramètrec du type de tâchegemm à la ligne 23 du programme. Ceci indique qu’une accumulation avec la fonction d’accumulationadd

(réalisant la somme de deux blocs) est réalisée sur l’objet référencé par le paramètrec. Cette accumulation est effectuée à la ligne 24 par l’appel de la méthodecumul sur la référence contraintec.

Écriture en Athapascan-1 7.3

1 : struct Factorization LU { // Factorisation LU séquentiel du bloc a

2 : void operator()( Shared r w<bloc> a ) {

3 : a.access().FactorisationLU();

4 : }};

5 : struct trsm sup { // b bsup(a)

1

6 : voit operator()( Shared r<bloc> a,

7 : Shared r w<bloc> b ) {

8 : b.access().dtrsm sup( a.read() );

9 : }};

10 : struct trsm inf { // b binf(a)

1

11 : void operator()( Shared r<bloc> a,

12 : Shared r w<bloc> b ) {

13 : b.access().dtrsm inf( a.read() );

14 : }};

15 : struct add { // a a+b

16 : void operator()(bloc& a, const bloc& b) {

17 : a += b;

18 : }};

19 : struct gemm { // ab

20 : void operator()( Shared r<bloc> a,

21 : Shared r<bloc> b,

22 : Shared cw<add,bloc> c) {

23 : c.cumul( a.read() * b.read() );

24 : }};

25 : Factorization LU Par(matrix bloc<Shared rp wp<bloc> >& A, int n ) {

26 : int i, j, k;

27 : for( k = 0; k < n ; k++ ) {

28 : Fork<Factorization LU>()( A(k,k) ) ;

29 : for( i = k+1; i < n ; i++ )

30 : Fork<trsm sup>()( A(k,k), A(i,k) ) ;

31 : for( j = k+1; j < n ; j++ )

32 : Fork<trsm inf>()( A(k,k), A(k,j) ) ;

33 : for(i = k+1; i < n ; i++ )

34 : for( j = k+1; j < n ; j++ )

35 : Fork<gemm>()( A(i,k), A(k,j), A(i,j) ) ;

36 : }

37 : }

Pour réaliser cette accumulation, il est nécessaire d’effectuer au préalable le produit

a * b sur une matrice temporaire. On pourrait penser qu’il est plus efficace

d’effec-tuer en place sur le bloc cle calcul a * b + c, comme le permet la fonction xgemm

de la librairie BLAS. Cependant, dans ce cas, lors de l’exécution sur une machine à mé-moire partagée, les tâches d’accumulation sur une même matrice ne pourraient pas être exécutées en parallèle.

La troisième étape consiste à écrire l’algorithme proprement dit en remplaçant les opérations sur les blocs par la création des tâches correspondantes. Pour la factorisation

LU, l’écriture de cet algorithme est fournie à partir de la ligne 26 du programme de la figure 7.1.

En conclusion, nous pouvons noter que, de par la sémantique séquentielle du modèle de programmation d’Athapascan-1, l’écriture de l’algorithme en Athapascan-1 est iden-tique à l’écriture séquentielle de l’algorithme 5 page 115. De plus, cette écriture particu-lièrement naturelle permet de décrire très précisément le parallélisme de ce programme.