• Aucun résultat trouvé

8.3 Implantation des dictionnaires

8.3.1 Définition

8.3.3 Itérer sur un dictionnaire . . . 143

8.4 Rebalancement de la localisation des données . . . 144 8.5 Implantation des ensembles . . . 145

8.5.1 Transformations d’un vecteur parallèle d’ensembles . . . 146 8.5.2 Définitions de base . . . 146 8.5.3 Union de deux ensembles . . . 146 8.5.4 Intersection de deux ensembles . . . 148 8.5.5 Différence de deux ensembles . . . 150

8.6 Implantation des Piles et Files . . . 152 8.7 Exemple d’une application scientifique . . . 154 8.A Preuves des opérations ensemblistes . . . 158

8.A.1 Preuves des opérations de l’union . . . 158 8.A.2 Preuves des opérations de l’intersection . . . 159 8.A.3 Preuves des opérations de la différence . . . 161

C

OMME nous l’avons déjà fait remarquer, certains problèmes scientifiques nécessitent des perfor-mances que seules les machines parallèles peuvent offrir. Programmer ce genre d’architectures reste un travail difficile. La complexité des ordinateurs parallèles demande donc le développement de logiciels et de modèles de programmation afin d’aider les chercheurs (ou les ingénieurs) qui n’ont pas forcément les connaissances suffisantes pour le calcul parallèle et/ou qui n’ont pas le temps nécessaire pour écrire eux-mêmes du code efficace.

8.1 Introduction

Comme nous le faisions remarquer, les applications manipulent très fréquemment des structures de données. L’idée sous-jacente de ce chapitre est de remplacer l’utilisation de certaines structures de données séquen-tielles par leurs versions parallèles implantées dans un langage de programmation parallèle (ici, ce sera bien sûr BSML, mais un autre choix est envisageable) afin d’obtenir du code parallèle. On peut comparer cette idée à la définition de squelettes (patrons) de structures de données en BSML.

Le principal problème est donc de remplacer les structures initiales par des versions parallèles qui doivent rester efficaces surtout en présence d’un grand nombre de données. Dans les applications scientifiques, les

structures de données sont généralement une représentation du domaine physique, et donc une approxima-tion du résultat final. La clé pour une bonne parallélisaapproxima-tion est, ainsi, d’avoir un re-balancement des données sans détruire les propriétés de dépendance entre ces données : violer ces dépendances peut entraîner une sémantique incorrecte (par exemple, comment traiter un ensemble, dans le sens mathématique du terme, ayant des doublons ?) ou des calculs inutiles.

Ce chapitre traite de l’étude de cas d’implantation en BSML des structures de données classiques qui sont présentes dans la bibliothèque standard de OCaml. Le but est de faciliter le développement d’applications scientifiques effectuant du calcul symbolique sur des structures de données en utilisant les caractéristiques de haut niveau des langages fonctionnels. Pour cela, nos implantations utiliseront massivement le système de modules de OCaml, permettant de spécifier facilement une structure de données parallèle à partir de sa version séquentielle : la version parallèle sera ainsi indépendante de la version séquentielle utilisée. Ceci permettra une maintenance du code plus aisée. La complexité des implantations parallèles sera cachée par des interfaces de haut niveau que nous essaierons de rendre le plus proches possible de celles de OCaml, afin de simplifier leur emploi.

Un autre point important est l’application d’un calcul d’histogrammes pour le re-balancement des don-nées (suivant un algorithme décrit dans [19]). Ce re-balancement pourra s’effectuer de manière automatique (ou, plus exactement, suivant des paramètres donnés par le programmeur) ou de manière directe, c’est-à-dire à l’endroit où le programmeur le souhaite (quand il l’estime nécessaire). Dans ce chapitre, nous donnerons aussi les résultats d’une première expérience de l’utilisation de ces structures dans une application scien-tifique et de son expérimentation sur un cluster. Nous verrons alors comment, à l’aide de ces structures parallèles de haut niveau, nous obtenons des gains de performance significatifs.

Ce chapitre est organisé comme suit : dans un premier temps, nous rappellerons les bases du système de modules de OCaml. Ensuite, nous décrirons l’implantation de plusieurs structures de données parallèles, puis nous donnerons les résultats de nos expériences d’une application scientifique. Enfin, nous terminerons avec quelques travaux relatifs.

8.2 Description des modules en OCaml

Dans cette section, nous rappelons brièvement les caractéristiques du système de modules d’OCaml pour le lecteur à qui cela n’est pas familier. Il s’agit d’un langage à part entière au dessus du langage de base d’OCaml (il est en réalité indépendant du langage de base [177]), remplissant uniquement des fonctions de génie logiciel : compilation séparée, structuration de l’espace de noms, encapsulation et généricité du code.

8.2.1 Définition des structures

Le langage des modules est un langage fonctionnel d’ordre supérieur fortement typé, dont les termes sont appelés modules. Les «briques» de base sont les structures, unités regroupant entre eux des types, des valeurs, des exceptions ou des définitions de modules. Par exemple, une implantation naïve des ensembles sous la forme de listes ordonnées pourrait être la suivante :

module Set = struct

typeαset =αlist

(empty:αlist)

let empty = []

(comp:α →α →int)

let comp = compare

(add:α →αlist→α list)

let rec add e s = match s with

[]→[e]

| hd::tl→match (comp e hd) with

0→s (e est déjà dans s)

139 8.2. DESCRIPTION DES MODULES EN OCAML

| x when x>0→hd :: add e tl

(member:α →αlistbool)

let rec member e s = match s with

[]→false

| hd::tl→match (comp e hd) with

0→true (x est inclu dans s)

| x when x<0false (x est plus petit qu’un élément de l’ ensemble s)

| x when x>0→member e tl

end

8.2.2 Définition des signatures

Les types des structures, appelés signatures, permettent de restreindre la visibilité des composantes d’une structure en lui adjoignant une interface1 et de masquer la définition de certains types (on parle alors de

type abstrait). Ainsi, une signature possible masquant l’implantation des ensembles serait :

module type SET = sig

typeαset

val empty:αlist

val add:α →αlist→αlist

val member:α →αlist→bool

end

Restreindre la structureSetavec la signatureSETainsi définie permet d’avoir un autre point de vue de la structure où la fonctioncompn’est plus accessible (fonction utilitaire pour l’implantation et superfétatoire à l’utilisateur) et où la représentation des ensembles (c’est-à-dire comment ils ont été implantés) est cachée :

module Abstract_Set = (Set : SET)

Notons que l’on peut directement resteindre l’interface d’une structure durant sa définition :

module Set: SET = struct ... end

8.2.3 Abstraction des structures

Les fonctions de ce langage, appelées foncteurs, permettent d’écrire des modules paramétrés par d’autres modules et de les appliquer ensuite à des cas particuliers. L’apport des foncteurs en terme de génie logiciel est important lorsqu’il s’agit de paramétrer de manière cohérente un ensemble de types et de fonctions par un autre ensemble de types et de fonctions.

Par exemple, nous pouvons «fonctoriser» l’implantation desSetavec un module définissant le type des éléments de l’ensemble et comment comparer de tels éléments. Cette dernière fonction remplacera l’emploi de la fonction générique (et dans ce cas peu sûre)compared’OCaml :

(type de comparaison)

type comparison = Less | Equal | Greater

(module pour la comparaison des éléments)

module type ORDERED_TYPE = sig

type t

val compare: t→t→comparison

end

(redéfinition abstraite des ensembles)

module type SET =

sig

type elt (type des éléments)

type t (type des ensembles)

val empty: t val add: elt→t→t

val member: elt→t→bool

end

module Set (Elt: ORDERED_TYPE) : SET with type elt = Elt.t = struct

type elt = Elt.t type t = elt list let empty = []

let rec add e s = match s with

[]→[e]

| hd::tl→match Elt.compare e hd with

Equal→s | Less→e :: s

| Greater→hd :: add e tl

let rec member e s = match s with

[]→false

| hd::tl→match Elt.compare e hd with

Equal→true

| Less→false

| Greater→member e tl

end

L’annotationwith typepermet d’unifier ici le type abstraiteltde la signatureSETavec le typetde la signatureORDERED_TYPE. D’autre part, la signatureORDERED_TYPEexigée pour l’argument du foncteur ne contient que ce qui est nécessaire à l’implantation de l’algorithme. On pourra cependant appli-quer ce foncteur à tout module dont la signature contient au moinsORDERED_TYPE, c’est-à-dire qui en est un sous-type. Enfin, il est possible de construire des agrégats de signatures ou de modules à l’aide de la constructioninclude, qui peut être vue comme une inclusion textuelle.

La bibliothèque standard d’OCaml fournit de nombreuses structures de données. Chacune est définie par un module où sont décrites les opérations sur ces structures ainsi que, pour certaines, la complexité de telles opérations.

Les modules implantant les structures de données parallèles devront avoir les mêmes signatures que celles d’OCaml. La sémantique de haut niveau (décrivant le résultat de l’opérateur sur la structure) devra elle aussi être la même, afin de conserver la cohérence des résultats des programmes. Naturellement, les coûts seront différents, ainsi que le fonctionnement «bas niveau». Les signatures seront aussi dotées de quelques opé-rateurs permettant un meilleur emploi des structures parallèles. De cette manière, un programmeur pourra profiter pleinement des capacités de la machine parallèle en utilisant ces représentations parallèles des structures, et ceci sans avoir tous les inconvénients et difficultés d’un langage dédié à la programmation parallèle.

Pour l’instant, nous avons l’implantation de cinq de ces structures de données :Set(ensemble),Map (dictionnaire, parfois aussi appelé table d’association),Hashtable(table de hachage) , Queue(file, ou encore appelée FIFO, «first in, first out») etStack(pile, également appelée LIFO, «last in, first out»). Dans les sections qui vont suivre, nous détaillerons ces implantations. Nous donnerons le coût BSP de chacune des opérations (ou squelettes de structures de données) et ceci de manière indépendante vis-à-vis de la complexité des opérations séquentielles. Nous notonsCtla complexité d’une opération séquentielle etS la

141 8.3. IMPLANTATION DES DICTIONNAIRES

8.3 Implantation des dictionnaires

8.3.1 Définition

Le module des dictionnaires parallèles est un foncteurMakequi construit une implantation d’un diction-naire parallèle polymorphe à partir :

1. D’un module contenant un type pour les clés, une fonction d’ordre total sur celles-ci et une fonction de hachage (telle que deux éléments égaux ont la même clé de hachage) ;

2. D’un module définissant la stratégie de re-balancement (celle-ci sera plus détaillée dans la sec-tion 8.4) ;

3. D’un dernier foncteur permettant la construction d’un dictionnaire séquentiel. En OCaml, nous obtenons :

module Make (Ord : OrderedType)(Bal:BALANCE)

(MakeLocMap:functor(Ord:OrderedType)Map.S with type key=Ord.t) : S with type key = Ord.t and typeαseq_t =αMakeLocMap(Ord).t

avec les signatures suivantes :

module type OrderedType = sig

type t

val compare:t→t→int

val hash:t→int

end

module type S = sig type key (type des clés)

typeαt (type d’un dictionnaire parallèle)

typeαseq_t (type d’un dictionnaire séquentiel) (opérateurs classiques)

(opérateurs parallèles)

end

Nous définissons un dictionnaire parallèle comme une paire comportant un vecteur parallèle de dictionnaires séquentiels et une paire comportant un booléen et un entier :

module Make (Ord : OrderedType)(Bal:BALANCE)

(MakeLocMap:functor(Ord:OrderedType)Map.S with type key=Ord.t) =

struct

(LocMap est le module des dictionnaires séquentiels)

module LocMap = MakeLocMap(Ord) type key = Ord.t

typeαt =αLocMap.t par∗(int∗bool)

typeαseq_t =αLocMap.t

(opérations surαt, le dictionnaire parallèle)

end

Le booléen permet de savoir si le dictionnaire parallèle a été (oui ou non) re-balancé et l’entier définit le nombre d’opérations qui ont modifié le dictionnaire parallèle. Cet entier sera utilisé pour le re-balancement. Nous l’ignorons pour l’instant et nous nous reférons à la section 8.4 pour de plus amples informations.

Un dictionnaire parallèle enregistre un élément (c’est-à-dire une liaison entre une clé et une donnée) dans un unique dictionnaire séquentiel, et donc dans un seul processeur à la fois. Toutes les opérations se feront en supposant et en maintenant le fait qu’une liaison ne peut être présente que sur un seul processeur. Ce sera donc l’invariant de nos opérations. Le dictionnaire parallèle emploie le module des dictionnaires séquentiels qui a été donné en paramètre via un foncteur. Nous pouvons ainsi utiliser l’implantation OCaml des dictionnaires (arbres binaires équilibrés), mais nous pouvons tout aussi bien recourir à n’importe quelle autre implantation, comme celles qui ont été certifiées en Coq dans [106] (nous y trouvons des implantations certifiées se servant de listes triées, d’arbres binaires équilibrés ou bien encore d’arbres dits rouge-noir). Maintenant, nous allons décrire l’implantation générique des opérations sur ces dictionnaires parallèles. Celles-ci seront purement fonctionnelles, c’est-à-dire sans effets de bord. Par la suite, nous notonsmipour un dictionnaire séquentiel au processeuri.

Documents relatifs