You are on page 1of 62

E COLE D OCTORALE

U.F.R. S CIENTIFIQUE
S CIENCES POUR
ET T ECHNIQUE
L’I NGÉNIEUR

U NIVERSITÉ B LAISE PASCAL


C LERMONT-F ERRAND

MEMOIRE
présenté par
Joël FALCOU
en vue de l’obtention du

D.E.A.
«C OMPOSANTS ET S YSTÈMES POUR LE T RAITEMENT DE
L’I NFORMATION »

Option “Vision pour la Robotique”

L ABORATOIRE DES S CIENCES ET M ATÉRIAUX POUR


L’E LECTRONIQUE , ET D ’AUTOMATIQUE
(U.M.R. 6602 DU C.N.R.S.)

Développement d’une bibliothèque de calcul SIMD.


Application au traitement d’image.

le 9 septembre 2003
Remerciements

Je tiens tout d’abord à remercier Jocelyn SEROT pour son soutien et l’interêt porté au
projet.

Je remercie aussi Christophe DUHAMEL pour son écoute et ses conseils avisés.

je tiens aussi à remercier Eric ROYER, Maxime FIANDINO et Lucie MASSON pour
leurs aides diverses et variées.

2
Résumé
Les divers algorithmes mis en oeuvre dans le traitement d’image deviennent de plus
en plus coûteux en terme de temps de calcul au fur et à mesure que leur complexité
s’accroît. Pour pallier ce problème, diverses solutions basées sur l’utilisation de machine
parallèle ont vu le jour. Un cas particulier de ces machines est l’ unité de calcul SIMD
AltiVec, intégré au processeur PowerPC G4, qui permet d’obtenir sur un de ces seul pro-
cesseur des performances équivalentes à celle de machines multiprocesseurs. Pourtant,
l’utilisation de cette unité de calcul reste marginale du fait de sa complexité.

La bibliothèque E.V.E utilise une approche basée sur la programmation orienté ob-
jet pour fournir aux développeurs d’application de traitement d’image un moyen simple
d’utiliser le potentiel de l’unité de calcul SIMD Altivec. E.V.E utilise diverses techniques
de meta-programmation template pour fournir une interface de programmation intui-
tive. Les premiers résultats ont montré que E.V.E permet d’exprimer un grand nombre
d’algorithmes de traitement d’images en fournissant des facteurs d’accélération com-
pris entre 2 et 12.

Mots-clés : Traitement d’image, SIMD, AltiVec, programmation orienté objet, meta-


programamtion template.

Abstract
As image processing algorithms become more complex, their need in computing po-
wer increases. To solve this problem, parallel machines can be employed. An example
is AltiVec SIMD unit - embedded in the PPC G4 chip - which allows performances
equivalent to multiprocessors machines on a single processor CPU. However AltiVec unit
remains marginaly used because of their relative complexity.

The E.V.E library aims to ease the development of Altivec application by using an ob-
ject oriented framework and to provide significant performance gains. E.V.E library relies
on template meta-programming techniques to expose a intuitive API. First tests have
shown a speedup factor of 2 to 12 on various image processing algorithms.

Keywords : Image processing, SIMD, AltiVec, object oriented programming, Tem-


plate meta-programming.
Table des figures

1.1 Vision macroscopique du modèle à parallélisme de données . . . . . . . . 10


1.2 Vision microscopique du modèle à parallélisme de données . . . . . . . . 10
1.3 Exécution de la primitive map . . . . . . . . . . . . . . . . . . . . . . . 12
1.4 Exécution d’une primitive foldl . . . . . . . . . . . . . . . . . . . . . . . 12
1.5 Une Machine MIMD-SM . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.6 Une Machine MIMD-DM . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.7 Une Machine SIMD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.8 Déroulement d’une opération SWAR . . . . . . . . . . . . . . . . . . . . 16
1.9 Les types de données AltiVec . . . . . . . . . . . . . . . . . . . . . . . . 16
1.10 Structure interne du PowerPC 74xx . . . . . . . . . . . . . . . . . . . . . 17
1.11 Filtre 1x3 en C ANSI classique . . . . . . . . . . . . . . . . . . . . . . . 18
1.12 Principe du filtre 1x3 en AltiVec . . . . . . . . . . . . . . . . . . . . . . 18
1.13 Algorithme du filtre 1x3 en C AltiVec . . . . . . . . . . . . . . . . . . . 19
1.14 Performances du Filtre 1x3 . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.1 Transtypage erroné de vecteur AltiVec. . . . . . . . . . . . . . . . . . . . 22
2.2 Transtypage de vecteur AltiVec. . . . . . . . . . . . . . . . . . . . . . . 22
2.3 Calcul de l’inverse en AltiVec . . . . . . . . . . . . . . . . . . . . . . . 23
2.4 Filtre 1x3 utilisant la classe AVector . . . . . . . . . . . . . . . . . . . . 24
2.5 Performances du Filtre 1x3 en C++ AltiVec . . . . . . . . . . . . . . . . 25
2.6 Expression simple utilisant une classe définie par l’utilisateur. . . . . . . 25
2.7 Code "précompilé" du filtre 1x3 utilisant AVector. . . . . . . . . . . . . . 26

3.1 Fonction integrate utilisant un pointeur de foncton . . . . . . . . . . . . . 28


3.2 Exemple simple d’Expressions Templates. . . . . . . . . . . . . . . . . . 29
3.3 la fonction integrate "déroulée" . . . . . . . . . . . . . . . . . . . . . . . 29
3.4 Arbre syntaxique de x/(1+x) . . . . . . . . . . . . . . . . . . . . . . . . 30
3.5 Vue éclatée de l’arbre syntaxique de x/(1+x) . . . . . . . . . . . . . . . . 30
3.6 Une fonction template simple : min . . . . . . . . . . . . . . . . . . . . . 31
3.7 Utilisation d’une fonction template . . . . . . . . . . . . . . . . . . . . . 32
3.8 Grammaire simplifiée des expressions arithmétiques . . . . . . . . . . . . 32
3.9 Classe template BinaryNode . . . . . . . . . . . . . . . . . . . . . . . . 33
3.10 Classe template Expr . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.11 Reconstruction du type associé à x/(1+x) . . . . . . . . . . . . . . . . . . 34
TABLE DES FIGURES

3.12 Classe template OpAdd . . . . . . . . . . . . . . . . . . . . . . . . . . . 35


3.13 Classe template Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.14 Classe template Const . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.15 Opérateur + générateur de donnée statique . . . . . . . . . . . . . . . . . 36
3.16 Code de la fonction integrate . . . . . . . . . . . . . . . . . . . . . . . . 37
3.17 Chaîne d’évaluation d’une expression . . . . . . . . . . . . . . . . . . . 37
3.18 Squelette d’une classe mêlant vecteur numérique et E.T . . . . . . . . . . 38
3.19 Classe template BinaryNode modifiée . . . . . . . . . . . . . . . . . . . 39
3.20 Classe template Var modifiée . . . . . . . . . . . . . . . . . . . . . . . . 39
3.21 Classe template Const modifiée . . . . . . . . . . . . . . . . . . . . . . . 40
3.22 Opérateur + modifié . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.1 Initialisation de E.V.E . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.2 Option de compilation de E.V.E . . . . . . . . . . . . . . . . . . . . . . 42
4.3 Utilisation de array . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.4 Fonctions et opérateurs de array . . . . . . . . . . . . . . . . . . . . . 44
4.5 Fonctions inter-éléments . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.6 Exemple d’utilisation de E.V.E . . . . . . . . . . . . . . . . . . . . . . . 45
4.7 Liste d’initialisation de array . . . . . . . . . . . . . . . . . . . . . . . 46
4.8 array et la notation indicielle . . . . . . . . . . . . . . . . . . . . . . . 46
4.9 Prototype de la fonction where . . . . . . . . . . . . . . . . . . . . . . 47
4.10 Exemple d’utilisation de la fonction where . . . . . . . . . . . . . . . . 47
4.11 Prototype de la fonction fold . . . . . . . . . . . . . . . . . . . . . . . 47
4.12 Exemple d’utilisation de la fonction fold . . . . . . . . . . . . . . . . 48
4.13 Exemple d’utilisation de filter . . . . . . . . . . . . . . . . . . . . . . . 48
4.14 Exemple d’utilisation des filtres constants . . . . . . . . . . . . . . . . . 49
4.15 Exemple d’utilisation des itérateurs de E.V.E. . . . . . . . . . . . . . . . 49
4.16 Utilisation conjointe de EVE et des fonctions de la STL. . . . . . . . . . 50
4.17 Utilisation conjointe de EVE et des flux standards. . . . . . . . . . . . . 51
4.18 Méthodes print_on et read_from. . . . . . . . . . . . . . . . . . . 51
4.19 Comparaison print_on et cout. . . . . . . . . . . . . . . . . . . . . 52
5.1 Inverseur d’image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.2 Soustracteur d’image . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.3 Performances des algorithmes simples. . . . . . . . . . . . . . . . . . . . 54
5.4 Performances du produit-somme interne. . . . . . . . . . . . . . . . . . . 55
5.5 Application du détecteur de Harris . . . . . . . . . . . . . . . . . . . . . 57
5.6 Filtrage et Evaluation du détecteur de Harris . . . . . . . . . . . . . . . . 57
5.7 Performances du détecteur de Harris - Filtrage. . . . . . . . . . . . . . . 58
5.8 Performances du détecteur de Harris - Evaluation de H. . . . . . . . . . . 58
5.9 Comparaison de performances PPC G4/Pentium IV. . . . . . . . . . . . . 58

5
Table des matières

Remerciements 2

Table des matières

Introduction 8

1 Contexte de l’Etude 9
1.1 Le Parallélisme de données . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.1.1 Modèle de programmation . . . . . . . . . . . . . . . . . . . . . 9
1.1.2 Modèle d’exécution . . . . . . . . . . . . . . . . . . . . . . . . 13
1.2 Cas Particulier : le SWAR . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.2.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.2.2 Un exemple : la Technologie AltiVec . . . . . . . . . . . . . . . 16
1.2.3 Développer avec AltiVec . . . . . . . . . . . . . . . . . . . . . . 18

2 Position du Problème 21
2.1 Difficulté du développement utilisant AltiVec . . . . . . . . . . . . . . . 21
2.1.1 Gestion des types . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.1.2 Autres difficultés . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.2 Solution envisagée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.2.1 Principes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.2.2 Analyse de la situation . . . . . . . . . . . . . . . . . . . . . . . 25

3 les Expressions Templates 27


3.1 Technique d’évaluation partielle . . . . . . . . . . . . . . . . . . . . . . 31
3.1.1 Rappel sur les templates . . . . . . . . . . . . . . . . . . . . . . 31
3.1.2 Application à l’évaluation partielle . . . . . . . . . . . . . . . . . 32
3.1.3 Evaluation des données dynamiques . . . . . . . . . . . . . . . . 37
3.2 Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

4 La bibliothèque E.V.E 41
4.1 Présentation de l’interface . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.1.1 Mise en place de E.V.E . . . . . . . . . . . . . . . . . . . . . . . 41
4.1.2 Fonctionnalités de base . . . . . . . . . . . . . . . . . . . . . . . 42
4.2 Algorithmes et primitives de plus haut-niveau . . . . . . . . . . . . . . . 46
4.3 Utilisation conjointe de E.V.E et de la STL . . . . . . . . . . . . . . . . . 49
4.3.1 Gestions des itérateurs . . . . . . . . . . . . . . . . . . . . . . . 49
4.3.2 Fonctions classiques de la STL . . . . . . . . . . . . . . . . . . . 50
4.3.3 E.V.E et les flux standards . . . . . . . . . . . . . . . . . . . . . 50

5 Performances de E.V.E 53
5.1 Opérations élémentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.1.1 Principes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.1.2 Code E.V.E . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.1.3 Performance : . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.2 Algorithmes de la STL . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.2.1 Produit-somme interne . . . . . . . . . . . . . . . . . . . . . . . 55
5.2.2 Analyse : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.3 Un Détecteur de point d’intérêt . . . . . . . . . . . . . . . . . . . . . . . 56
5.3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.3.2 Mise en oeuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.3.3 Performances . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.3.4 Commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

Conclusion 60

Références bibliographiques
Introduction

Le traitement d’image est une technique dont les progrès sont fortement liés à ceux
de l’informatique et de l’électronique. Depuis son émergence dans les années 70, le trai-
tement d’images n’a cessé de se diversifier et de réclamer de plus en plus de ressources à
la fois techniques et humaines. En outre, la complexité des algorithmes mis en oeuvre ne
cesse d’augmenter, demandant des machines toujours plus puissantes pour être exécutés.
Les machines parallèles ont permis de pallier cette demande croissante de puissance en
offrant des puissances de calcul que ne peuvent fournir les ordinateurs séquentiels.

Le groupe GRAVIR du LASMEA s’est positionné sur ce créneau des machines pa-
rallèles et à notamment développé depuis plusieurs années des machines de vision artifi-
cielle parallèles. Grâce à des réalisations comme la machine TransVision ou le Beowulf
OSSIAN, divers types d’algorithmes et d’applications de traitement d’image et de vision
artificielle ont pu être implantés de manière efficace. Récemment des travaux portant sur
l’utilisation d’unité de calcul SIMD au sein de processeurs "classiques" mirent en évi-
dence le potentiel d’une telle technologie. Les études effectuées sur l’unité AltiVec du
PPC G4 de Motorola par Lionel Damez ont notamment montré que l’utilisation d’une
telle unité de calcul fournissait un moyen efficace d’implanter des algorithmes de vision
gourmands en temps de calcul sur une machine monoprocesseur. les mêmes travaux ont
toutefois montré que l’utilisation d’une telle extension SIMD restait complexe pour les
non-spécialistes.

L’objet de cette étude est de démontré qu’il est possible de développer une biblio-
thèque (un framework) permettant à un développeur familier des bibliothèques standards
du langage C++ ou à un algorithmicien de développer rapidement une application tirant
partie de l’accélération fournie par AltiVec. Le but ultime étant de pouvoir écrire un code
humainement lisible, très proche du formalisme mathématique et algorithmique, qui soit
interprété de manière optimale par les machines équipées de processeur doté d’une unité
AltiVec.

Nous présenterons donc dans le chapitre 1 de cette étude un historique du parallélisme


de donnée et des extensions SIMD. Nous verrons au chapitre 2 quel solutions furent envi-
sagées pour résoudre notre problèmes et aux chapitre 3 quelles techniques furent utilisée.
Finalement, le chapitre 4 présentera le résultat de nos travaux, la bibliothèque E.V.E, et le
chapitre 5 les performances obtenues sur divers algorithmes de traitement d’images.

8
Chapitre 1

Contexte de l’Etude

1.1 Le Parallélisme de données


1.1.1 Modèle de programmation
1.1.1.1 Principe du modèle de programmation à parallélisme de données

En programmation parallèle, on distingue globalement deux classes de modèles de


programmation : les modèles à parallélisme de contrôle et les modèles à parallélisme de
données. Dans les premiers, le parallélisme provient de l’exécution parallèle de processus
séquentiels. On parle alors de modèle "PAR of SEQ" [1]. Un exemple est le modèle
CSP1 [2] introduit par Hoare et utilisé par exemple dans le langage Occam [3] ou le
modèle dit "à passage de message" sur lequel repose la bibliothèque MPI [4]. Dans les
seconds modèles, le parallélisme provient de l’exécution séquentielle d’actions opérant
sur des structures de données parallèles. On parle alors de modèle "SEQ of PAR" [1].

Les modèles à parallélisme de données fournissent deux points de vues différents et


complémentaires sur un même programme :

– un point de vue macroscopique : ce point de vue correspond à la vision de l’uti-


lisateur final. Un programme est considéré comme une séquence d’opérations sur
un ensemble de données à accès parallèles (cf fig. 1.1). On ramène alors les pro-
grammes à une suite de ré-arrangement d’éléments d’un tableau. Il apparait alors
qu’un grand nombre d’algorithme peuvent être exprimés à partir de telles opérations
[5]. Des langages comme HPF [6, 7] sont essentiellement fondés sur cette vision du
parallélisme.

1
Communicating Sequential Processes

9
1.1. LE PARALLÉLISME DE DONNÉES

Tableau de données

Processeur Mémoire

F IG . 1.1 – Vision macroscopique du modèle à parallélisme de données

– un point de vue microscopique : il correspond au point de vue bas-niveau du com-


pilateur. Le programme est une composition parallèle d’opérations séquentielles
menées sur des données parallèles locales et est soumis à un mécanisme de synchro-
nisation de ces traitements asynchrones. Tout se passe comme si l’on avait affaire
à un tableau de processeurs (cf fig.1.2). On retrouve une telle approche dans des
langages comme POMPC [8].

Tableau de processeurs
interconnectés

Séquenceur

F IG . 1.2 – Vision microscopique du modèle à parallélisme de données

L’idée sous-jacente de la programmation à parallélisme de données est donc d’effec-


tuer une suite d’opérations sur des structures de données de grande taille. Lors de ces
traitements, les opérations menées sur des éléments indépendants de cette structure sont
effectuées simultanément. Par rapport aux modèles à parallélisme de contrôle, les mo-
dèles à parallélismes de données présentent trois principaux avantages :

10
1.1. LE PARALLÉLISME DE DONNÉES

– le parallélisme de données conduit à une exécution déterministe des programmes.


Il élimine donc les problèmes de développement et de mise au point liés à au non-
determinisme (inter-blocages, race conditions ).

– l’expression du parallélisme au sein du programme est explicite et apparaît à travers


le choix des structures de données alors que l’ensemble de la structure algorith-
mique du code reste celle d’un programme séquentielle. Ainsi l’écriture du code
reste simple et ne demande pas au développeur de changer radicalement de façon
de penser.

– Le degré de parallélisme potentiel d’une machine utilisant le modèle à parallélisme


de données est directement lié à la taille des structure de données parallèles utili-
sées. Le parallélisme peut être augmenté simplement en augmentant la taille des
structures de données. L’organisation du code reste identique et aucune réorgani-
sation de l’algorithme n’est nécessaire. Pour le parallélisme de contrôle, ce degré
est lié au nombre de tâches exécutables simultanément au sein de l’algorithme. En
pratique, les ordres de grandeur de ces degrés sont très différents 2 .

1.1.1.2 Primitives de bases

L’écriture de programme à parallélisme de données suppose l’existence d’un certain


nombre de primitives classiques que nous allons décrire ici. Dans la suite de cette exposé,
nous utiliserons une notation simplifié pour expliciter la sémantique de ces primitives :

1. [x0 , ..., xn ] représente une séquence de valeurs3 .[] représente la séquence vide.
2. Le symbole ⊕ représente un opérateur ou une fonction binaire quelconque.
3. Le symbole ⊙ représente un opérateur ou une fonction unaire quelconque.
4. L’application d’un opérateur ou d’une fonction à un ou plusieurs arguments se note
respectivement (⊙ x) et (x ⊕ y) ;

A partir de ces considérations structurelle, on peut définir un certain nombre de primi-


tives pour le parrallélisme de données.

– Primitive applicative : map.


La primitive map permet d’appliquer une fonction à un ensemble de données. Il
s’agit d’une primitive très simple et très employée. Elle se définit ainsi (fig. 1.3) :

map ⊙ [x0 , ..., xn ] = [⊙x0 , ..., ⊙xn ]

2
Typiquement inférieure à la dizaine pour le parallélisme de contrôle, jusqu’à plusieurs dizaines de milliers pour le
parallélisme de données.
3
En pratique, ces séquences correspondent à des tableaux ou des listes

11
1.1. LE PARALLÉLISME DE DONNÉES

Exemple :
map abs [-1.2, -2.3, 3.4, -4.5 ] = [1.2, 2.3, 3.4, 4.5 ]

0 1 2 3 4 5

F(.) F(.) F(.) F(.) F(.) F(.)

F(0) F(1) F(2) F(3) F(4) F(5)

F IG . 1.3 – Exécution de la primitive map

– Primitive de réduction : fold.


La primitive fold se décline en deux versions : foldl et foldr qui effectuent res-
pectivement une réduction à gauche et à droite. La primitive foldl par exemple est
illustrée sur le figure 1.4 :

X0 X1 X2 X3 X4 X5

foldl Z + [X 0, ... X5]


Z

F IG . 1.4 – Exécution d’une primitive foldl

f oldl ⊕ z[x0 , ..., xn ] = [...(z ⊕ x0 ) ⊕ x1 )... ⊕ xn ]

Exemple :
foldl (+) 0 [1,2,3,4 ] = 10

– Primitive de reduction des préfixes : scan.


La primitive scan permet de réduire successivement tout les préfixes d’une liste.
Tout comme fold, elle admet deux versions : scanr et scanl. Pour l’exemple de la
primitive scanl, on a ainsi :

scanl ⊕ z[x0 , ..., xn ] = [z, z ⊕ x0 , z ⊕ x0 ⊕ x1 , ..., z ⊕ x0 ... ⊕ xn ]

12
1.1. LE PARALLÉLISME DE DONNÉES

Exemple :
scanl (*) 1 [1,2,3,4,5 ] = [1,1,2,6,24,120 ]

De nombreux travaux ont permis d’établir des règles et des théorèmes sur la composi-
tion de telles primitives. Le formalisme dit de Bird-Meertens [9, 10] par exemple permet
de raisonner formellement sur des équations utilisant ces primitives pour permettre de
simplifier l’expression algorithmique de ces équations et de réduire dans certains cas le
temps d’exécution des programmes.

1.1.2 Modèle d’exécution


Si le modèle de programmation renvoie à la vision du programmeur, le modèle d’exé-
cution concerne l’organisation physique des machines à même d’exécuter ces programmes.
Si on se restreint au parallélisme de données, deux modèles architecturaux sont suscep-
tibles de le supporter :

1. Le modèle MIMD :
Dans ce modèle, la machine est formée de N processeurs, chaque processeur étant
composé d’une unité de séquencement des instructions (unité de contrôle) et d’une
unité de traitement. Chaque processeur peut disposer de son propre espace mémoire
(MIMD à mémoire distribuée, fig. 1.6) ou cette mémoire peut être partagée par
l’ensemble des processeurs (MIMD à mémoire partagée, fig. 1.5). Dans le premier
cas, un réseau de communication permet l’échange de données inter-processeurs.
Les machines MIMD se prêtent bien à l’implantation de programme à parallélisme
de contrôle, chaque processeur prenant alors en charge l’exécution d’un ou plu-
sieurs processus séquentiels. Elles peuvent aussi être utiliser pour implanter des
programmes à parallélisme de données en forçant chaque processeur à exécuter
le même programme mais sur des données différentes. On parle alors de modèle
SPMD (Single Program, Multiple Data).

13
1.1. LE PARALLÉLISME DE DONNÉES

UC UT
1 1

UC UT

Mémoire
2 2

...

UC UT
N N

Flot d'instruction

Flot de données

F IG . 1.5 – Une Machine MIMD-SM

UC UT MEM
1 1 1
Réseau de communication

UC UT MEM
2 2 2

...
UC UT MEM
N N N

Flot d'instructions

Flot de données

F IG . 1.6 – Une Machine MIMD-DM

14
1.2. CAS PARTICULIER : LE SWAR

2. Le modèle SIMD :
Dans ce modèle, une seule unité de contrôle fournit le flot d’instructions pour un
ensemble d’unité de traitement opérant en parallèle (cf fig. 1.7). Si ce modèle a
connu son heure de gloire dans les années 80 avec des machines comme la CM-
2 [11], il est resté en pratique peu utilisé car il suppose une architecture dédiée
(contrairement au machines MIMD qui peuvent être construite en inter-connectant
des ordinateurs séquentiels). Il est réapparu toutefois sous une forme particulière à
la fin des années 90 en donnant naissance au SWAR.

UT
1

UT

Mémoire
UC P

...

UT
N

Flot d'instruction

Flot de données

F IG . 1.7 – Une Machine SIMD

1.2 Cas Particulier : le SWAR


1.2.1 Principe
Le SWAR (SIMD Within A Register) est une spécialisation du modèle SIMD consistant
à fournir au sein d’un processeur classique une unité de traitement SIMD permettant de
gérer des programmes à parallélisme de donnés en utilisant un jeu de registres spécifiques
stockant en leur sein plusieurs données de même type. L’application d’opérations sur de
tels registres revient donc à effectuer la même opération en parallèle sur l’ensemble des
données contenues dans le registre (cf fig. 1.8). Parmi les différentes incarnations de ce
principe, on retrouve les extensions dites "multimédia" présentes sur certains processeur

15
1.2. CAS PARTICULIER : LE SWAR

généraliste : l’extension MAX[12] de HP, les extensions MMX[13],SSE et SSE2 des pro-
cesseurs Intel ou l’extension AltiVec des PowerPC.

Mémoire
UC UT

Flot d'instruction

Flot de données

F IG . 1.8 – Déroulement d’une opération SWAR

1.2.2 Un exemple : la Technologie AltiVec


AltiVec est le nom de l’extension SIMD intégré au processeur MPC 74xx de Motorola[14,
15]. L’unité de calcul AltiVec qui est intégrée à l’architecture du PowerPC repose sur une
architecture vectorielle permettant le traitement simultané de plusieurs données en paral-
lèle [15]. Elle se distingue des autres unités de calcul parallèle concurrentes [13, 12] en
plusieurs points :

Une arithmétique polyvalente.


L’unité de base des calculs effectués par AltiVec est le vector. Un vector est une struc-
ture de données de 128 bits pouvant contenir des données de plusieurs types (cf. figure
1.9) :

1. C C C C C C C C C C C C C C C C 16 Entiers 8 bits

2. Short Short Short Short Short Short Short Short 8 Entiers 16 bits

3. Int Int Int Int 4 Entiers 32 bits

4. Float Float Float Float 4 Flottants 32 bits

5. 8 Pixels RGBA 16 bits

6. A A A A 16 pixels RGBA 32 bits

F IG . 1.9 – Les types de données AltiVec

16
1.2. CAS PARTICULIER : LE SWAR

Outre cette diversité de type, AltiVec gère aussi bien l’arithmétique classique que
l’arithmétique saturante. L’utilisateur peut donc choisir de gérer à sa convenance les er-
reurs de dépassements lors de ses calculs.

Un parfaite intégration au sein du processeur.


L’unité AltiVec est complètement indépendante des unités de calculs classiques (cf.
figure 1.10).

Decodeur / Répartiteur
Flot d’instructions

IU FPU Unité Vectorielle

GPR FPR Registres Vectoriels

32 bits 64 bits 128 bits

Mémoire cache

F IG . 1.10 – Structure interne du PowerPC 74xx

Deux unités de traitement numérique indépendantes lui permettent de traiter à la fois


les opérations sur les entiers et sur les flottants. Ainsi, contrairement aux architectures
MMX, on peut générer du code mélangeant à la fois des instructions manipulant des
types de données classiques (int, float) et des instructions manipulant des types vec-
toriels (vector float, vector int). L’unité AltiVec peut aussi traiter simultané-
ment des vector int et des vector float. De part les limitations matérielles de
l’extension AltiVec, le processeur ne peut toutefois réorganiser que deux niveaux d’ins-
tructions. On parle alors de processeur super-scalaire de degré 2. Le programmeur a donc
la possibilité d’utiliser tous les registres scalaires qu’il souhaite et les 32 registres vecto-
riels fournit par AltiVec. C’est cette spécificité qui a permis de développer un compilateur
C gérant l’utilisation des registres vectoriels et donc de fournir un outil de développement
facile à appréhender et à utiliser (contrairement au MMX où l’utilisation de l’assembleur
est requise [16, 13]).

17
1.2. CAS PARTICULIER : LE SWAR

1.2.3 Développer avec AltiVec


Pour illustrer les capacités d’AltiVec, nous allons considérer un problème simple, l’ap-
plication d’un filtre 1X3 sur une image en niveau de gris. L’image est stockée sous la
forme d’un tableau d’entiers 8 bits non signés Pour conserver une précision maximale,
nous utilisons un masque de filtre et un tableau d’entier 16 bits signés pour les calculs
intermédiaires. L’algorithme séquentiel appliqué s’exprime en C de la manière suivante :

unsigned char* img;


signed short m[3],* res;

for( int i=1; i < TAILLE_IMAGE-1; i++ )


{
res[i] = m[0]*img[i-1]+m[1]*img[i]+m[2]*img[i+1];
}

F IG . 1.11 – Filtre 1x3 en C ANSI classique

Pour paralléliser ce code pour l’extension AltiVec, il faut donc repenser l’algorithme
du filtre de manière "vectorielle", c’est à dire en termes d’opérations opérant sur des vec-
teurs de pixels et non plus pixel à pixel. Considérons le voisinage vectoriel d’un pixel Pi
de l’image(cf fig.1.12). A partir de ce point, on voit que l’on peut accéder aux pixels pré-
cédent (Pi−1 ) et suivant (Pi+1 ) en effectuant des décalages d’éléments au sein du vecteur.

Pi-1 Pi Pi+1
X
M0 M0 M0 M0 M0 M0 M0 M0
+
Pi-1 Pi Pi+1
X
M1 M1 M1 M1 M1 M1 M1 M1
+
Pi-1 Pi Pi+1
X M2 M2 M2 M2 M2 M2 M2 M2

= Ri

F IG . 1.12 – Principe du filtre 1x3 en AltiVec

18
1.2. CAS PARTICULIER : LE SWAR

Une fois ces opération effectuées, une simple multiplication/somme des trois vecteurs
(original,décalé à droite, décalé à gauche) nous permet de retrouver la valeur de l’image
filtrée sur ce pixel ainsi que pour l’ensemble des pixels du vecteur. L’algorithme de filtrage
"vectorisé" pour une image de 16 pixels (donc contenue dans un seul vecteur) est donné
sur la figure 1.13. Comme nous le voyons, l’utilisation d’entiers 16 bits pour effectuer des
calculs sans perte de dynamiques nous oblige à utiliser des opérations de conversions de
types vectoriels. Pour une image de plus de 16 pixels, on procède en itérant cet algorithme
sur des blocs de 16 pixels.

C0 = [M 0 ... M 0]
C1 = [M 1 ... M 1]
C2 = [M 2 ... M 2]
R1 = [P0 ... ... P15 ]

// Decalage de R1 à droite et à gauche


R0 = R1 » 1
R2 = R1 « 1

Transtyper R0 => R01 et R02.


Transtyper R1 => R11 et R12.
Transtyper R2 => R21 et R22.

// Ici nous utilisons des séries de multiplication addition pour tirer partie
// de l’instruction AltiVec vec_madd qui effectue cette opérations en un cycle.
R01 = R01*C0 + R11*C1 + R21*C2 + 0
R02 = R02*C0 + R12*C1 + R22*C2 + 0

Transtyper R01 et R02 => R13.


Ecrire R13 dans l’image résultat.

F IG . 1.13 – Algorithme du filtre 1x3 en C AltiVec

Le tableau 1.14 donne pour différentes tailles d’images les accélérations obtenues
avec un code vectorisé selon la technique précédente par rapport à un code séquentiel
"classique" (cf fig. 1.11). On remarquera que l’accélération mesurée excède la valeur
"théorique" maximale - qui est de 8 pour un algorithme opérant sur des entiers 16 bits.
Cette surlinéarité s’explique par les effets combinés du pipeline de l’Unité AltiVec, du
chargement efficace des données dans le cache et de la réorganisation du code effectué
par le compilateur [17].

19
1.2. CAS PARTICULIER : LE SWAR

Taille de l’image C séquentiel C AltiVec Accélération


16x16 3.89µs 2.03µs 1.92
32x32 12.09µs 2.87µs 4.21
64x64 48.80µs 6.27µs 7.78
128x128 209.59µs 20.05µs 10.45
256x256 854.37µs 75.99µs 11.24
512x512 3737.92µs 322.85µs 11.58
1024x1024 16253.54µs 1440.39µs 11.28

F IG . 1.14 – Performances du Filtre 1x3

Ces tests ont permis de mettre en oeuvre un certain nombre de techniques de vectori-
sation. Ces techniques sont largement approfondies dans les travaux de Lionel DAMEZ
[16].

20
Chapitre 2

Position du Problème

La technologie AltiVec s’avère être un moyen efficace d’utiliser le parallélisme de


données au sein d’applications coûteuses en termes de temps de calcul comme le traite-
ment du signal en général et le traitement d’image en particulier. Son utilisation ne va
toutefois pas se faire sans quelques difficultés. Ce chapitre va présenter les principales
difficultés et va montrer qu’elles ne peuvent être résolu facilement. Ce constat constituera
le fondement des travaux présentés au Chapitre 3.

2.1 Difficulté du développement utilisant AltiVec


2.1.1 Gestion des types
Le code C AltiVec reste lisible pour des problèmes simples. Mais, pour des algo-
rithmes plus complexes, les optimisations et les expressions dues aux pré-traitement des
données initiales rendent le code difficilement extensible. En outre, peu d’algorithmes
conservent une écriture naturelle une fois vectorisé. Reprenons le filtre 1x3 du chapitre
précédent. Si l’on décide d’utiliser un masque de filtrage flottant, nous nous retrouvons
face à un des problèmes d’AltiVec : la conversion de type. Si en C cette conversion est
transparente, ce n’est pas le cas en AltiVec. En effet, le transtypage classique du C ne
produit aucun changement du contenu d’un vector Altivec. Ce transtypage laisse in-
tact les bits du vecteur et ne gère pas les problèmes dus au changement de la taille des
éléments du vecteur. Ainsi, l’écriture d’un code comme celui de la figure 2.1 ne génère
pas du tout un vecteur de flottants correct. Il ne faut pas en effet oublier que si la taille
des vecteurs reste fixe (128 bits soit 16 octets), celle des éléments du vecteur varie. Le
transtypage d’un vecteur de 16 entiers 8 bits produit ainsi 4 vecteurs de flottants 32 bits.
Il est donc nécessaire d’écrire explicitement la transformation du vecteur d’entiers 8 bits
en 4 vecteurs de flottants 32 bits.

21
2.1. DIFFICULTÉ DU DÉVELOPPEMENT UTILISANT ALTIVEC

vector unsigned char source;


vector float f1;

f1 = (vector float)source;

F IG . 2.1 – Transtypage erroné de vecteur AltiVec.

Le principe de cette transformation est de créer à partir du vecteur de 16 entiers 8


bits deux vecteurs de 8 entiers 16 bits contenant les mêmes valeurs. Puis, à partir de ces
deux vecteurs, on génère quatre vecteurs d’entiers 32 bits qui sont ensuite transformés en
vecteur de flottants (cf fig. 2.2). Appliqué au code du filtre 1x3, cette technique accroît
significativement la complexité du code. En effet, une fois les calculs effectués, il faut à
nouveau transformer les vecteurs de flottants en vecteurs d’entiers 8 bits 1 .

vector unsigned char c;


vector unsigned short s1,s2;
vector float f1,f2,f3,f4;

// Conversion des 8 premiers éléments de c


s1 = vec_unpackh( c );
// Conversion des 8 derniers éléments de c
s2 = vec_unpackl( c );

// Conversion des 4 premiers éléments de s1


vec_st( vec_ctf( vec_unpackh(s1),0 ), 0, f1 );
// Conversion des 4 derniers éléments de s1
vec_st( vec_ctf( vec_unpackl(s1),0 ), 0, f2 );

// Conversion des 4 premiers éléments de s2


vec_st( vec_ctf( vec_unpackh(s2),0 ), 0, f3 );
// Conversion des 4 derniers éléments de s2
vec_st( vec_ctf( vec_unpackl(s2),0 ), 0, f4 );

F IG . 2.2 – Transtypage de vecteur AltiVec.

Ce probléme de transtypage se retrouve pour toutes les combinaisons de types de don-


nées AltiVec.

1
On pourra trouver dans le manuel de référence d’AltiVec[17] les détails des fonctions utilisées ici

22
2.1. DIFFICULTÉ DU DÉVELOPPEMENT UTILISANT ALTIVEC

2.1.2 Autres difficultés


On liste ici, sans les détailler plus avant, un certain nombre de point technique qui
rendent moins trivial qu’il n’y parait l’écriture de code AltiVec. Pour plus de détails on
pourra se reporter à [17] et [16].

1. Asymétrie du jeu d’instructions : un certain nombre d’instructions AltiVec n’existent


que pour un sous-ensemble des types de données gérés par l’extension. Par exemple,
les opérations de multiplications sont inexistantes pour les entiers 8 bits et 32 bits. Il
est donc nécessaire d’utiliser des constructions plus complexes, basées sur les opé-
rations de transtypage, pour permettre de telles opérations.

2. Hétérogénéité du niveau de précision : certaines instructions AltiVec opèrent avec


une faible précision. Ainsi des opérations comme le logarithme, l’exponentielle,
l’inverse ou la racine carré fournissent un résultat 32 bits avec 24 bits de précision.
Il est donc nécessaire de pallier ces imprécisions en utilisant des techniques comme
le raffinement de Newton-Raphson[17] pour obtenir un résultat à la précision vou-
lue. Le calcul de l’inverse d’un vecteur s’effectue par exemple comme ceci (cf fig.
2.3) :

vector float inverse( vector float v )


{
// rec = valeur approché de 1/v = ~(1/v)
vector float rec = vec_re(v);

// one = [1.0 1.0 1.0 1.0 ]


vector float one = vec_ctf(vec_splat_u32(1),0));

// Ce calcul utilise le raffinement de Newton-Raphson


// à l’ordre un pour évaluer une valeur exacte de 1/v.
// Avec :
// vec_madd(a,b,c) = a*b+c
// vec_nmsub(a,b,c) = c-a*b
// On obtient :
// r = ~(1/v)*( 1 - v*(1/v) ) + ~(1/v)

return vec_madd( rec,vec_nmsub( rec, v, one ),rec);


}

F IG . 2.3 – Calcul de l’inverse en AltiVec

3. Gestion du cache et du pipeline : dans le cas d’un programme écrit en C, le com-


pilateur décharge le développeur des subtilités de la gestion du pipeline et du cache

23
2.2. SOLUTION ENVISAGÉE

de données. Dans le cas d’un code C AltiVec, il est important que le développeur
prenne en charge cette gestion. En effet, une application utilisant AltiVec peut voir
ses performances doubler ou tripler lors d’un simple réarrangement d’un groupe
d’instructions AltiVec de par la grande sensibilité du cache et du pipeline. Des tech-
niques comme le dépliage de boucle ou le réorganisation des instructions au sein
d’une boucle[16] doivent être utilisées pour garantir un certain niveau de perfor-
mance. mais le bon usage de ces techniques rend le code plus long et plus difficile à
entretenir.

2.2 Solution envisagée


2.2.1 Principes
Face au difficultés soulevées plus haut, une approche possible consiste à créer une
classe C++ encapsulant un concept de vecteur parallèle similaire à celui du vector
fournie par AltiVec. Cette classe nous permettrait d’utiliser AltiVec tout en augmentant le
niveau d’abstraction du code et donc de produire du code lisible et facilement modifiable.
L’interface de cette classe peut par exemple se fonder sur celle de la classe valarray de
la bibliothèque STL[18] qui fournit un ensemble méthodes visant à manipuler des tableaux
de valeurs numériques de grande taille. La classe valarray est par ailleurs compatible
avec les nombreuses fonctions de la bibliothèque algorithm, ce qui en fait un outil pré-
cieux pour de nombreux développeurs. Nous avons donc développé dans cette première
étape de travail une classe similaire à valarray mais utilisant AltiVec pour effectuer
les calculs nécessaires. Cette classe AVector fournit un ensemble de méthode de calculs
ainsi qu’un certain nombre d’opérateurs surchargés permettant d’écrire naturellement des
opérations de manipulations de vecteurs. De part sa nature générique, la classe AVector
permet de retrouver en C++ la diversité des types de données supportés par AltiVec. Voici
par exemple le filtre 1x3 réécrit en utilisant la classe AVector.

AVector<unsigned char, TAILLE> image;


AVector<signed short, TAILLE> result;
float mask[3];

/* On charge l’image et
on remplit le masque. */

result = mask[0]*image.shift(-1) +
mask[1]*image +
mask[2]*image.shift(1);
}

F IG . 2.4 – Filtre 1x3 utilisant la classe AVector

24
2.2. SOLUTION ENVISAGÉE

Comme nous le voyons les opérateurs arithmétiques surchargés fournissent un moyen


élégant de formaliser l’algorithme du filtrage. Il est clair que le gain en lisibilité est no-
table. Reste à évaluer l’efficacité de cette approche, c’est à dire le surcoût qu’elle induit
par rapport à une implantation directe en C AltiVec. Le tableau 2.5 donne pour cela les
temps de calculs pour une implantation utilisant AVector et les accélérations correspon-
dantes par rapport à une implantation en C séquentiel du filtre décrit au chapitre précédent.

Taille de l’image Temps de calcul Gain


16x16 11.69µs 0.33
32x32 16.18µs 0.74
64x64 46.29µs 1.05
128x128 2605.43µs 0.08
256x256 3428.13µs 0.25
512x512 17349.30µs 0.21
1024x1024 75811.47µs 0.21

F IG . 2.5 – Performances du Filtre 1x3 en C++ AltiVec

Comme on peut le constater, l’efficacité de cette approche est extrêmement faible.


L’accélération par rapport à du C séquentiel reste inférieure à 2 (alors qu’elle atteint 11
dans la version C AltiVec). Dans la plupart des cas, cette accélération est même inférieure
à 1, le code vectorisé étant alors moins rapide que le code séquentiel !

2.2.2 Analyse de la situation


Les faibles performances obtenues en utilisant la classe AVector sont principalement
dues au mécanisme d’évaluation utilisé par le compilateur C++. En examinant le stan-
dard du langage C++, on s’aperçoit que lors de l’évaluation d’une expression incluant des
types pourvus d’opérateurs surchargés, ces derniers sont évalués par paires2 et provoquent
la génération de variables temporaires au fur et à mesure de l’évaluation de l’expression.

Ainsi le code source suivant (fig. 2.6) :

AVector<float,TAILLE> w,x,y,z;
z = x + y + w;

F IG . 2.6 – Expression simple utilisant une classe définie par l’utilisateur.

va se révéler équivalent à celui-ci (fig. 2.7) :

2
On parle de réduction dyadique.

25
2.2. SOLUTION ENVISAGÉE

AVector<float,TAILLE> w,x,y,z;
AVector<float,TAILLE> tmp1;
for(i=0;i<TAILLE;i++
{
tmp1[i] = y[i]+w[i];
}

AVector<float,TAILLE> tmp2;
for(i=0;i<TAILLE;i++
{
tmp2[i] = x[i]+tmp1[i];
}

for(i=0;i<TAILLE;i++
{
z[i] = tmp2[i];
}

F IG . 2.7 – Code "précompilé" du filtre 1x3 utilisant AVector.

Le nombre de variables intermédiaires augmente proportionnellement à la complexité


de l’expression. Chaque création et destruction d’une telle variable s’accompagne d’une
manipulation de l’espace mémoire3 (allocation ou libération). Le temps passé dans ces
opérations devient de fait rapidement prépondérant. Le temps perdu dans les contrôleurs
de boucles et lors des recopies d’objets intermédiaires est aussi un facteur limitant, surtout
dans le cas d’objets de grande taille.Le gain apporté par l’extension AltiVec parvient
à peine à compenser les pertes dues à ces mécanisme d’évaluations. L’approche basé
sur la simple encapsulation des fonctionnalités offertes par AltiVec apparaît donc, après
expérimentation, comme une impasse. Cet état de fait a donc motivé la recherche d’une
autre solution. La description de cette solution fait l’objet du chapitre suivant.

3
Ce qui est souvent le cas pour des classes de types tableaux numériques

26
Chapitre 3

les Expressions Templates

Comme nous l’avons vu au chapitre précédent, l’approche fondée sur la simple encap-
sulation des fonctionnalités de l’AltiVec dans une classe produit un code peu performant
à cause de la multiplication des variables intermédiaires et des boucles de traitement. La
solution passe donc par l’élimination (ou la réduction au strict minimum) de ces boucles
et de ces variables. Ainsi le code suivant :

AVector<float,TAILLE> r,a,b,c;
r = a + b + c;

devrait se compiler idéalement sous une forme équivalente à :

AVector<float,TAILLE> r,a,b,c;
for( int i=0;i<TAILLE;i++)
r[i] = a[i] + b[i] + c[i];

Cette formulation se traduirait alors immédiatement et sans surcoût (par rapport à une
implémentation manuelle) en un code C AltiVec équivalent à :

AVector<float,TAILLE> r,a,b,c;
vector float tmp;
for(int i=0;i<TAILLE/4;i++)
{
tmp = vec_add( vec_ld ( &a[4*i],0),
vec_add( vec_ld( &b[4*i],0),
vec_ld( &c[4*i],0)
));
vec_st( &r[4*i], 0, tmp);
}

27
Dans ce code, les fonctions vec_ld et vec_st sont respectivement des fonctions
de lecture et d’écriture de vecteur AltiVec et vec_add la fonction AltiVec d’addition
vectorielle.

L’idée est donc de mettre en place un mécanisme qui permettrait au compilateur de


générer automatiquement ce code optimal à partir de l’expression de départ.

La technique des Expressions Templates fournit la base d’un tel mécanisme. Cette
technique a été développée par Todd Veldhuizen[19] et avait pour but original de fournir
une alternative élégante et performante au passage d’expression sous forme de pointeur
de fonction en argument d’autres fonctions. Par exemple, la bibliothèque standard du lan-
gage C fournit des routines comme qsort() ou bsearch() dont un des arguments
est un pointeur vers une fonction définie par l’utilisateur. De nombreuses bibliothèques
fournissent des mécanismes semblables pour manipuler les fonctions comme un élément
atomique du langage. Par exemple, on peut considérer la fonction integrate qui effec-
tue le calcul de l’intégrale d’une fonction numérique sur un intervalle donné (fig. 3.1) :

double f( double x)
{
return (x/(1.0+x));
}
double integrate( double (*f)(double), double inf, double sup)
{
static const double dx = 0.01;
double r = 0.0;
for( double x=inf;x<sup;x+= dx )
r += dx*f(x);
return r;
}

int main ()
{
double i = integrate(f,0,10);
}

F IG . 3.1 – Fonction integrate utilisant un pointeur de foncton

Le problème de cette approche par pointeur est son inefficacité, surtout si les opéra-
tions effectuées au sein de la fonction sont simples. En effet, il est nécessaire d’effectuer
un déréférencement de pointeur et un appel de fonction à chaque itération de l’algorithme
utilisé par la fonction integrate. Le principe des Expressions Templates est de per-
mettre l’utilisation d’expressions comme argument de fonction et de dérouler automati-
quement le code nécessaire à l’évaluation de ces expressions au sein de la fonction appe-

28
lante au moment de la compilation. Avec cette approche, le code de la figure 3.1 se réécrit
ainsi :

int main ()
{
Var<double> x;
double i = integrate(x/(1.0+x),0,10);
}

F IG . 3.2 – Exemple simple d’Expressions Templates.

Le compilation de la fonction integrate produit alors un code dans lequel le code


correspondant à la fonction à intégrer est déroulé ("inliné") en place (fig. 3.3) :

//Intégrale d’une fonction entre 0 et 100


double r=0.0;

for(int x=0;x<100;x+=0.1)
r += 0.1*(x/(1.0+x));

F IG . 3.3 – la fonction integrate "déroulée"

Pour réaliser cela, la technique des Expressions Templates consiste à examiner à la


compilation le contenu syntaxique d’une expression et à utiliser ces informations pour
générer un code qui est l’exacte traduction de cette syntaxe. Ce principe repose sur celui
de l’évaluation partielle du code à la compilation [20] qui consiste à déporter les calculs
et les opérations sur les structures de données et les données statiques à la compilation
pour n’effectuer que les calculs et opérations sur les données dynamiques à l’exécution.
Pour mieux comprendre cette technique, nous allons reprendre l’exemple de la fonction
integrate vue ci-dessus (cf fig. 3.2).

Dans cet exemple, l’expression à intégrer est la suivante :

x/(1.0+x)

Au niveau du compilateur, une telle expression se représente par un arbre de syntaxe


abstraite (Abstract Syntax Tree ou AST) :

29
_..

x +

x 1

F IG . 3.4 – Arbre syntaxique de x/(1+x)

Dans cette représentation les noeuds représentent les opérations à effectuer sur les
noeuds fils, les feuilles représentent les données et les constantes sur lesquelles portent ces
opérateurs. Pour une expression donné, les noeuds et leur agencement sont fixes et connus
à la compilation alors que les valeurs portées par les feuilles sont en général connues à
l’exécution. On peut ainsi dire que la forme de l’arbre syntaxique correspondent à la par-
tie statique de l’expression et que les feuilles correspondent à la partie dynamique de cette
expression comme illustrée sur la figure 3.5 :

_..

x
+

x 1

F IG . 3.5 – Vue éclatée de l’arbre syntaxique de x/(1+x)

Sur ce schéma, nous avons représenté en rouge les parties statiques de l’expression
x/(1+x) et en bleu, les parties dynamiques de cette expression. La technique d’opti-
misation présentée dans ce chapitre s’appuie sur cette dualité statique/dynamique. Elle
décompose l’évaluation d’une expression en deux phases :

1. une évaluation statique (à la compilation) pendant laquelle la structure de l’AST est


analysé et où l’on génère un code optimisé permettant l’évaluation de l’expression
pour un jeu de données dont les valeurs sont inconnues.

30
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE

2. une évaluation dynamique (à l’exécution) au cours de laquelle les valeurs des don-
nées de l’expression sont fournies au code produit précédemment.

L’interêt d’une telle approche -appelée évaluation partielle hors-line [20]- provient du
fait que le travail d’analyse et d’optimisation effectuer à la compilation n’a plus à être
effectué à l’exécution, d’où un gain de temps d’exécution qui peut être significatif lorsque
l’expression évaluée comporte de large parties statiques.

3.1 Technique d’évaluation partielle


La mise en oeuvre de la technique d’évaluation partielle ébauchée au paragraphe pré-
cédent suppose que l’on soit capable de récupérer et de manipuler l’ AST d’une expression
au niveau du langage source. En C++, cette manipulation n’est possible qu’en représen-
tant l’AST sous la forme d’un type et en utilisant le mécanisme des templates.

3.1.1 Rappel sur les templates


Les templates furent créés dans le but de permettre l’écriture de code générique et fa-
cilement réutilisable. Par exemple, une fonction calculant le minimum de deux variables
de types quelconques peut s’écrire :

template<class T> T min( T& a, T& b)


{
return ( a<b ? a : b );
}

F IG . 3.6 – Une fonction template simple : min

Dans cette écriture T représente un argument de type alors que a et b sont des argu-
ments de fonction classiques. Cette fonction min sera utilisable sur les types de données
atomiques et sur les types de données définis par l’utilisateur dés lors que ce type fournit
une surcharge de l’opérateur "<". Il est alors possible d’instancier cette fonction géné-
rique en lui fournissant des données de types diverses :

Il faut aussi noter que dans la majorité des cas, l’instanciation d’une fonction tem-
plate génère non pas un appel vers une fonction mais un code inline directement réécrit
au point d’appel de la fonction template. Ce mécanisme allège considérablement le coût
due à la généricité et permet, à l’instar des macros, de produire du code à partir de divers
paramètres au moment de la compilation. A partir de cette utilisation de base, diverses
techniques ont vu le jour et ont permis de mettre au point des fonctions et des classes

31
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE

double r1;
int r2;
char r3;

r1 = min( 3.57, 2.55 );


r2 = min( 4512, 9433 );
r3 = min( ’c’, ’f’ );

F IG . 3.7 – Utilisation d’une fonction template

templates permettant de générer du code "inline" très performant [21] ou de mettre en


place des équivalents statiques des structures de contrôles classiques. On parle alors de
meta-programmation par templates[22].

3.1.2 Application à l’évaluation partielle


Dans notre cas, les techniques de meta-programmation par templates permettent de
mettre en place un système simple de construction d’AST. Si l’on revient à une définition
purement grammaticale1 des expressions arithmétiques, on peut ainsi définir un ensemble
réduits de règles pouvant décrire une expression arithmétique quelconque :

val := ID | CONST
OP := + | - | * | /
expr := val | expr OP expr

F IG . 3.8 – Grammaire simplifiée des expressions arithmétiques

A partir de ces règles de grammaire, il est possible de définir un certain nombre de


types permettant de représenter au sein du langage la structure même d’un AST. Pour
cela, on code la structure de l’arbre au sein d’un type de donnée. On définit alors trois
types de classes :

1. Les classes décrivant la topologie de l’AST.

L’unité de base de l’AST est le noeud. Dans notre exemple simplifié, chaque noeud
possède un fils droit, un fils gauche et un opérateur décrivant l’opération associée au
noeud. Cette topologie est reprise telle quelle dans la classe BinaryNode (fig. 3.9).
Les paramètres templates de BinaryNode reflètent sa nature de noeud binaire.
En effet, les paramètres de type L et R représentent le contenu respectivement des
1
Au sens grammaire des langages de programmation

32
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE

fils gauche et droit du noeud de l’arbre binaire et le type OP représente le type de


l’opération effectuée au sein du noeud.

template<class T, class L, class R, class OP>


class BinaryNode
{
public :
BinaryNode( L& l, R& r ) : _l(l),_r(r) {}
T eval( T x ) const
{ return OP::apply(_l.eval(x),_r.eval(x)); }
private :
L _l;
R _r;
};

F IG . 3.9 – Classe template BinaryNode

Par exemple, le noeud représentant la sous-expression "1+x" a pour type :

BinaryNode<T,Const<T>,Var<T>,OpAdd>
Pour faciliter la gestion de tel noeud, on utilise ici une technique dite de template
anonyme. Cette technique permet d’encapsuler au sein d’un template un type d’une
complexité arbitraire. Ainsi les types L et R peuvent en fait être eux même des type
insatncié à partir de BinaryNode.

En se basant sur la règle de grammaire expr := val | expr OP expr, il est


alors possible d’introduire une classe permettant de manipuler une sous-expression
quelconque. Ainsi, la classe Expr (fig. 3.10) sert de modèle pour l’ensemble des ex-
pressions et sous-expressions d’un AST. Ses deux paramètres templates permettent
de spécifier le type de données manipulé (paramètre T) et le type de l’expression en
elle même (paramètre E).

template<class T, class E> class Expr


{
public :
Expr( E& xpr) : _xpr(xpr) {}
T eval( T x ) const {return _xpr.eval(x); }
private :
E _xpr;
};

F IG . 3.10 – Classe template Expr

33
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE

La construction des types correspondants à une expression quelconque peut alors


être effectuée. Ainsi, l’expression donnée en exemple ci dessus (x/(1+x)) s’encode
de la manière suivante (fig. 3.11) :

_..

Var +
Const Var

Expr<BinaryNode<Const,Var,OpAdd> >

Expr<BinaryNode<Var,Expr< >,OpDiv> >

x/(1+x) => Expr<BinaryNode<Var,Expr<BinaryNode<Const,Var,OpAdd>>,OpDiv>>

F IG . 3.11 – Reconstruction du type associé à x/(1+x)

Il est évident que la génération de tel type de donnée ne doit pas être laissé à la
charge de l’utilisateur final. Nous verrons plus loin comment gérer ces construc-
tions.

Autre point important, la présence de la méthode eval dans les interfaces des deux
classes. Cette méthode est le point d’entrée qui sera utilisé par le compilateur pour
produire le code inline idéal et évaluer les aspects dynamique de l’expression. Les
détails de cette méthode seront donnée plus loin.

34
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE

2. Les classes décrivant les opérations au noeuds de l’AST.

Ces classes definissent un type pour chaque opérateur arithmétique utilisable dans
l’AST. Par exemple la classe OpAdd représente l’utilisation de l’opérateur + au sein
d’une expression (cf fig. 3.12) :

template<class T> struct OpAdd


{
static inline T apply( T l, T r ) { return l+r; }
};

F IG . 3.12 – Classe template OpAdd

On définit de même les classes OpSub,OpDiv, etc ... pour chaque type d’opéra-
teur arithmétique. Il est aussi possible d’étendre ces classes pour leur permettre de
représenter n’importe quelle fonction numérique comme le logarithme ou la racine
carré. Grâce aux propriétés d’ inlining implicite des templates, le code de la mé-
thode apply se retrouve réinjecté au point même de son appel.

3. Les classes décrivant les données à évaluer à l’exécution.

Ces classes servent de marqueur (ou placeholder) au sein de notre représentation


de l’arbre et se positionnent à l’emplacement d’une donnée de type dynamique qui
devra être évaluée à l’exécution. Dans un premier temps, il est suffisant de considé-
rer deux classes : la classe Var qui va représenter les données variables (fig. 3.13) :

template<class T> struct Var


{
Var() {}
T eval( T x ) const { return x; }
};

F IG . 3.13 – Classe template Data

et la classe Const qui va représenter une donnée de type constante (fig. 3.14) :

35
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE

template<class T> class Const


{
public:
Const ( T& val) : _v(val){}
T eval( T x ) const { return val; }
private:
T _v;
};

F IG . 3.14 – Classe template Const

Ces deux classes exposent dans leur interface la méthode eval décrite plus haut.

A partir de l’ensemble de ces classes et de leurs différentes spécialisations, il est re-


lativement simple de fournir un mécanisme de construction de type pour des expressions
arithmétiques. Une simple surcharge des opérateurs arithmétiques du C++ permet à l’uti-
lisateur d’écrire simplement des expressions et de générer le code statique correspondant.
Grâce à ces opérateurs, il est possible de décrire de façon simple et lisible des expressions
tout en permettant leur encodage automatique sous la forme d’un type C++. Bien sur, il
est nécessaire de fournir une version de chaque opérateur (+,-,*,/) pour chaque configura-
tion possible des noeuds de l’arbre correspondant aux règles explicitées sur la figure 3.8.
Ainsi, l’opérateur + appliqué à des variables de types Var<T> s’exprime ainsi (fig. 3.15) :

template<class T>
Expr< BinaryNode<Var<T>,Var<T>,OpAdd<T>>>
operator+( const Var<T>& l, const Var<T>& r )
{
typedef BinaryNode<Var<T>,Var<T>,OpAdd<T>> expr_t;
return Expr<T,expr_t>( expr_t(l,r) );
}

F IG . 3.15 – Opérateur + générateur de donnée statique

Si l’on se dote de l’ensemble des opérateurs classiques, l’écriture

Data<double> x;
integrate( x/(1+x), 0,10);

se résout alors ainsi :

Data<double> x;
integrate( operator/( x, operator+( 1, x ) ), 0,10);

36
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE

3.1.3 Evaluation des données dynamiques


A partir de ces mécanismes de reconstruction, on obtient donc à la compilation une
instance de la classe Expr qui contient l’ensemble des informations de structures néces-
saires à l’évaluation de l’expression associée. L’évaluation proprement dite de cette ex-
pression utilise la méthode eval fournie par chacune des classes utilisées précédemment
pour décrire les données statiques de l’expression. Cette méthode à pour but de calculer
une valeur de chaque variable de l’expression. La fonction integrate devient donc :

template<class E>
double integrate( Expr<E>& expr, double s, double e )
{
static const double dx = 0.01;
double r = 0.0;
for(int x=s;x<e;x+=dx) r += dx*expr.eval(x);
return r;
}

F IG . 3.16 – Code de la fonction integrate

Lors de la compilation, l’appelle de la méthode eval de l’objet Expr<E> va déclen-


cher une série de résolution d’appel de fonction template et donc produire un code inline
de manière récursive (fig. 3.17).
expr._xpr.eval(x)
=> BinaryNode<T,Data<T>,Expr<E>,OpDiv>.eval(x)
=> OpDiv::apply( _l.eval(x),_r.eval(x) )
<= _l.eval(x)
=> x;
<=_r.eval(x)
=> BinaryNode<T,Data<T>,Const<T>,OpAdd>.eval(x)
=> OpAdd:apply( _l.eval(x),_r.eval(x) );
<= _l.eval(x)
=> x;
<= _r.eval(x)
=> 1;
=> 1+x
=> x/(1+x);

F IG . 3.17 – Chaîne d’évaluation d’une expression

Dans ce schéma les symboles => et <= représentent respectivement l’appel et la ré-
duction d’un niveau de la récursion.

37
3.2. APPLICATION

Une fois cette réduction effectuée à la compilation, le code permettant de calculer


expr(x) est déroulé automatiquement à l’emplacement précis où cette évaluation est de-
mandée. Le code final ne contient donc aucun appel de fonction. Sur cette exemple, ce
code final est donnée sur la figure 3.3.

3.2 Application
la technique de méta-programmation par templates présentée au paragraphe précédent
peut s’appliquer à la manipulation et au calcul sur des vecteurs numériques [19, 22] et
en particulier à des vecteurs basés sur les types AltiVec. Elle permet alors de pallier à la
majeure partie des inconvénients mis en évidence au paragraphe 2.2.2.

Afin de permettre une utilisation simple des Expressions Templates au sein d’une
classe de vecteur numérique, il faut modifier à la fois l’interface classique d’une cette
classe vecteur et les différents composants du mécanisme d’expressions. En fournissant
une version spécialisé de l’opérateur d’affectation pour la classe vecteur, on reconstruit
un mécanisme similaire à celui de la méthode eval. Plus précisément, on peut concevoir
un squelette de classe qui encapsule ces principes (fig. 3.18) :

template<class T> class Vecteur


{
public:
template<class E> Vecteur<T>& operator=( const Expr<T,E>& xpr )
{
for( int i=0;i<_t;i++) _data[i] = xpr.eval(i);
return *this;
}

T* begin() { return _data; }


private:
int _t;
T* _data;
};

F IG . 3.18 – Squelette d’une classe mêlant vecteur numérique et E.T

Dans cette classe minimale, l’opérateur d’affectation joue alors un rôle équivalent à ce-
lui de la méthode eval. Il prend comme argument une instance de la classe Expr<T,E>
qui contient l’expression que l’on désire évaluer et affecter au vecteur. Le code de cet opé-
rateur se déroule de la même manière que le code de la fonction integrate vu plus haut
et génére en particulier un code inline. La principale différence avec la présentation faite
précédemment consiste à remplacer les classes placeholder par des itérateurs pointant

38
3.2. APPLICATION

vers les données contenus dans un tableau numérique. On reformule la méthode eval, la
classe var et les opérateurs arithmétique de la manière suivante :

1. La méthode eval utilise un paramètre supplémentaire qui définit l’indice de l’élé-


ment à évaluer. Par exemple, la classe BinOp devient :

template<class T, class L, class R, class OP>


class BinaryNode
{
public :
BinaryNode( L& l, R& r ) : _l(l),_r(r) {}
T eval( int i ) const
{ return OP::apply(_l.eval(i),_r.eval(i)); }
private :
L _l;
R _r;
};

F IG . 3.19 – Classe template BinaryNode modifiée

2. La classe Var sert désormais de conteneur pour les itérateurs sur les données nu-
mériques (fig. 3.20) :

template<class T> class Var


{
public:
Var( T* val) : _data(val) {}
T eval( int i ) const { return _data[i]; }

private:
T* _data;
};

F IG . 3.20 – Classe template Var modifiée

De même, la classe Const subit le même type dez modification :

39
3.2. APPLICATION

template<class T> class Const


{
public:
Const( T val) : _data(val) {}
T eval( int ) const { return _data; }

private:
T _data;
};

F IG . 3.21 – Classe template Const modifiée

3. Les opérateurs arithmétiques utilisent désormais directement des instances de la


classe Vecteur pour construire les expressions (fig. 3.22 :

template<class T>
Expr< BinaryNode<Data<T>,Data<T>,OpAdd<T>>>
operator+( const Vecteur<T>& l, const Vecteur <T>& r )
{
typedef BinaryNode<Data<T>,Data<T>,OpAdd<T>> expr_t;
return Expr<T,expr_t>( expr_t(Data(l.begin()),Data(r.begin())) );
}

F IG . 3.22 – Opérateur + modifié

Nous disposons désormais de l’ensemble des techniques nécessaires pour développer


une classe de vecteur numérique utilisant l’extension AltiVec de manière performante.
Nous allons donc aborder dans le chapitre suivant les détails de conception et de fonc-
tionnement d’une telle classe.

40
Chapitre 4

La bibliothèque E.V.E

L’ensemble des travaux issus des techniques présentées au chapitre 3 a permis la réa-
lisation d’un bibliothèque de gestion des tableaux numériques utilisant les fonctionnalités
d’AltiVec de manière efficace. Cette bibliothèque nommée E.V.E 1 est décrite dans ses
grandes lignes dans ce chapitre.

4.1 Présentation de l’interface


L’interface de E.V.E a été conçue pour permettre une utilisation intuitive et naturelle
des fonctionnalités offertes par AltiVec. Pour ce faire, cette interface reprend le schéma
des classes et fonctions de la STL2 afin de permettre un portage simple d’applications
existantes et de fournir un cadre familier aux développeurs. Le but de ce chapitre est
de présenter comment E.V.E peut s’utiliser au sein d’une application et quels sont les
différents niveaux de fonctionnalités que la bibliothèque propose.

4.1.1 Mise en place de E.V.E


La bibliothèque E.V.E est utilisable via un nombre restreint de fichiers en-têtes. Pour
utiliser E.V.E au sein d’un programme, il suffit donc d’inclure ces fichiers dans les sources
de l’application. Par la suite, l’ensemble des classes et fonctions de E.V.E sont accessibles
via le namespace eve. Un programme minimaliste utilisant E.V.E se présente alors ainsi
(fig. 4.1) :

1
acronyme de Expressive Velocity Engine.
2
Standard Template Library
4.1. PRÉSENTATION DE L’INTERFACE

// En-têtes strictement nécessaires.


#include <EVE/eve.h>
#include <EVE/array.h>

// utilisation du namespace eve


using namespace eve;

// Le reste de l’application

F IG . 4.1 – Initialisation de E.V.E

E.V.E supporte toutes les directives de compilations de la STL et se comporte de ma-


nière standard vis à vis de ces directives. Pour plus de details, on peut se reporter à [18].

Au niveau de la compilation, un certain nombre d’options sont à utiliser. Pour les


compilateurs de la famille GCC 3.x, les options suivantes doivent être ajoutées au options
classiques de compilations :

-faltivec -ftemplate-depth-512 -finline-functions -finline-limit=1000

F IG . 4.2 – Option de compilation de E.V.E

Sur certaines machines, l’option -finline-limit=1000 peut conduire à des temps


de compilations extrêmement long. Il est possible de diminuer ce temps de compilation
en utilisant des valeurs de 500 à 200 3 .

4.1.2 Fonctionnalités de base


E.V.E repose principalement sur la classe array, pendant de la classe valarray
dela STL, qui encapsule le concept de tableaux numériques. La classe array permet la
création et la manipulation de tableaux de dimension 1 ou 2 et de taille fixe, connue à la
compilation et supporte un nombre restreint de type de données :

bool, float, (un)signed int, (un)signed short, (un)signed char.

Les schémas suivants sont alors possibles (fig. 4.3) :

3
On peut noter ici que le manuel de GCC 3.1 nous encourage à utiliser des valeurs proches de 50000 dans le cas d’un
utilisation poussée des templates. Des tests effectués avec une telle valeur en paramètres conduits à une compilation de
plusieurs minutes pour une simple différence entre deux tableaux et plus de vingt-cinq minutes pour une convolution
par un masque 7x7. Il est donc nécessaire de bien considérer le rapport temps de compilation/gain final.

42
4.1. PRÉSENTATION DE L’INTERFACE

#include <vector>
#include <EVE/eve.h>
#include <EVE/array.h>
using std::vector;
using namespace eve;

float tab_classic[64];
vector<signed short> stl_vector(128);

// Déclaration d’un tableau de 15 flottants


array<float,15> tab1;

// Déclaration d’un tableau de 256*256 entiers 8 bits.


array<signed char,256,256> tab2;

// Déclaration d’un tableau de 1000 bool initialisé à false


array<bool,1000> tab3(false);

// Déclaration d’un tableau de 7*7 256*256 entiers 8 bits.


// initialisé à partir de tab3
array<signed char,256,256> tab4(tab3);

// Déclaration d’un tableau de 256*256 entiers 8 bits


// initialisé à partir d’une expression de array
array<signed char,256,256> tab5(2*tab2-tab4);

// Déclaration d’un tableau de 8*8 flottants


// initialisé à partir d’un tableau C classique
array<float,8,8> tab6(tab_classic);

// Déclaration d’un tableau de 128 entiers 16 bits


// initialisé à partir d’un vector de la STL
array<signed short,128> tab7(stl_vector.begin());

F IG . 4.3 – Utilisation de array

La classe array fournit aussi une surcharge pour les opérateurs arithmétiques et lo-
giques classiques. Les opérateurs =, +, -, /, *, <<, >>, !, &, |, ~, ^, +=, -=, *=, /=, &=,
|=, ^=, <<=, >>= sont présents et surchargés de façon à générer un code utilisant de ma-
nière efficace les fonctionnalités AltiVec. Tous ces opérateurs prennent en charge les opé-
rations entre array et des types scalaires. Sont aussi surchargés certaines fonctions de
l’en-tête math.h, à savoir abs, ln, log, exp, trunc, round, ceil, floor, sqrt,
min, max, average. Toutes ces fonctions opèrent élément par élément et conservent
leur caractère binaire ou unaire. Par exemple :

43
4.1. PRÉSENTATION DE L’INTERFACE

#include <EVE/eve.h>
#include <EVE/array.h>
using namespace eve;
array<unsigned char,512,512> x,y,z,a,b,c;

// Pour tout i,j de 0 à 511 :

// z(i,j) = a(i,j) = b(i,j) = c(i,j) = 4;


z = a = b = c = 4;

// x(i,j) = (2*a(i,j)+b(i,j))*ln(c(i,j))+2
x = (2*a+b)*ln(c)+2;

// y(i,j) = (a(i,j)-2*b(i,j))/sqrt(c(i,j))
y = (a-2*b)/sqrt(c);

// z(i,j) = z(i,j) + 3*min((a(i,j),b(i,j)])


z += 3*min(a,b);

F IG . 4.4 – Fonctions et opérateurs de array

array propose aussi un ensemble d’opérateurs et de fonctions dédiées aux tests boo-
léens. Les opérateurs &&,||,==,!=,<=,>=,> et < permettent d’effectuer des tests élé-
ment par élément entre deux array et renvoient un array<bool> comme résultat.
Pour effectuer des test booléens utilisables dans des structures de controles classiques, il
est nécessaire d’utiliser les fonctions suivantes :

for_all exists

Ces fonctions permettent donc d’effecteur des tests entre vecteur et/ou entre expres-
sion :

if(exists(v1 > v3)) puts("condition 1 remplie") ;

if(for_all(v1 == v2)) puts("condition 2 remplie") ;

Dans cette exemple, la phrase "Condition 1 remplie" s’affiche si et seulement si au


moins un élément du vecteur v1 est supérieur à son correspondant dans le vecteur v3.
C’est à dire si ∃ i, v1 [i] > v3 [i]. La phrase "Condition 2 remplie" s’affiche si tous les
éléments de v1 sont égaux à ceux de v2.

44
4.1. PRÉSENTATION DE L’INTERFACE

La classe array fournit aussi des méthodes de calcul inter-éléments. Ces méthodes
permettent de déterminer respectivement la somme des éléments du tableau, leur mini-
mum, leur maximum.

array<float,16,16> v;
float r,m;

// Somme des éléments de v


r = v.sum()

// Minimum & maximum de v


m = v.min()
m = v.max()

// Moyenne des éléments de v


r = v.average()

F IG . 4.5 – Fonctions inter-éléments

Grâce à l’ensemble de ces opérateurs et fonctions, il est possible d’implanter un cer-


tain nombre d’algorithme simple. La figure 4.6 donnent l’exemple de deux algorithmes
de traitement d’image écrits grâce à E.V.E :

array<unsigned char,512,512> res,img1,img2;

// Inversion d’une image


res = ~img1;

// Différence d’image
res = img2 - img1;

F IG . 4.6 – Exemple d’utilisation de E.V.E

Enfin, la classe array fournit un ensemble de mécanisme permettant d’initialiser


simplement un vecteur ou une matrice. Tout d’abord, il est possible d’utiliser des listes
d’initialisation explicite :

Si la taille de la liste est inférieure à celle du tableau, les éléments non initialisées sont
laissés en l’état. Si la liste est plus grande que le tableau, une erreur de compilation se
produit.

la classe array supporte aussi l’initialisation par équation d’indice. L’en-tête EVE/util.h

45
4.2. ALGORITHMES ET PRIMITIVES DE PLUS HAUT-NIVEAU

array<float,7> mask;
array<unsigned char,3,3> img;

mask = 0.25, 1, 0.25;

img = 1, 5, 1,
9, 7, 9,
1, 5, 1;

F IG . 4.7 – Liste d’initialisation de array

fournit deux type : first_index et second_index qui représentent respectivement


les indices verticaux et horizontaux de array. Ces deux types peuvent être manipulées
comme des éléments arithmétiques au sein d’une expression. Les écritures suivantes sont
alors possibles :

array<unsigned float,3,3> A;
array<unsigned char,16> B;
util::first_index i;
util::second_index j;

// A(i,j) = 1/(i+j) pour i,j=0..2


A = 1/i+j;

// B(i,j) = 3*exp(i)+1 pour i=0..15


B = 3*exp(i)+1;

F IG . 4.8 – array et la notation indicielle

L’utilisation d’un second_index dans une expression affectée à un tableau mono-


dimensionel provoque un erreur de compilation.

4.2 Algorithmes et primitives de plus haut-niveau


L’ensemble des algorithmes exprimables grâce à E.V.E ne se limitent pas aux quelques
exemples données au paragraphe précédent. E.V.E propose toute une série de fonctions
plus évoluées qui permet d’exprimer plus facilement certains types d’algorithmes. Ces
fonctions et classes sont regroupées dans l’en-tête EVE/algorithm.h et sont optimi-
sées pour gérer certains cas particuliers de manipulations de tableaux numériques :

1. La fonction where permet d’effectuer une mise à jour sélective d’un tableau à par-
tir de l’évaluation d’un prédicat P, c’est à dire une expression dont l’évaluation

46
4.2. ALGORITHMES ET PRIMITIVES DE PLUS HAUT-NIVEAU

retourne un tableau de booléen. Son prototype est le suivant :

template <class T,class E1,class E2,int W,int H>


inline boolean_evaluator<T,E1,E2,W,H>
where( array<bool,W,H>& pred, expr<T,E1>& xpr1, expr<T,E2>& xpr2 );

F IG . 4.9 – Prototype de la fonction where

Lorsque la création d’un vecteur peut s’exprimer de la manière suivante :


(
e1 [i] si P[i]
v[i] =
e2 [i] sinon
Cet algorithme est transcrit en une version AltiVec optimisée et s’utilise de la ma-
nière suivante :

array<signed char,512,512> img,res;


signed char seuil = 140;

// Binarisation d’une image


res = where( img > seuil, 0, 255);

// Selection par expression


res = where( abs(img) >= 1, 1, 2-abs(img) );

F IG . 4.10 – Exemple d’utilisation de la fonction where

2. La fonction fold implante la primitive de reduction présenté au chapitre 1. Elle


permet d’effecteur des réductions d’opérations sur le contenu d’un array. Son
prototype est le suivant :

template <class O, class T, int W, int H>


T fold( array<T,W,H>& source, const T& zero );

F IG . 4.11 – Prototype de la fonction fold

fold prend donc en argument le tableau à réduire et la valeur initiale de la ré-


duction. L’opérateur à utiliser lors de la réduction est passé en paramètre template.
Les opérateurs utilisables comme argument de fold sont accessibles par l’en-tête
EVE/util.h. Il est possible de créer ces propres opérateurs utilisable par fold
en suivant le modèle fournit par la classe util::op_exemple.

47
4.2. ALGORITHMES ET PRIMITIVES DE PLUS HAUT-NIVEAU

La fonction fold s’utilise alors comme ceci :

array<signed char,512,512> img1;


signed char m,s;

// Produit des élément d’un tableau


m = fold<util::multiplies>( img1, img1[0] );

// Somme des éléments d’un tableaux


s = fold<util::plus>( img1, 0 );

F IG . 4.12 – Exemple d’utilisation de la fonction fold

3. Les fonctions de filtrages


E.V.E fournit un ensemble de fonctions destinées au filtrage de signaux 1D ou 2D.
Deux manières de procédés sont fournies :

– Une fonction de filtrage générique : filter.


cette fonction prend en argument un tableau quelconque et un tableau monodi-
mensionel servant de masque de convolution. filter effectue alors un produit
de convolution entre le tableau de données et le masque et produit un tableau ré-
sultat contenant le produit de convolution souhaitée.

array<unsigned char,256,256> tab;


array<unsigned char,1,3> mask;

mask = 1,2,1;
tab = filter(tab,mask);

F IG . 4.13 – Exemple d’utilisation de filter

– Des fonctions de filtrage spécialisés : filter_h_x et filter_v_x.


E.V.E fournit une famille de classes permettant de générer de sproduite de convo-
lutions à coefficients constants. Leur utilisation doit être préférée à celle de la
fonction filter dans les cas ou les composants du masque de convolution sont
constants. Les classes fournit sont :

// Filtres horizontaux
filter_h_3 à filter_h_15

// Filtres verticaux
filter_v_3 à filter_v_15

48
4.3. UTILISATION CONJOINTE DE E.V.E ET DE LA STL

Leur utilisation est très simple :

array<unsigned char,256,256> tab;


filter_h_3<1,2,1> gauss_x;
filter_v_3<-1,0,1> grad_y;

tab = gauss_x(tab);
tab = grad_y(tab);

F IG . 4.14 – Exemple d’utilisation des filtres constants

Les versions ultérieures de E.V.E permettront de générer un code optimisées pour la


composition des filtres ainsi qu’un support pour les filtres NxM.

4.3 Utilisation conjointe de E.V.E et de la STL


E.V.E n’a pas pour but de se substituer entièrement à la STL mais de fournir une
implantation optimisée pour certaines de ses fonctionnalités. Plusieurs fonctionnalités de
E.V.E sont en fait prévues pour permettre une inter-opérabilité entre ces deux biblio-
thèques.

4.3.1 Gestions des itérateurs


La classe array fournit un ensemble de méthodes qui permet d’utiliser des itérateurs
compatibles avec les itérateurs fournis et attendus par la STL. Les méthodes begin,
end et position permettent d’obtenir un itérateur utilisable dans d’autres fonctions de
la STL non surchargées par E.V.E. Un exemple d’utilisation est par exemple le tri d’un
array :

array<unsigned char,256,256> tab;


array<unsigned char,256,256>::iterator it;
//tab est rempli de valeur aléatoire.
std::fill( res.begin(), res.end(), rand() );

// tab est trié par std::sort via ces itérateurs


std::sort( res.begin(), res.end() );

// Récupération de l’itérateur sur le 25e élément de tab


it = tab.position(25);

F IG . 4.15 – Exemple d’utilisation des itérateurs de E.V.E.

49
4.3. UTILISATION CONJOINTE DE E.V.E ET DE LA STL

4.3.2 Fonctions classiques de la STL


E.V.E surcharge les fonctions classiques de la STL, à savoir :

partial_sum inner_product
count_if count
adjacent_difference

Ces fonctions prennent directement un array en paramètre et effectuent leur calcul


sur l’ensemble des données contenues dans ce dernier. Ces opérations sont optimisées
de manière à tirer partie d’AltiVec (fig. 4.16). Leurs versions utilisant des itérateurs sont
aussi utilisable mais ces dernières ne peuvent pour des raisons techniques être totalement
optimisées. Il en est de même avec les fonctions de la STL non présentées ici [18] : elles
ne peuvent être utilisées directement sur un paramètre de type array mais sont utilisable
par l’intermédiaire des itérateurs fournis par les méthodes vus au paragraphe précédent.

util::first_index i;
util::second_index j;
array<unsigned char,4,4> res,x,y,z;
res = i+j;

// Version optimisée AltiVec


x = adjacent_difference(res);

// Versions non-optimisées
y = adjacent_difference(res.begin(),res.end());
copy( z.begin(), z.end(), y.begin(), y.end() );

F IG . 4.16 – Utilisation conjointe de EVE et des fonctions de la STL.

4.3.3 E.V.E et les flux standards


E.V.E se comporte de manière standard vis à vis des classes de flux de la STL. Les
opérateurs << et >> sont surchargées pour toutes les classes de l’en-tête iostream et
fstream. Le code suivant est donc parfaitement valide :

50
4.3. UTILISATION CONJOINTE DE E.V.E ET DE LA STL

std::ifstream file_in("in.txt");
std::ofstream file_out("out.txt");

util::first_index i;
util::second_index j;
array<unsigned char,4,4> res;

// res(i,j) = i+j pour i,j=0..3


res = i+j;

std::cout << res << std::endl;

file_in >> res;


where( res >127, 0, 255 );
file_out << res;

F IG . 4.17 – Utilisation conjointe de EVE et des flux standards.

Lors de l’utilisation directe de << et >>, les données contenues dans un array sont
écrites ou lues sur le flux de manière brute. Pour lire ou écrire ces valeurs de manière plus
lisibles, on peut utiliser les méthodes print_on et read_from.

#include <iostream>
#include <EVE/eve.h>
#include <EVE/array.h>

using namespace eve;

// Sortie sur le flux standard.


res.print_on(std::cout);

// Lecture sur le flux standard.


res.read_from(std::in);

F IG . 4.18 – Méthodes print_on et read_from.

Les sorties respectives des exemples des figures 4.17 et 4.18 sont données ici :

51
4.3. UTILISATION CONJOINTE DE E.V.E ET DE LA STL

Sortie figure 4.17


0 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6

Sortie figure 4.18


array :
size = 4x4
content =
[ 0 1 2 3
1 2 3 4
2 3 4 5
3 4 5 6 ]

F IG . 4.19 – Comparaison print_on et cout.

52
Chapitre 5

Performances de E.V.E

5.1 Opérations élémentaires


Ces algorithmes sont des exemples simples de calculs appliqués au traitement d’images.
L’accélération est mesurée entre le code C séquentiel et le code utilisant la bibliothèque
E.V.E. Le protocole utilisé consiste à exécuter le programme test sur une image de réfé-
rence pour un grand nombre d’itérations (généralement 10000) et d’effectuer une moyenne
des accélérations mesurées. L’ensemble des exécutables furent compilés sur une machine
PPC G4 2x1.2GHz pourvue de 768 Mo de mémoire vive grâce à gcc 3.3. les codes utili-
sant E.V.E furent compilés avec les options présentés au chapitre 4 et les codes séquentiels
ont étét compilés avec l’option -O3.

5.1.1 Principes
Inverseur d’image

L’inversion d’une image consiste à remplacer chaque pixel de l’image original par
son complément à 2 dans l’image de sortie. Pour des images en 256 niveau de gris, cette
opération revient à calculer la différence entre 255 et la valeur du pixel courant.

Soustraction de deux images

Cette opération effectue la soustraction des valeurs des pixels de deux images.
5.2. ALGORITHMES DE LA STL

5.1.2 Code E.V.E

array<unsigned char,256,256> original,resultat;


resultat = 255 - original;

F IG . 5.1 – Inverseur d’image

array<unsigned char,256,256> img1,img2;


array<unsigned char,256,256> resultat;
resultat = img1 - img2;

F IG . 5.2 – Soustracteur d’image

5.1.3 Performance :
Le tableau 5.3 liste les accélérations mesurées pour les deux algorithmes précédents
pour des tailles d’image croissantes. La référence prise pour le calcul d’accélération est le
temps d’exécution du dit algorithme écrit en C séquentiel. On observe ainsi que pour une
large fourchette de taille d’image1 , l’accélération fournit par E.V.E varie entre 7 et 12.
Par contre, pour des échantillons de très grande tailles, l’accélération s’effondre. Cette
effet est du à un mauvais remplissage du cache de donnée de l’unité AltiVec et peut être
contrebalancée via l’utilisation de primitives spécifiques2 .

Taille Inverseur Soustracteur


64x64 6.84 8.22
128x128 7.92 11.56
256x256 9.97 11.43
512x512 4.87 1.78
1024x1024 2.54 1.52

F IG . 5.3 – Performances des algorithmes simples.

5.2 Algorithmes de la STL


Ces tests mettent en évidence les performances de E.V.E sur des algorithmes repro-
duisant des fonctions classiques de la bibliothèque algorithm de la STL. Ces tests sont
effectués selon les mêmes modalités que les précedents mais sont réalisés sur l’ensemble
1
entre 64x64 et 1024x1024 typiquement
2
comme l’instruction vec_dst

54
5.2. ALGORITHMES DE LA STL

des types de données fournies par E.V.E. Pour référence lors du calcul de l’accélération ,
nous utilisons les temps de calcul des algorithmes fournient par la STL.

5.2.1 Produit-somme interne


Ce test concerne la fonction inner_product. Cette fonction est une fonction de
réduction scalaire qui calcule la quantité suivante :

X
R= a[i] ∗ b[i]

Performance :

Taille char short long float


16x16 1.38 1.29 0.90 2.32
32x32 1.75 2.03 0.95 9.94
64x64 3.28 2.86 1.11 16.71
128x128 4.23 3.66 1.24 13.83
256x256 4.68 2.94 1.36 6.50
512x512 3.25 1.90 1.16 3.52
1024x1024 2.01 1.49 1.07 3.24

F IG . 5.4 – Performances du produit-somme interne.

5.2.2 Analyse :
Il faut noter l’asymétrie certaine des résultats entre les types entiers et flottants. En
effet, l’accélération théorique des types entiers atteint 16,8 ou 4 et celle du type flottant
atteint 4. Or ici, nous obtenons des accélérations très inférieures à ces maxima théoriques
pour les types entiers et supérieures à ces maxima pour les flottants. Deux phénoménes
sont ici en cause :

– une faiblesse relative de l’unité flottante scalaire. Cette unité possède en effet
moins d’étages de pipeline que son homologue vectorielle et est légèrement moins
rapide lors des accès au cache de donnée. L’unité AltiVec bénéficie donc d’une
meilleur architecture globale en plus de son caractère vectoriel ce qui augmente
grandement les accélérations sur ce type de données.

– le manque d’utilisation des instructions de manipulation du cache. Aucune des


instructions permettant de précharger des données n’a été utilisée dans toutes les

55
5.3. UN DÉTECTEUR DE POINT D’INTÉRÊT

fonctions de E.V.E. Cette lacune se justifie par un temps de développement restreint


et produit donc ces résultats sous-linéaires. Une prochaine version de E.V.E permet-
tra de manipuler ces instructions et donc de limiter ces pertes.

– les transtypages implicites. Il n’existe pas pour les types entiers 8 bits et 32 bits
de fonctions de multiplication-addition. Pour parvenir à effectuer le calcul correcte-
ment, les types entiers doivent être transtypés en flottants ou en entiers 16 bits. Ces
transtypages sont coûteux et ralentissent d’autant les calculs.

5.3 Un Détecteur de point d’intérêt


5.3.1 Principe
Cet exemple à pour but de montrer comment un algorithme complexe peut être ex-
primé via l’interface de E.V.E. Pour cela, nous avons choisis de reprendre un detecteur de
point d’interêt, le detecteur de Harris [23]. Ce détecteur repose sur l’algorithme suivant :

Pour une image donnée I(x, y), on effectue un lissage par un filtre gaussien horizontal
et vertical. Ensuite, pour chaque pixel (x, y) on calcule :
∂I 2 ∂I ∂I
!
( ∂x ) ∂x ∂y
M(x, y) = ∂I ∂I ∂I 2
∂x ∂y
( ∂y )

Dans le cas d’une image numérique, ces gradients sont calculés via un filtrage par des
filtres adéquats. On lisse ensuite M(x, y) par une filtre gaussien vertical puis horizontal.
On évalue enfin la quantité :

H(x, y) = Det(M) − k.trace(M)2 , k ∈ [0.04; 0.06]

On retient ensuite les maxima locaux de H supérieur à 0.0001||H||∞.

Ces principaux avantages sont d’être invariant aux rotations de l’images sources, d’être
moins sensible au bruit que d’autres détecteurs du même types (Détecteur de Moravec par
exemple) et de finalement retenir un nombre de points limités.

On présente ici le résultat de l’application du détecteur à une image tiré d’une sé-
quence video :

56
5.3. UN DÉTECTEUR DE POINT D’INTÉRÊT

F IG . 5.5 – Application du détecteur de Harris

5.3.2 Mise en oeuvre


Notre implémentation se concentre sur l’optimisation les phases de filtrages et de dé-
tection de points d’intérêt. Avec E.V.E, les deux parties optimisées sont donc le filtrage
de l’image initiale et le calcul de la matrice M. Ces deux parties peuvent se coder ainsi :

array<signed short,320,240> img,a,b,c,t1,t2;


array<float,320,240> H;
filter_h_3<1,2,1> smooth_x;
filter_v_3<1,2,1> smooth_y;
filter_h_3<-1,0,1> grad_x;
filter_v_3<-1,0,1> grad_y;

// Filtrage
t1 = smooth_x(img);
t2 = smooth_y(img);
t1 = grad_y(t1);
t2 = grad_x(t2);

a = t1*t1;
a = smooth_x(a);
a = smooth_y(a);
b = t2*t2;
b = smooth_x(b);
b = smooth_y(b);
c = t1*t2;
c = smooth_x(c);
c = smooth_y(c);

// Evaluation
H = (a*b-c*c)-0.06*(a+b)*(a+b);

F IG . 5.6 – Filtrage et Evaluation du détecteur de Harris

57
5.3. UN DÉTECTEUR DE POINT D’INTÉRÊT

5.3.3 Performances
Les performances de ce détecteur furent mesurées par rapport à une implantation sé-
quentiel en C. Les mesures ont été réalisé sur des images 320*240 en utilisant une version
non définitive de l’API de E.V.E. Les résultats concernants les filtrages sont donc poten-
tiellement biaisés. On donne pour comparaison les performances du même algorithme
implanter sur un Pentium IV 2.4GhZ utilisant MMX et SSE2.

Temps d’execution Accélération Accélération Théorique


1.83ms 5.38 8

F IG . 5.7 – Performances du détecteur de Harris - Filtrage.

Temps d’execution Accélération Accélération Théorique


1.06ms 1.22 4

F IG . 5.8 – Performances du détecteur de Harris - Evaluation de H.

Plate-forme Temps
PPC G4 + E.V.E 2.9ms
Pentium 4 + MMX-SSE2 5.5ms

F IG . 5.9 – Comparaison de performances PPC G4/Pentium IV.

5.3.4 Commentaires
Les résultats obtenus confirment que E.V.E génère un code relativement efficace.
Néanmoins, les performances obtenues restent assez loin des performances théoriques
maximales. Plusieurs cause à cela :

– Au niveau du filtrage : les filtres sont appliqués les uns après les autres. Chaque
appel de filtre génère une boucle de traitement et de chargements. Or pour un code
AltiVec écrit à la main, ce genre d’appel serait réduit par le fait que le program-
meur aurait auparavant effectuée la composition des filtres pour se retrouver avec
une seule boucle de traitement.

– Au niveau de l’évaluation : l’implantation actuelle des opérateurs arithmétiques


de E.V.E ne permet pas de différencier les instances des tableaux utilisés. Ainsi, le
code correspondant à a*a va charger deux fois le vecteur a en mémoire à chaque
itération. Il n’existe pas de moyen simple d’eviter ce problême.

58
5.3. UN DÉTECTEUR DE POINT D’INTÉRÊT

– Globalement : c’est l’absence de préchargement du cache qui grève encore les


performances. La résolution simple du problème de spécifier les stratégies de pré-
chargement expression par expression est un problème ouvert qui sera intéressant
de régler par la suite.

L’ensemble de ces points seront certainement étudiés plus avant et intégré à une future
version de E.V.E, ce qui permettrait de se rapprocher encore des performances obtenues
par un code écrit spécialement pour ce genre d’applications.

59
Conclusion

La bibliothèque E.V.E est encore incomplète. De nombreuses fonctionnalités sont à


ce jour encore à développer ou à corriger. Néanmoins, les premiers essais nous montrent
que l’utilisation des techniques avancées de la programmation orientée objet telle que la
Meta-Programmation Template s’avère une stratégie payante. De nombreuses fonction-
nalités supplémentaires sont envisageables grâce a cette technique (composition de filtre,
élimination d’invariant de boucles, etc.). Elle permettra donc de compléter rapidement
l’API de E.V.E. parmi ces fonctionnalités, la résolution du problème des stratégies de pré-
chargement sera certainement un facteur important pour la suite du projet.

En outre, la volonté de Apple et de IBM de continuer à exploiter la technologie Alti-


Vec comme par exemple au sein du processeur PPC 970 (G5) disponible depuis peu nous
conforte dans l’intérêt que nous portons à cette technologie. Il est par ailleurs possible
d’envisager un portage de E.V.E sur d’autres plates-formes utilisant des extensions SIMD
(comme le Pentium avec MMX et SSE2). Ce portage permettrait la aussi de simplifier le
travail de nombreux développeurs rebutés par la complexité d’utilisation de ces unités.

Mais c’est dans un projet de plus grande ampleur que va venir s’insérer E.V.E. Elle
sera en effet une brique de base d’environnement logiciel d’une machine parallèle hybride.
Ce projet qui débutera en octobre 2003 vise à obtenir, en s’appuyant sur une utilisation
conjointe de trois niveaux de parallélisme , des facteurs d’accélération supérieur à 50 pour
des algorithmes de reconnaissance et suivi d’objets.

60
Références bibliographiques

[1] L. Bougé. Le Modèle de Programmation à Parrallélisme de Données : une Perspec-


tive Semantique. Technical Report 94-06, Laboratoire de l’Informatique du Parallé-
lisme, ENS Lyon, 1994.
[2] C.A.R Hoare. Communicating Sequential Processes. Prentice Hall, 1985.
[3] Geraint Jones. Programming in occam. Prentice-Hall international, 87.
[4] Lyndon Clarke, Ian Glendinning, and Rolf Hempel. The mpi message passing inter-
face standard, March 94.
[5] R.H. Perrot. A Langage for Array and Vector Processors. ACM Trans. on Prog.
Lang. and Syst., 1(2) :177–195, 1979.
[6] I. Foster, B. Avalani, A. Choudhary, and M. Xu. A Compilation System That Inte-
grates HPF and Fortran M. In IEEE Computer Society Press, editor, Scalable High
Performance Computing Conference, 1994.
[7] High Performance Fortran Forum. High Performance Fortran Language Specifica-
tion V1.1, 1994.
[8] P. Marquet. Languages and Expressions of Data Parallelism. Technique et Science
Informatiques, 12(6) :685–714, 1993.
[9] R. Backhouse. An Exploration of the Bird-Meertens Formalism. Technical Report
CS8810, Department of Mathematics and Computing Science, University of Gro-
ningen, 1988.
[10] D.B Skillicorn. The Bird-Meertens Formalism as a Parallel Model. In J.S. Kowa-
lik and L. Grandinetti, editors, NATO ARW "Software for Parallel Computation”,
volume 106. Springer-Verlag NATO ASI, 1993.
[11] Fouliras and Hamlen. The Connection Machine. Technical Report QMW-DCS-
1989-568, Queen Mary College, Department of Computer Science, 1989.
[12] Nathan Slingerman and Allan Jay Smith. Performance Analysis of Instruction Set
Architecture Extensions for Multimedia. Technical report, Apple Computer Inc. and
University of California at Berkeley, 2001.
[13] Alex Peleg and Uri Weiser. MMX Technology Extension to the Intel Architecture.
IEEE Micro, 16(4) :42–50, Ao˚t 1996.
[14] Apple Velocity Engine Frequently Asked Questions. http ://developer.apple.com.
[15] Keith Diefendorf. AltiVec Extension to Power PC Accelerates Media Processing.
Technical report, IEEE Micro, 2001.
[16] L. Damez. Evaluation de la Technologie AltiVec pour les Algorithles de Vision
Bas Niveau. Mémoire de DEA, Ecole Doctorale des Sciences pour l’Ingénieur,
Université Blaise Pascal, Clermont Ferrand, Septembre 2002.
[17] Ian Ollman. AltiVec. http ://www.idevgames.com/fileshow.php3 ?showid=134, mars
2001.
[18] Silicon Graphics, Inc. Standard Template Library Programmer’s Guide, 1993-2003.
http ://www.sgi.com/tech/stl/.
[19] T Veldhuizen. Expression Templates. C++ Report, 7 :26–31, 1995.
[20] T Veldhuizen. C++ Templates as Partial Evaluation. C++ Report, 1995.
[21] S Karmesin and al. Arrays Design and Epression Evaluation in POOMA II. ISCO-
PE’98, 1505 :38–44, 1998.
[22] T Veldhuizen. Using C++ Template Meta-Programms. C++ Report, 7 :36–43,
1995.
[23] C Harris and M Stephens. A Combined Corner and Edge Detector. 4th Alvey Vision
Conference, 1988.
[24] M. Herbordt, J. Burrill, and C. Weems. Making a Dataparallel Language Portable
for Massively Parallel Array Computers. In IEEE Computer Society Press, editor,
Int’l Workshop on Computer Architecture for Machine Perception, pages 160–169,
October 1997.
[25] G. E. Blelloch. Programming Parallel Algorithms. Communications of the ACM,
Mars 1996. http ://www.cs.cmu/.
[26] S. J. Gilbert. A MasPar Implementation of Data Parallel c++, Septembre 1992.
[27] Craig Lund. Altivec Introduction. Publication commerciale de Motorola, mai 2001.
[28] S Chiba. A Metaobject Protocol for C++. OOP SLA’95, pages 285–299, 1995.
[29] Y Ishikawa and al. Design and Implementation of Metalevel Architecture in C++ -
MPC++ Approach. Reflection’96, 1996.
[30] D R Engler. Incorporating Application Semantic and Control into Compilation.
USENIX Conference on DSL, 1997.
[31] T Veldhuizen. Arrays in Blitz++. Dr Dobb’s Journal of Software Tools, pages
38–44, 1996.
[32] The C++ BOOST Library. http ://www.boost.org/.
[33] The SPIRIT Parser Framework. http ://www.boost.org/libs/spirit/index.html.

You might also like