• Aucun résultat trouvé

Partie entière d'un double

6.2 Expérimentations

6.2.1 Partie entière d'un double

La méthode floorD2I calcule la partie entière inférieure d'un ottant double précision. Elle prend un double en entrée et retourne un entier en sortie. L'optimisation appliquée à cette méthode peut être étendue à d'autre type d'arrondi (entier supérieur) et pour des ottants simple-précision.

Cette méthode constitue un exemple de méthode pouvant être optimisée simplement à l'aide d'instructions machine adaptées. Par ailleurs sa très faible granularité implique un coût d'intégration quasi-nul an de bénécier de ces instructions. Ces caractéristiques en font un bon cobaye d'expérimentation et ont motivées son intégration sous forme d'intrinsic. Dans l'application étudiée les calculs d'arrondis constituent des points chauds de faible intensité avec une tendance d'exécution diuse (cf. Section 6.1.1.1). Notons par ailleurs qu'il ne s'agit pas de méthode application-spécique du fait de son caractère générique. La Section 6.2.1.1 présente la version Java compilée par le JIT et la Section 6.2.1.2 la version native intrinsiée.

6.2.1.1 Version Java et code généré

L'implémentation Java de la méthode est montrée Figure 6.9. La méthode utilise des branchements conditionnels. Ainsi le code généré par le JIT pour cette méthode dépendra de la distribution des données d'entrées qui détermine les fréquences d'exécution de chaque branche prolée par le runtime (cf. Section 3.3.2.2). Comme vu Section 6.1.2.2, la spécia-lisation du code aux donnée prolées doit être considérée dans les tests de performance. Néanmoins, comme expliqué dans les sections suivantes, la réorganisation des branches n'entraîne pas de gain de performance comme les chemins exécutés restent similaires à la version générique.

Jeux de données considérés L'implémentation Figure 6.9 dénit trois chemins d'exé-cution. Le premier correspond aux doubles positifs, le second aux doubles strictement négatifs avec une partie fractionnaire non nulle (notés FSN) et le troisième aux doubles strictement négatifs avec une partie fractionnaire nulle (notés ESN). On dénit ainsi trois jeux de données d'entrée, un jeu de données correspondant à un type de double vu précé-demment, exécutant exclusivement l'un des trois chemins et pour lesquels le code généré est

8Dans lcps2d, le préxe "l" fait référence à "long", "cps" est l'acronyme de cross-product sign (en français "signe de produit vectoriel") et le suxe "2d" fait référence à "2 dimensions"

Versions\données Double positif Double FSN Double ESN Générique ALL→RET ALL→SN→FSN→RET ALL→SN→RET Spécialisée aux

doubles positifs ALLRET ALLdeopt_trap ALLdeopt_trap Spécialisée aux

doubles FSN ALLdeopt_trap ALLSNFSNRET ALLSNFSNdeopt_trap Spécialisée aux

doubles ESN ALLdeopt_trap ALLSNdeopt_trap ALLSNRET

Tab. 6.4: Chemins d'exécution empruntés pour les diérentes versions compilées par C2 en fonction du type de double en entrée. Une ligne correspond à une version du code compilée par C2 et une colonne à un type de double en entrée. Les couples code/donnée en vert sont ceux n'exécutant pas de branche de désoptimisation et considérés pour les tests de performance. FSN (resp. ESN) désigne les doubles strictement négatifs avec partie fractionnaire non-nulle (resp. nulle)

spécialisé. De plus on considère un jeu de données aléatoire pour lequel les trois chemins sont exécutés de manière équiprobable (ce dernier étant le plus dèle quant aux condi-tions d'utilisation réelles). Pour ce jeu de données, le code généré est générique (i.e. non spécialisé) et similaire à celui produit en désactivant la réorganisation des blocs de base9

relativement aux fréquences d'exécution prolées.

Versions générées par C2 pour l'implémentation Java La Table 6.5 montre les diérentes versions générées par le JIT pour les jeux de données dénis précédemment. La Table 6.4 montre les diérents chemins exécutés pour chacune des versions en fonction du type de double en entrée. Les chemins sont décrits avec les blocs dénis dans le code assembleur.

Le bloc ALL, exécuté pour tous les doubles, eectue le test de positivité. Le bloc SN, exécuté uniquement pour les doubles strictement négatifs, test la nullité de la partie fractionnaire. Enn le bloc FSN, exécuté uniquement pour les doubles strictement négatifs avec une partie fractionnaire non nulle, décrémente la valeur de retour.

Les versions spécialisées générées par C2 correspondent à une spécialisation du code à l'un des chemins empruntés dans la version générique. Les branches n'étant jamais empruntées sont remplacées par une branche de désoptimisation (repérée par le label deopt_trap dans le code).

La première portion du chemin avant exécution du bloc ALL est considérée dans le pa-ragraphe suivant. Comme précisé dans ce papa-ragraphe, les valeurs extrêmes emmenant à l'exécution du bloc VE ne sont pas considérées dans les tests de performance, ainsi, l'exé-cution du bloc ALL est uniquement précédée de l'exél'exé-cution du bloc ENTRY.

Comportement aux valeurs extrêmes La première opération eectuée par la mé-thode est la troncature (ou cast) de la valeur d'entrée de double à entier (cf. Figure 6.9). Cette troncature correspond au bytecode d2i dans la version bytecode et correspond à

public static int floorD2I (double a){

int ia = (int) a;

if (a < 0.) {

double dia = (double) ia;

if ( dia != a)

return ia - 1; }

return ia; }

Fig. 6.9: Implémentation Java de la méthode floorD2I retournant la partie entière infé-rieure d'un double (oor). La méthode utilisant des branchements conditionnels, le code généré par C2 dépend de la fréquence d'exécution des diérentes branches et donc du jeu de données en entrée. Le troncage du double a vers l'entier ia correspond au bytecode d2i respectant certaines spécications aux valeurs extrêmes

l'exécution des blocs ENTRY ou ENTRY→VE dans le code assembleur Figure 6.5.

Cette troncature introduit un branchement conditionnel (vers le bloc VE) pour les valeurs extrêmes an de respecter les spécications de d2i10. Ces valeurs extrêmes sont ±∞, NaN et celles pour lesquelles la troncature est inexacte11.

La compilation de d2i génère l'instruction CVTTSD2SI eectuant la troncature. L'instruc-tion CVTTSD2SI renvoie la valeur hexadécimale 0x8000000012 dans les cas de troncatures particulières évoquées précédemment. Si cette valeur est retournée le code appelle la rou-tine d2i_fixup (bloc VE) chargée de retourner les valeurs requises par les spécications. Les tests de performance ne considèrent pas les cas d'exécution de la branche exception-nelle. Par ailleurs son comportement dans ces cas d'exécution n'est pas déni par les spécications de la méthode comme précisé dans la section suivante. L'opération de tron-cature constitue un exemple où les spécications du langage Java peuvent contraindre la génération de code plus performant.

6.2.1.2 Version native optimisée

Le code correspondant est montré Table 6.5. La version native optimisée utilise l'ins-truction ROUNDSD de SSE4.113. L'instruction est utilisée avec un préxe VEX pour éviter la transition AVX/SSE (comme vu Section 6.1.3.2). L'instruction ROUNDSD calcule l'arrondi d'un double selon le mode voulu paramétré par un opérande immédiat. Si l'opérande vaut 0, l'arrondi se fait à l'entier inférieur (oor). Ii il vaut 1 l'arrondi se fait à l'entier supérieur

10Selon les spécications [60] d2i renvoie l'entier 0 si l'entrée vaut NaN. Il renvoie Integer.MIN_VALUE (resp. Integer.MAX_VALUE) si l'entrée vaut−∞(resp.+∞) ou si la troncature est trop petite (resp. trop grande)

11Les troncatures inexactes correspondent aux cas où la valeur ottante arrondie est trop grande ou trop petite pour être encodée dans un entier

12Équivalente à Integer.MIN_VALUE

13SSE4.1 est un sous ensemble de l'extension SSE4 (pour Streaming SIMD Extensions version 4 ) de Intel64 apparue dans l'architecture Intel Nehalem pour améliorer les performances des algorithmes numé-riques notamment multimédia

(ceil). La sémantique de cette instruction permet de dénir des intrinsics pour d'autres signatures et modes d'arrondi en bénéciant des même gains de performance.

Cette instruction n'est pas générée par C2 et n'est pas déclarée dans la librairie assembleur de HotSpot14. Sa dénition préalable a donc été un pré-requis dans l'implémentation de l'intrinsic. La seconde instruction utilisée est CVTTSD2SI qui, à l'instar de la version Java, est utilisée pour tronquer l'arrondi ottant en entier.

Divergence fonctionnelle avec la version Java La version native optimisée et la version Java compilée n'ont pas le même comportement pour les valeurs extrêmes. La ver-sion Java renvoie l'entier 0 si l'entrée vaut NaN. Elle renvoie Integer.MIN_VALUE (resp. Integer.MAX_VALUE) si l'entrée vaut −∞ (resp. +∞) ou si la troncature est trop petite

(resp. trop grande). La version native, elle, renvoie systématiquement la valeur Integer.MIN_VALUE pour ces valeurs d'entrée particulières. Ainsi les spécications de la méthode intrinsiée

doivent préciser que le comportement aux valeurs extrêmes est indéterminé. Cette spéci-cation se justie lorsque son usage applicatif se limite à des valeurs non-extrêmes.