• Aucun résultat trouvé

2.4 Le parallélisme implicite

2.4.4 Solutions à patrons

Les bibliothèques de parallélisme implicite générales, comme nous venons de le voir, cherchent à cacher le parallélisme par le biais de conteneurs, d’algorithmes et d’itérateurs (tableaux 1D, 2D, graphes etc.). Nous allons maintenant aborder des solutions proposant un niveau d’absrtaction que nous considérons plus haut puisque davantage de détails sont cachés à l’utilisateur. Ces solutions sont, elles aussi, basées sur des structures de données implicitement distribuées, mais proposent, de plus, d’identifier les opérations effectuées dans un programme comme un ensemble de patrons de programmation (ou patterns). Ces patterns seront ensuite responsables de la parallélisation implicite des opérations séquentielles du code de l’utilisateur. Les patrons proposent un haut niveau d’abstraction. Par exemple, ce type de solutions cache généralement la navigation dans

les structures de données, la notion d’itérateur n’est alors plus nécessaire. Nous allons décrire ici deux grandes familles de solutions à patrons. Tout d’abord, le domaine des squelettes algorithmiques sera décrit et quelques unes des nombreuses bibliothèques de ce domaine seront étudiées. Puis, quelques autres solutions à patrons seront décrites. 2.4.4.1 Squelettes algorithmiques.

Les squelettes algorithmiques parallèles ont été introduits en 1988 par Muray Cole [40]. Ils représentent des patrons de parallélisation fonctionnels, en d’autres termes des abstractions de schémas de parallélisme, que l’on retrouve de façon récurrente dans les applications parallèles. Ainsi, en théorie, n’importe quel programme parallèle peut s’exprimer comme une suite ou une imbrication de squelettes algorithmiques fonction-nels. Aucune norme n’a été définie pour écrire des squelettes, ni même aucun consensus. Toutefois, le travail de Cole [39] indique quelques règles de conception pour produire des squelettes adaptés et donc plus utilisés. Un squelette a idéalement un champ d’ap-plication le plus large possible, afin de pouvoir être utilisé dans un grand nombre de cas, sa sémantique doit être compréhensible des utilisateurs, et enfin il ne doit pas être redondant avec d’autres squelettes.

Les squelettes algorithmiques se découpent en trois grandes classes, les squelettes pour le parallélisme de données (map, reduce, zip etc.), les squelettes pour le parallélisme de tâches (farm, pipeline etc.), et enfin les squelettes dits de résolution (divide and conquer, branch and bound). Des détails sur l’ensemble de ces squelettes peuvent être trouvés dans la thèse de Legaux [84]. Nous n’allons ici décrire que quelques squelettes pour le parallélisme de données, auxquels nous feront référence dans cette thèse. Nous pouvons, tout d’abord, noter les trois squelettes de base les plus connus et les plus simples à comprendre. Le premier s’appelle map et permet d’appliquer une fonction locale à un ensemble de données d’entrée en parallèle. Une fonction locale est alors une fonction dont le calcul ne dépend que d’un élément d’entrée sans aucune dépendance avec les autres éléments. Le squelette peut alors distribuer la structure de données et appliquer la fonction à chacun des éléments séparément. Le squelette prend alors un vecteur de données d’entrée [x1, x2, . . . , xn], retourne un vecteur de données de sortie [y1, y2, . . . , yn] et applique une fonction f telle que

map f [x1, x2, . . . , xn] = [f (x1), f (x2), . . . , f (xn)] = [y1, y2, . . . , yn].

Le second squelette de base est le squelette zip qui est une extension de map pour deux vecteurs d’entrée. Il distribue deux vecteurs de données d’entrée [x1, x2, . . . , xn] et [x01, x02, . . . , x0n], de même taille, et retourne un nouvel vecteur de sortie [y1, y2, . . . , yn] en appliquant une fonction f telle que

zip f ([x1, x2, . . . , xn], [x01, x02, . . . , x0n]) = [f (x1, x01), f (x2, x02), . . . , f (xn, x0n)] = [y1, y2, . . . , yn].

Enfin, le squelette reduce permet de réduire un vecteur de données d’entrée [x1, x2, . . . , xn] en un unique élément e suite à l’appel d’une opération de réduction, que nous noterons

2.4. Le parallélisme implicite 51

⊕, telle que

reduce ⊕ [x1, x2, . . . , xn] = x1⊕ x2⊕ . . . ⊕ xn= e.

Les squelettes map et zip sont des squelettes qui ne peuvent appliquer que des calculs locaux, de par leur construction. En d’autres termes, il n’est pas possible avec uniquement ces squelettes d’effectuer des calculs de type stencil. La fonction décrite par l’utilisateur ne décrit, en effet, que l’opération à effectuer sur un élément de l’ensemble de départ. Un calcul stencil dépendant d’un certain voisinage de l’élément courant, il est nécessaire de faire appel au squelette shift. Ce squelette va prendre un vecteur d’entrée [x1, x2, . . . , xn], et retourner un vecteur de sortie [y1, y2, . . . , yn] égal à l’ensemble d’entrée décalé (le décalage appliqué étant précisé par l’utilisateur). Par exemple, pour un décalage de un élément vers la droite nous obtiendront

[y1, y2, . . . , yn] = [×, x1, x2, . . . , xn−1].

De cette manière en accédant au deuxième élément des ensembles [x1, x2, . . . , xn] et [×, x1, x2, . . . , xn−1], il est possible de faire des opérations sur x2 et x1 en même temps.

Avec ces quatre squelettes de base, on peut très facilement observer les limites de l’ap-proche par squelettes pour des simulations scientifiques complexes. Pour cette raison, des squelettes de type stencil sont apparus, notamment dans la bibliothèque SkelCL [21,111]. Cette bibliothèque implémente des squelettes de base pour les GPU et multi-GPU en uti-lisant le langage OpenCL [112]. Il s’agit donc également d’un code portable. Elle propose un squelette de stencil simple nommé MapOverlap qui permet de décrire une opération de stencil simple, et un squelette de stencil plus complexe, nommé Stencil permettant notamment de décrire des opérations stencil itératives. Afin de pouvoir effectuer les cal-culs de type stencil, une distribution contenant des éléments fantômes est mise en place dans la bibliothèque de squelettes, et n’existe pas dans les autres solutions. De plus, les échanges à effectuer entre les processeurs sont automatiquement détectés par les argu-ments utilisés dans le stencil. Notons que SkelCL ne fonctionne que pour les structures de données de type vecteur ou matrices.

Parmi les bibliothèques de squelettes permettant de faire du parallélisme de données et écrites en C++, on peut noter OSL [72], SkeTo [76], SkePu [52], et Muesli [37], chacune ayant ses propres particularités. SkeTo, par exemple, est la seule bibliothèque proposant une solution de squelettes sur les arbres [90]. SkePu propose une implémentation GPU, et Muesli une implémentation hybride MPI/OpenMP des squelettes algorithmiques de base. Enfin, OSL propose des optimisations à base de méta-programmation C++ [71,84]. Bien que les squelettes algorithmiques parallèles proposent un niveau d’abstraction intéressant, ce domaine est très peu utilisé pour des simulations scientifiques complexes. A notre connaissance, aucune simulation complexe n’a été écrite avec des squelettes algo-rithmiques, et leur utilisation se limite à des cas “jouet” comme la résolution de l’équation de la chaleur. Avec l’arrivée de nouveaux squelettes spécifiquement écrits pour le calcul stencil [21], l’utilisation des squelettes algorithmiques est enclin à se développer dans cette discipline. Toutefois, dans le domaine des mathématiques appliquées, les langages de programmation enseignés aux scientifiques sont très souvent des langages impératifs comme Fortran, C et C++. L’utilisation de langages fonctionnels, et donc de squelettes

algorithmiques parallèles, demande un effort d’apprentissage supplémentaire qui pourrait éloigner certains numériciens. Toutefois, notons qu’un langage fonctionnel peut être ap-pris très rapidement et très facilement par les mathématiciens car il s’agit d’un langage plus proche des mathématiques.

2.4.4.2 D’autres solutions à patrons.

L’entreprise Google est à l’origine de la démocratisation de l’utilisation du modèle MapReduce [47]. Dans ce modèle il est considéré que tout calcul peut être décomposé en une série d’association du squelette map et du squelette reduce. Cette solution a permis de démocratiser les squelettes algorithmiques, grâce à l’association intelligente de deux concepts simples, dont la mise en œuvre est facilitée par un certain nombre d’outils. Ainsi, par exemple, le framework Hadoop [127], très connnu et très utilisé, propose un système de fichiers distribués et une implémentation de MapReduce. Il est alors possible de faciliter la création d’applications distribuées ainsi que leur déploiement sur des milliers de processeurs. Enfin, ce type de systèmes embarque généralement la gestion des pannes du programme ou du matériel, ce qui augmente encore l’intérêt des scientifiques, dont les simulations peuvent être très longues et coûteuses.

Google est également à l’origine d’un modèle de parallélisme simple pour les traite-ments parallèles sur les graphes dirigés, Pregel [88]. Dans l’état de l’art de ce travail, Pregel est comparé à PBGL et CGMGraph (décrits précédemment), et le principal argument avancé pour préférer son utilisation est la tolérance aux pannes. Toutefois, le type d’approche est très différent. En effet, Pregel conserve une idée proche des squelettes algorithmiques et se base également sur le modèle BSP pour structurer de façon générale des opérations sur des graphes dirigés. Dans Pregel, le graphe est tout d’abord distribué sur les différents processeurs. Un calcul dans Pregel est ensuite composé de plusieurs itérations, que l’on peut comparer à des super-étapes du modèle BSP. Dans chacune de ces étapes, le framework Pregel appelle une fonction utilisateur qu’il applique sur chaque nœud du graphe distribué. La fonction spécifie le comportement d’un unique nœud général v pour une étape S. Cette fonction peut recevoir des messages envoyés à v à l’étape S − 1, et peut envoyer des messages à d’autres nœuds qui seront reçus à l’étape S + 1. On retrouve alors la notion de fonction utilisateur de MapReduce, ou de tout autre squelette, et l’on retrouve également l’application de cette fonction sur chacun des nœuds, tout comme dans les squelettes algorithmiques. Toutefois le modèle Pregel est une solution plus générale que les squelettes algorithmiques habituels et ne représente pas un patron de parallélisation unique. La fonction utilisateur peut, en effet, décrire des problèmes très variés et offre une plus grande liberté de codage que dans l’utilisation des squelettes algorithmiques. Il est important de noter que les algorithmes sur les graphes peuvent être exprimés comme des chaînes d’appels à MapReduce [38,75]. Toutefois, le modèle Pregel propose de meilleures performances. En effet, il conserve la même distribution de graphe d’une étape à l’autre du calcul, et utilise uniquement l’envoi et la réception de messages pour obtenir les informations d’autres processeurs. L’utilisation de MapReduce implique, quant à elle, tout d’abord (1) une distribution initiale des données, puis (2) l’application du map, puis pour terminer (3) des communications pour

2.4. Le parallélisme implicite 53

l’application du reduce, ce qui revient à communiquer toutes les données résultantes du map à chaque appel d’un mapreduce. Giraph [7] est une alternative à Pregel et utilise les même concepts.

Là encore il s’agit de solutions de parallélisme implicite très intéressantes et avec un certain nombre d’avantages. L’utilisation de ce type de solutions semble, tout d’abord, simplifier encore davantage la création de programmes parallèles, et la classification des différentes opérations d’un programme en patrons ne semble pas extrêmement difficile à élaborer. De plus, ces solutions utilisent deux types de spécificités pour mettre au point des optimisations : les structure de données distribuées, et les patrons utilisés. Les possibilités de performances paraissent donc plus importantes que dans les solutions dites générales. Cependant, un certain nombre de problèmes peuvent être notés avec ce type de solution. Tout d’abord, et comme nous l’avons décrit, chaque opération décrite à l’aide d’un patron, ou d’un squelette, est en fait une opération simple. À l’exception de Pregel, ces solutions sont très proches de la programmation fonctionnelle. Ainsi, si des calculs complexes doivent être mis en œuvre, un grand nombre d’appels imbriqués sera nécessaire, ce qui peut complexifier l’écriture, la lecture et la compréhension des programmes. Dans cette solution, de nouveau, il semble possible que la difficulté de programmation parallèle soit déportée vers l’utilisation de nouveaux concepts, et notamment vers les difficultés de la programmation fonctionnelle, non connue de la plupart des scientifiques.