Professional Documents
Culture Documents
U.F.R. S CIENTIFIQUE
S CIENCES POUR
ET T ECHNIQUE
L’I NGÉNIEUR
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 »
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.
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.
5
Table des matières
Remerciements 2
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
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.
8
Chapitre 1
Contexte de l’Etude
1
Communicating Sequential Processes
9
1.1. LE PARALLÉLISME DE DONNÉES
Tableau de données
Processeur Mémoire
Tableau de processeurs
interconnectés
Séquenceur
10
1.1. LE PARALLÉLISME DE DONNÉES
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) ;
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
X0 X1 X2 X3 X4 X5
Exemple :
foldl (+) 0 [1,2,3,4 ] = 10
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. 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
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
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
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
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
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.
Decodeur / Répartiteur
Flot d’instructions
Mémoire cache
17
1.2. CAS PARTICULIER : LE SWAR
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
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 ]
// 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
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
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
21
2.1. DIFFICULTÉ DU DÉVELOPPEMENT UTILISANT ALTIVEC
f1 = (vector float)source;
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
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.
/* On charge l’image et
on remplit le masque. */
result = mask[0]*image.shift(-1) +
mask[1]*image +
mask[2]*image.shift(1);
}
24
2.2. SOLUTION ENVISAGÉE
AVector<float,TAILLE> w,x,y,z;
z = x + y + w;
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];
}
3
Ce qui est souvent le cas pour des classes de types tableaux numériques
26
Chapitre 3
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;
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.
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);
}
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);
}
for(int x=0;x<100;x+=0.1)
r += 0.1*(x/(1.0+x));
x/(1.0+x)
29
_..
x +
x 1
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
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 :
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.
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;
val := ID | CONST
OP := + | - | * | /
expr := val | expr OP expr
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
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.
33
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE
_..
Var +
Const Var
Expr<BinaryNode<Const,Var,OpAdd> >
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
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) :
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.
et la classe Const qui va représenter une donnée de type constante (fig. 3.14) :
35
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE
Ces deux classes exposent dans leur interface la méthode eval décrite plus haut.
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) );
}
Data<double> x;
integrate( x/(1+x), 0,10);
Data<double> x;
integrate( operator/( x, operator+( 1, x ) ), 0,10);
36
3.1. TECHNIQUE D’ÉVALUATION PARTIELLE
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;
}
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
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) :
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 :
2. La classe Var sert désormais de conteneur pour les itérateurs sur les données nu-
mériques (fig. 3.20) :
private:
T* _data;
};
39
3.2. APPLICATION
private:
T _data;
};
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())) );
}
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.
1
acronyme de Expressive Velocity Engine.
2
Standard Template Library
4.1. PRÉSENTATION DE L’INTERFACE
// Le reste de l’application
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);
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;
// 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);
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 :
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;
// Différence d’image
res = img2 - img1;
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;
img = 1, 5, 1,
9, 7, 9,
1, 5, 1;
array<unsigned float,3,3> A;
array<unsigned char,16> B;
util::first_index i;
util::second_index j;
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
47
4.2. ALGORITHMES ET PRIMITIVES DE PLUS HAUT-NIVEAU
mask = 1,2,1;
tab = filter(tab,mask);
// 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
tab = gauss_x(tab);
tab = grad_y(tab);
49
4.3. UTILISATION CONJOINTE DE E.V.E ET DE LA STL
partial_sum inner_product
count_if count
adjacent_difference
util::first_index i;
util::second_index j;
array<unsigned char,4,4> res,x,y,z;
res = i+j;
// Versions non-optimisées
y = adjacent_difference(res.begin(),res.end());
copy( z.begin(), z.end(), y.begin(), y.end() );
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;
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>
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
52
Chapitre 5
Performances de E.V.E
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.
Cette opération effectue la soustraction des valeurs des pixels de deux images.
5.2. ALGORITHMES DE LA STL
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 .
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.
X
R= a[i] ∗ b[i]
Performance :
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.
55
5.3. UN DÉTECTEUR DE POINT D’INTÉRÊT
– 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.
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é :
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
// 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);
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.
Plate-forme Temps
PPC G4 + E.V.E 2.9ms
Pentium 4 + MMX-SSE2 5.5ms
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.
58
5.3. UN DÉTECTEUR DE POINT D’INTÉRÊT
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
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