• Aucun résultat trouvé

Description des micro-routines

5.5 Prols de performance des micro-routines

5.5.1 Description des micro-routines

Le choix des micro-routines s'est porté sur des routines élémentaires pouvant être caté-gorisées en 3 types : les routines vectorielles (cf. Section 5.5.1.1), les réductions (cf. Section 5.5.1.2) et les routines mixtes (cf. Section 5.5.1.3).

Les routines considérées sont de complexité linéaire par rapport à la taille des données, avec un coût constant propre négligeable. Ces dernière montrent un comportement plus réguliers du prol de performance ainsi qu'une sensibilité aux faibles quantité de calcul. Les routines calculent en double-précision ce qui, en contrepartie d'une meilleure précision, d'une part augmente la taille des données en mémoire rendant plus précoce les eets de cache lorsque la quantité de calcul augmente et d'autre par réduit le bénéce de la vecto-risation (cf. Section 5.4.2.1).

Pour chaque type de micro-routines on considère deux routines qui se distinguent par leur nature soit memory-bound ou bien CPU-bound déterminée par leur intensité arithmétique (cf. Section 2.1.2.3 Chap. 2). Memory-bound signie que les opérations limitant majori-tairement les performances de la routine sont les accès mémoire tandis que CPU-bound signie qu'il s'agit des opérations arithmétiques.

L'intensité arithmétique évolue en fonction de l'implémentation, les valeurs considérée sont celles de l'implémentation scalaire sans déroulage de boucle. L'intensité arithmétique théo-rique pour l'architecture considérée est de 1/6 ops/byte. Ainsi une routine ayant une intensité arithmétique au dessus (resp. au dessous) est CPU-bound (resp. memory-bound) pour cette architecture. La Table 5.5 résume les caractéristiques des diérentes micro-routine.

Micro-routines Intensitéarithmétique

[Flop/Byte] Caractéristiques Addition de tableaux

Additionne le second tableau au premier passé en paramètre

1/24 Routine vectorielle memory-bound. Vectorisée par le JIT Exponentielle

Calcul la fonction exponentielle sur chaque élément du tableau d'entrée et écrit le ré-sultat dans le tableau de sortie

1/2 Routine vectorielle CPU-bound.

Non-vectorisée par le JIT Somme horizontale

Retourne la somme des éléments du ta-bleau d'entrée

1/8 Réduction memory-bound. Non-vectorisée par le JIT Aire de polygone

Retourne l'aire du polygone représenté par les coordonnées des points le constituant

1/4 Réduction CPU-bound. Non-vectorisée par le JIT Horner par-coecient

Calcul l'image par un polynôme quel-conque (dénie par son tableau de coe-cient) de chaque élément du tableau d'en-trée et écrit le résultat dans le tableau de sortie. Le parcours dans la boucle externe se fait par coecient

1 12 + 4(N1 +D1) Avec N le nombre d'éléments et D le degré du polynôme Routine mixte memory-bound. Vectorisée par le JIT Horner par-élément

Idem horner par-coecient excepté que le parcours dans la boucle externe se fait par éléments

1 4 +D8

Routine mixte

CPU-bound (pourD >4). Non-vectorisée par le JIT Tab. 5.5: Descriptif des micro-routines considérées. Sous fond bleu gurent les routines vectorielles, sous fond vert les réductions et sous fond rouge les routines mixtes. L'intensité arithmétique à l'équilibre pour l'architecture considérée est de 1/6 Flop/Byte (cf. Sec-tion 2.1.2.3 Chap. 2). L'intensité arithmétique des diérentes routines est calculée pour la version scalaire sans déroulage de boucle

5.5.1.1 Routines vectorielles

Les routines vectorielles sont les plus adaptées pour la vectorisation car elles expriment un parallélisme intrinsèque. Ce sont les routines du type out[i]=fonction(in[i]) ; à sa-voir qu'elles calculent pour chaque valeur d'entrée une valeur de sortie, le calcul étant identique pour chaque élément et indépendant des autres. Pour ce type de routine, lorsque les données sont déjà empaquetées en mémoire sous forme de vecteurs, la vectorisation est toujours protable.

Contrairement aux réductions (cf. Section 5.5.1.2), le parallélisme d'instruction est déjà exprimé dans les routines vectorielles du fait de l'indépendance des itérations4. Il peut y avoir, si la boucle n'est pas déroulée, des anti-dépendances mais celles ci sont résolues au niveau matériel par renommage de registre (cf. Section 2.1.1.2 Chap. 2).

On considère deux routines vectorielles. La première étant l'Addition de tableaux eectuant la somme de deux tableaux en-place (inout[i]=inout[i]+in[i]) et ayant une intensité arithmétique de 1/24 (memory-bound). La seconde étant la routine exponentielle calculant l'exponentielle du tableau d'entrée dans le tableau de sortie (out[i]=exp(in[i])) et ayant une intensité arithmétique de 1/2 (CPU-bound).

Routine exponentielle L'implémentation Java utilise la fonction exp de la classe FastMath de la librairie Apache Commons Math [167]. Celle-ci est en moyenne 3 fois plus rapide que la version utilisant la classe StrictMath du JDK. La version native utilise une implémen-tation assembleur vectorisée et micro-optimisée pour Sandy Bridge générée par PeachPy [38], librairie python pour la génération de routines assembleurs5. Les deux implémenta-tions respectent le standard IEEE 754 [18] pour la fonction exponentielle ce qui garantit que le gain de performance d'une implémentation ne se fait pas au détriment d'une perte de précision numérique.

L'implémentation Java de la routine exponentielle n'est pas vectorisée par le JIT. C'est, de manière similaire, le cas pour d'autres fonctions usuelles fournies par la classe Math du JDK (sin, cos, sqrt, log...) lorsqu'elles sont appliquées de manière itérative sur des don-nées empaquetées en mémoire. La vectorisation de la routine nécessite deux optimisations préalables :

L'inlining de la méthode (en l'occurrence exp) ;

Le déroulage de boucle d'un facteur au moins égal à la taille des vecteurs (en l'oc-currence 4).

Concernant l'inlining, celui de exp doit être forcé car son empreinte mémoire (niveau byte-code) dépasse la limite xée par les heuristiques d'inlining de HotSpot, soit 325 bytes par défaut6.

Concernant le déroulage de boucle, une première contrainte concerne le nombre de n÷uds constituant le corps de boucle dans la représentation intermédiaire de C2 ; il ne doit pas après déroulage dépasser un certain seuil xé par le paramètre -XX:LoopUnrollLimit qui vaut 60 par défaut. Pour permettre le déroulage de boucle de la routine, ce seuil a donc été augmenté.

Malgré les ajustements pour garantir l'inlining et le déroulage de boucle, la routine exponen-tielle n'est pas vectorisée ce qui peut : soit provenir d'une autre contrainte sur le déroulage de boucle ; soit provenir d'une contrainte de vectorisation liée au corps de boucle. Après vé-rication il s'avère que la boucle est bien déroulée, il s'agirait donc plus vraisemblablement d'une contrainte sur le corps de boucle.

5La librairie Yeppp ! [56] fournit une API Java basée sur JNI permettant de bénécier de ces implé-mentations micro-optimisées

6Dans le cas où un site d'appel chaud est rencontré, la taille limite est xée par le paramètre -XX:FreqInlineSize valant 325 bytes par défaut. Dans le cas d'un site d'appel froid il s'agit du para-mètre -XX:MaxInlineSize valant par défaut 35 bytes

5.5.1.2 Réductions

Les réductions sont les routines réduisant un tableau d'entrée à une valeur scalaire en sortie au travers d'une chaîne de dépendance. Les réductions consistant en une chaîne de dépendance, elles peuvent par conséquent être plus diciles voire impossibles à paralléliser et donc à vectoriser.

Les optimisations sur les réductions consistent en la division de la chaîne de dépendances en plusieurs sous-chaînes traitant chacune une partition du tableau d'entrée et fusionnant les résultats à l'arrivée. Cette division permet d'exprimer à la fois le parallélisme vectoriel et le parallélisme d'instruction (cf. Section 5.4.2.1). Contrairement au routines vectorielles (cf. Section 5.5.1.1) où le parallélisme d'instruction peut être exprimé par déroulage de boucle, ce dernier doit être sémantiquement exprimé au niveau du code généré dans le cas des réductions.

On considère deux réductions. La première est la somme horizontale retournant la somme des éléments d'un tableau (cf. Section 5.4.2.1), qui a une intensité arithmétique de 1/8 (i.e. memory-bound). La seconde est la routine aire de polygone retournant l'aire d'un polygone, qui a une intensité arithmétique de 1/4 (i.e. CPU-bound).

Routine aire de polygone Concernant la routine aire de polygone, les contraintes ap-plicatives nécessitent des polygones encodés par un tableau de double sous la forme [x0, y0, x1, y1...] ou le couple (xi, yi) représente les coordonnées du i-ème point du po-lygone. Cette représentation des données est qualiée de tableau de structures [42] où une structure est dans le cas présent un couple de coordonnées i.e. un point. Elle n'est généra-lement pas la mieux adaptée pour la vectorisation car elle peut nécessiter des opérations additionnelles pour empaqueter les données dans les registres vectoriels. À l'inverse, un polygone encodé par un tableau de double sous la forme d'une structure de tableaux [42], à savoir [x0, x1... y0, y1...], contient des données déjà empaquetées pouvant être directement chargées dans les registres vectoriels.

La Figure 5.10 montre le graphe de dataow du corps de boucle de deux implémentations vectorisées du calcul d'aire de polygone, l'une utilisant une structure de tableaux et l'autre un tableau de structures. À chaque itération les éléments ei associés à des segments suc-cessifs du polygone sont calculés puis ajoutés aux accumulateursacci. En n de routine les valeurs de chaque accumulateur sont sommées pour former la valeur de retour.

La version utilisant une structure de tableaux permet de calculer 4 élémentsei([e0 e1 e2 e3]) par itération avec une intensité arithmétique de 1/8 Flop/Byte, ce qui la rend donc memory-bound. Notons que l'implémentation proposée n'est pas optimale comme la plu-part des coordonnées sont chargées deux fois (16 valeurs sont chargées à chaque itération sur 10 utiles) et que les accès ne sont pas tous alignés. La version avec tableau de structures permet de calculer seulement 2 éléments par itération mais avec une meilleure ecacité mémoire comme il n'y a pas d'information chargée redondante. Son intensité arithmétique de 3/8 Flop/Byte est également plus élevée. Les défauts de cette implémentation, à savoir

le faible parallélisme vectoriel est le surcoût des opérations d'empaquetage (VPERM2F128 et VPERMILBD), la rendent néanmoins moins performante que l'autre version.

[y1 y2 y3 y4]

vmovupd

vaddpd

[x0 x1 x2 x3] [x1 x2 x3 x4] [y0 y1 y2 y3]

[dx0 dx1 dx2 dx3] [dy0 dy1 dy2 dy3]

[e0 e1 e2 e3] [acc0 acc1 acc2 acc3]

vmovupd vmovupd vmovupd

vsubpd vaddpd

vmulpd

[acc0 acc1 acc2 acc3]

(a) [x2 y2 x3 y3] vmovupd [x0 y0 x1 y1] [x1 y1 x2 y2] vperm2f128 vaddsubpd [dx0 dy0 dx1 dy1] vpermilpd [dy0 dx0 dy1 dx1] vmulpd [e0 e0 e1 e1] [acc0 acc0 acc1 acc1]

vaddpd

[acc0 acc0 acc1 acc1]

(b)

Fig. 5.10: Impact de la structure de donnée sur la vectorisation. Les gures montrent les graphes de dataow en assembleur du corps de boucle de deux implémentations vectorisées du calcul d'aire de polygone. L'implémentation 5.10(a) utilise en entrée une structure de tableaux de la forme [x0, x1... y0, y1...] facilitant la vectorisation. L'implémentation 5.10(b) utilise en entrée un tableau de structures de la forme [x0, y0, x1, y1...] ren-dant la vectorisation plus fastidieuse. C'est le cas pour la routine aire de polygone étudiée

5.5.1.3 Routines mixtes

Les routines mixtes sont les routines vectorielles eectuant également des réductions. C'est le cas de la routine Horner considérée, qui retourne l'image par un polynôme variable de chaque élément d'un tableau d'entrée, l'image étant calculée par la méthode d'Horner (cf. Table 5.6). Elle possède deux facteurs d'échelle qui sont le nombre d'élément d'entrée et le degré du polynôme (correspondant à l'index de son coecient de plus haut degré non nul).

Pour deux raisons principales, les tests ne considèrent que le nombre d'élément comme facteur d'échelle variable, le degré du polynôme étant xé à 64 :

La première raison est qu'en terme d'utilisation, le passage à l'échelle se fait plus classiquement sur le nombre de données d'entrée que sur le degré du polynôme ; La seconde raison est que les prols de performances à 2 dimensions sont plus

fasti-dieux à mesurer.

La routine Horner est qualiée de routine mixte car c'est une routine vectorielle du point de vue du tableau des éléments d'entrée (du type out[i]=fonction(in[i])) et c'est par ailleurs une réduction du point de vue du tableau des coecients (si l'on xe une entrée, on obtient la fonction qui pour n'importe quel polynôme calcule l'image de l'entrée xée, cette fonction étant une réduction).

Les deux facteurs d'échelle permettent de dénir deux versions de la routine, par-élément et par-coecient, dont les algorithmes sont exposés Figure 5.6.

La première version Horner par-coecient parcourt le tableau de coecient dans la boucle externe. Ainsi chaque étape de la réduction est calculée pour tous les éléments d'entrée. Une implémentation par bloc du parcours par coecient, parcourant des blocs d'éléments dont la taille garantit qu'ils logent dans le cache L1d, est également exposée (cf. Section 5.5.2.3) an d'améliorer la localité mémoire et les performances.

La seconde version Horner par-élément parcourt le tableau des éléments de la boucle ex-terne et eectue la réduction pour chaque élément. Contrairement à la somme horizontale, la réduction par la méthode d'Horner n'est pas vectorisable. Le bénéce principal de cette version provient de l'augmentation de l'intensité arithmétique (cf. Table 5.5) la rendant CPU-bound contrairement à la version précédente memory-bound.

Parcours par éléments Parcours par coecients

fori←0, n−1 do yi ←cd forj←d−1,0 do yi←yi∗xi+cj end for end for fori←0, n−1do yi←cd end for forj←d−1,0 do fori←0, n−1do yi←yi∗xi+cj end for end for

Tab. 5.6: Algorithme d'Horner pour le calcul polynomial avec parcours par éléments (i.e. parcourant les éléments d'entrée en premier) et parcours par coecients (i.e. parcourant les coecients polynomiaux en premier). Les éléments d'entrée sont notés (xi)i=0,...,n−1; les éléments de sortie(yi)i=0,...,n−1 et les coecients du polynôme de degré d(ci)i=0,...,d