• Aucun résultat trouvé

Sens du parcours, passage d’un array à un flux de données et vice versa

4.4 Primitives du calcul comme skeletons algorithmiques

4.4.4 Sens du parcours, passage d’un array à un flux de données et vice versa

Le passage d’une structure statique de données tel qu’un vecteur ou array 2D à une structure utilisée dynamiquement pour le traitement en flux nécessite le choix du sens de parcours.

Pour nos besoins, nous allons comprendre sous le terme parcours d’un array une séquence des index qui désignent les éléments d’un array. Nous parlons ainsi d’un stream des index et ce stream peut, géné- ralement, ne désigner qu’un sous-ensemble des éléments de cet array. Si les index de ce stream désignent tous les éléments d’un array, nous parlons d’un parcours complet d’un array.

Une fois le parcours défini, le passage d’un array à un flux de données sera obtenu par l’application de l’opération indexation des éléments, exprimée par la fonction ! du Haskell, sur le stream des index. À cette occasion, nous allons parler d’extraction des éléments ou également d’échantillonnage d’un array.

Ainsi perçu, l’accès à la mémoire qui est nécessaire pour l’obtention des éléments d’un array est exécuté en tant qu’opération sur le stream et la fonction ! d’échantillonnage de l’image devient, en effet, le kernel du calcul sur les flux de données. Nous pouvons le décrire dans le formalisme fonctionnel par l’expression suivante :

(map (ar ! ) ) $ (strm ar )

où ar est l’array d’entrée, strm désigne la fonction de parcours qui crée un stream des index à partir de l’array ar. Elle est suivie par l’application de la fonction map sur ce stream qui se charge d’exécuter l’indexation de l’array ar! sur chacun des index de ce stream. La figure 4.5 illustre cette situation. Le parcours de l’image y est représenté par le bloc de la fonction génératrice des index qui est succédé par le bloc d’extraction des éléments de la mémoire.

Cette modélisation mathématique que nous introduisons ici et qui perçoit l’accès à la mémoire comme opération sur les streams est très intéressante d’un point de vue pratique. En tant qu’opération

Array Fonction génératrice de la séquence des indexes Fonction d’extraction des éléments de la mémoire Flux d’indexes définissant le parcours de l’image Flux de données

FIG. 4.5 : Passage d’un array à un flux de données est effectué dans la logique des kernels d’exécution

sur les streams, elle peut profiter de toutes les techniques de parallélisation de traitement des streams, notamment de celle de la réplication fonctionnelle modélisée par le skeleton farm, cf. page 67. L’accès concurrent à la mémoire est difficile à imaginer sur les architectures classiques de Von Neumann, mais c’est une technique connue et utilisée sur les machines parallèles ou des architectures dédiées.

C’est cette approche de parcours de l’array et d’extraction des éléments que nous allons utiliser pour le passage d’un array à un flux de données. Cependant, les fonctions de parcours de l’image vont nous servir également lors de la recomposition d’un array de sortie à partir d’un flux de données. Il s’agit, en effet, du processus inverse au passage à flux de données et pour l’exprimer en formalisme fonctionnel, nous allons utiliser la fonction standard array du Haskell de création d’un array.

La fonction array prend deux arguments. Le premier argument est un tuple des bornes minimales et maximales des index. Nous reconstituons un array de sortie à partir d’un array d’entrée qui a les mêmes bornes maximales et minimales. Nous pouvons utiliser directement la fonction du Haskell qui fournit ces informations, bounds avec l’array d’entrée comme paramètre. Le deuxième argument de la fonction array est une liste des tuples (index, valeur) qui doit contenir tous les éléments inclus dans les bornes. Nous construisons cette liste à partir de notre stream des résultat et à partir du stream des index, le même que nous avons utilisé pour parcourir l’array. Nous les associons élément par élément avec la fonction zip du Haskell.

Voici un exemple de cette construction :

array (bounds $ ar ) ( zip ixs ( id ss) )

where ixs = strm ar ; ss = map (ar ! ) ixs

où ar désigne l’array d’entrée, ixs est le stream des index créé par la fonction strm du parcours de l’array et ss est le stream des valeurs des résultats. La fonction d’identité id nous indique l’endroit où nous plaçons des fonctions exécutives effectuant un traitement sur le stream des valeurs.

4.4.4.2 Fonction indices, fonction standard du Haskell pour le parcours d’un array

Dans le cas où nous avons besoin de parcourir l’array entier mais sans poser de contrainte sur le sens du parcours, nous pouvons utiliser la fonction standard indices du Haskell. Elle nous retourne des indices de tous les éléments de l’array par l’indexage de ses bornes.

Sachant que nous avons utilisé une fonction standard pour le passage à un flux de données, nous pouvons utiliser un mécanisme plus simple pour la recomposition de l’array de sortie en utilisant la fonction listArray du Haskell. Cette dernière n’exige pas l’association des éléments du flux des résultat avec l’index mais travaille directement avec ce stream. L’exemple suivant illustre cette situation :

listArray (bounds $ ar ) ( id ss)

4.4.4.3 Fonctions de parcours d’un array

Commençons notre explication par l’introduction de la fonction concrète de parcours d’un array 1D. La fonction streamAr1D définit un skeleton algorithmique qui crée un stream des index à partir d’un array 1D. La valeur passée par son paramètre how détermine le sens du parcours que nous voulons obtenir, la valeur ”FW” correspond au sens au-devant, la valeur ”BW” correspond au sens en arrière. La fig. 4.6 illustre ces deux cas sur un vecteur de la taille 3.

streamAr1D :: [ Char] → Ar I α → [ I ] streamAr1D how ar

| how == "FW" = [ ( lo + i ) | i ← [ 0 .. size−1]] | how == "BW"= [ ( hi−i) | i ← [ 0 .. size−1]] where

( lo , hi ) = bounds $ ar ; size = rangeSize(lo,hi)

1,2

1,1 1,3 (1,3) (1,2) (1,1)

(a)sens au-devant, streamAr1D avec "FW" 1,2

1,1 1,3 (1,1) (1,2) (1,3)

(b)sens en arrière, streamAr1D avec "BW"

FIG. 4.6 : Choix du parcours de l’image pour un array de 1D

Tandis que pour les structures 1D le choix du sens de parcours est simple et nous utilisons soit le parcours au-devant, soit le parcours en arrière, pour les structures 2D nous avons beaucoup plus de possibilités et nous décrivons celles qui sont utilisées le plus souvent. Il n’est pas difficile, en cas de besoin, d’en définir davantage qui seraient appropriées à un traitement particulier.

Mais tout d’abord, nous définissons un type Streamize que nous allons utiliser pour désigner une fonction de parcours d’un array de 2D dans les signatures de types de nos algorithmes :

typeStreamize α = Ar ( I , I ) α → [ ( I , I ) ]

Il s’agit des fonctions qui prennent un array 2D comme argument et qui nous retournent un stream des index.

Nous définissons un skeleton pour la création d’un stream des index à partir d’un array 2D par la fonction streamAr2D qui définit les parcours bien connus d’une image en sens vidéo et en sens anti- vidéo.

streamAr2D :: [ Char] → Ar ( I , I ) α → [ ( I , I ) ] streamAr2D how ar

| how == "FWFst" = [ ( flo + i , slo + j ) | j ←[0 .. smax], i←[0 .. fmax] ] | how == "FWSnd" = [ ( flo + i , slo + j ) | i←[0 .. fmax], j ←[0 .. smax] ] | how == "BWFst" = [ ( fhi −i,shi−j) | j ←[0 .. smax], i←[0 .. fmax] ] | how == "BWSnd" = [ ( fhi −i,shi−j) | i←[0 .. fmax], j ←[0 .. smax] ] where

( ( flo , slo ) , ( fhi , shi ) ) = bounds $ ar ;

fmax = rangeSize(flo,fhi)−1 smax = rangeSize(slo,shi)−1

Le type du parcours exact est déterminé par la valeur du paramètre how de cette fonction et corres- pond pour les valeurs ”FWFst” / ”FWSnd” aux sens au-devant par la première / deuxième coordonnée, et pour les valeurs et ”BWFst” / ”BWSnd” aux sens en arrière par la première / deuxième coordonnée, respectivement.

Notons que l’application partielle de cette fonction avec la paramètre how concret, e.g. :

nous définit dans Haskell une nouvelle fonction qui est du type Streamize α et qui est ainsi directement utilisable dans les algorithmes comme une fonction du parcours d’un array.

La figure 4.7 illustre le fonctionnement de ce skeleton pour un array 2D 3x3.

2,3 1,2 2,1 2,2 3,3 1,1 1,3 3,1 3,2 (1,1) (1,2) (1,3) (2,3) (2,2) (2,1) (3,3) (3,2) (3,1)

(a)sens au-devant par la première coordonnée, streamAr2D avec ”FWFst” 2,3 1,2 2,1 2,2 3,3 1,1 1,3 3,1 3,2 (1,1) (1,2) (1,3) (2,3) (2,2)(2,1) (3,3)(3,2) (3,1)

(b)sens au-devant par la deuxième coordonnée, streamAr2D avec ”FWSnd” 2,3 1,2 2,1 2,2 3,3 1,1 1,3 3,1 3,2 (1,1)(2,1) (3,1) (1,2) (2,2)(3,2) (1,3) (2,3) (3,3)

(c) sens en arrière par la première coordonnée, streamAr2D avec ”BWFst” 2,3 1,2 2,1 2,2 3,3 1,1 1,3 3,1 3,2 (1,1) (1,2)(1,3) (2,1) (2,2)(2,3) (3,1) (3,2) (3,3)

(d)sens en arrière par la deuxième coordonnée, streamAr2D avec ”BWSnd”

FIG. 4.7 : Choix du parcours de l’image pour un array de 2D 3 × 3