Titre_Itanium_XP 29/12/04 11:14 Page 1 Les processeurs Itanium Programmation et optimisation Smail Jamel © Groupe Eyrolles, 2005, ISBN : 2-212-11536-9 Niar Tayeb Livre Itanium.book Page 55 Mardi, 28. décembre 2004 3:05 15 2 Exploiter EPIC dans les applications Ce chapitre propose de montrer comment les éléments architecturaux et le jeu d’instructions de l’Itanium (voir chapitre 1) sont utilisés pour produire des applications performantes. Plus précisément, il s’agit de donner les éléments permettant aux programmeurs de faire une bonne utilisation des ressources de l’Itanium. Comme il n’est pas question de programmer directement en code assembleur Itanium, pour des raisons de simplification de la tâche du programmeur et de portabilité de code, notre travail s’effectuera par le billet d'un certain nombre d'outils logiciels. Il s'agit plus précisément des options et des directives de compilations. C’est donc à travers ces éléments que nous vous expliquerons comment les applications pourront profiter des ressources logicielles et matérielles de l’Itanium. Ainsi, nous montrerons comment le compilateur C/C++ d’Intel v8.1 utilise la richesse des moyens de l’Itanium pour produire un code performant. Nous vous recommandons fortement l’utilisation des compilateurs signés Intel pour générer vos codes binaires. Plusieurs raisons à cela dont la principale est la garantie d’obtenir le meilleur niveau d’optimisation possible à un instant donné pour les architectures Intel dont l’EPIC. Par ailleurs, les compilateurs Intel n’ont pas pour vocation de remplacer ceux déjà existants et bien installés dans la communauté des développeurs à l’instar de GCC sous Linux ou de la série Visual de Microsoft. Ainsi, les compilateurs Intel inter-opèrent avec ces derniers et sont généralement utilisés pour compiler et optimiser les quelques modules les plus exigeants en terme de performance. Livre Itanium.book Page 56 Mardi, 28. décembre 2004 3:05 15 56 Les processeurs Itanium Où trouver les compilateurs d’Intel ? Le choix et l’adoption d’un compilateur ne sont pas anodins pour une entreprise. Il semble donc normal qu’une équipe de développement souhaite prendre le temps nécessaire aux tests et à la réflexion. Vous pouvez donc évaluer les outils Intel en général, et les compilateurs C/C++ et FORTRAN en particulier, pendant une durée limitée de 30 jours. Ces moutures d’évaluation sont totalement fonctionnelles et donnent accès au support en ligne de l’éditeur (Premier Support). Elles sont téléchargeables à l’adresse : http://www.intel.com/software/products/compilers/index.htm. Vous devrez vous y enregistrer et recevrez en contre partie par e-mail une licence d’évaluation, non renouvelable, ainsi que l’adresse du serveur FTP où vous pourrez entamer le téléchargement du ou des compilateurs. Une fois vos évaluations terminées, et si vous optez pour les compilateurs Intel, vous trouverez une liste de distributeurs agrées pour effectuer vos achats. Signalons enfin que les outils Intel sont disponibles gratuitement pour une utilisation non commerciale et individuelle sous Linux. Vous trouverez plus de détails sur la nature exacte de la licence à l’adresse http://www.intel.com/software/products/noncom/. Comment utiliser les compilateurs Intel ? Loin de se substituer aux manuels d’utilisation livrés avec les compilateurs, nous souhaitons simplement attirer votre attention ici sur les éléments complémentaires que vous devrez vous procurer afin de les utiliser. Dans l’environnement Windows, vous devrez télécharger et installer la plate-forme SDK de Microsoft depuis le site MSDN de l’éditeur. Cette étape est impérative puisque le compilateur Intel n’est pas autonome sous Windows. Cela signifie qu’il utilise les bibliothèques et l’éditeur de liens fournis par Microsoft. Il existe de nombreux compilateurs pour les processeurs de la famille Itanium. Parmi ceux-ci figurent : les compilateurs FORTRAN et C/C++ d’Intel pour systèmes Windows et Linux, le compilateur C/C++ sous HP-UX de Hewlett-Packard, le compilateurs C/C++ de Microsoft livré dans la plate-forme SDK, la suite GCC sous Linux, l’ORC (Open Research Compiler – ipf-orc.sourceforge.net), OpenIMPACT du groupe GELATO (www.gelato.uiuc.edu), ou bien encore de nombreux projets d’optimisation fondés sur des compilateurs existants tel que l’optimiseur C++ pour GCC 3.3 de l’Académie des Sciences de Russie. Précisons que l’objectif de ce chapitre n’est pas d’étudier la façon dont le compilateur génère un code optimisé pour l’Itanium. Notre but consiste à présenter aux développeurs/ programmeurs les outils que nous offre le compilateur C/C++ d’Intel pour une utilisation efficace. Ces outils sont représentés par une série de techniques de profilage et d’optimisation des logiciels que l’utilisateur pourra appeler lors de la compilation ou qu’il pourra intégrer dans son application. Nous verrons également que les méthodes d’optimisation de profilage présentées ici pourront être utilisées aussi pour optimiser le code après compilation et profilage. En effet, le compilateur est tout à fait capable d’appliquer une série de transformations et d’optimisations (dépliage, fusion, pipeline logiciel, etc.). Néanmoins, il a été constaté dans la pratique, que lorsque l’utilisateur intègre ces optimisations directement dans son code (par des pragmas par exemple), cela permet d’obtenir un gain de temps au niveau de la compilation et donne la possibilité au compilateur de se concentrer sur d’autres pistes d’optimisations. Ainsi, dans la mesure où le développeur a le temps d’analyser son Livre Itanium.book Page 57 Mardi, 28. décembre 2004 3:05 15 Exploiter EPIC dans les applications CHAPITRE 2 application après compilation et de l’optimiser (par transformation ou par l’ajout de pragmas), le compilateur pourra dans ce cas créer un code encore plus efficace. Pour illustrer ces techniques, nous utiliserons des petits programmes lorsque cela est possible. Certes, ils sont, loin des applications réelles mais ils ont l’avantage d’être clairs et concis. Le lecteur trouvera dans les chapitres 5 et 6 des techniques d’optimisation sur des applications réelles. L’ensemble des tests que nous présentons dans ce chapitre a été réalisé sur un système DELL PowerEdge 3250 équipé d’un bi-processeur Itanium 2 à 1,4 GHz. Ce dernier est doté d’une mémoire cache L1 de 32 Ko, d’une mémoire cache L2 de 256 Ko et d’une mémoire cache L3 de 3 Mo. Conscients que la lecture d’un code assembleur n’est pas une tâche facile, nous avons d’une part commenté le code Itanium issu de la compilation et d’autre part, nous avons supprimé du code les instructions Itanium qui ne sont pas essentielles pour comprendre les facilités offertes aux logiciels par EPIC. Restructuration des boucles Comme nous l’avons vu au chapitre 1, l’architecture Itanium comprend plusieurs niveaux de mémoires cache. L’objectif de cette hiérarchie de mémoires cache est de réduire la différence des latences entre le processeur d’une part et la mémoire externe d’autre part. Au niveau du processeur, jusqu’à 4 instructions mémoire (correspondants aux 4 ports M) peuvent être exécutées par cycle. Deux instructions parmi les quatre doivent être des instructions de chargement. Les deux autres doivent correspondre à des instructions de stockage (store) en mémoire. Malheureusement, la mémoire externe ne peut pas suivre une telle cadence. Même en utilisant les mémoires DRAM les plus rapides, on arrive à des temps d’accès supérieurs à 50 cycles. Cette latence au niveau des accès mémoire aurait pu être utilisée pour exécuter jusqu’à 50*6 instructions (la valeur 6 correspond à deux bundles de 3 instructions chacun), soit 300 instructions. Comme, en plus, les instructions d’accès mémoire peuvent représenter jusqu’à 30 % des instructions d’une application, la quantité d’instructions pouvant être exécutées pendant le temps d’accès aux données au niveau des DRAM peut être très importante. De ce fait, il est intéressant de disposer des instructions et des données sur lesquelles travaille le processeur dans les mémoires cache, qui travaillent à des vitesses plus grandes que celle de la mémoire externe. Le but de cette section est de montrer comment il est possible d’utiliser les optimisations que le compilateur C/C++ d’Intel offre à nos applications afin de mieux profiter de ces mémoires cache. En effet, la grande richesse des ressources disponibles au niveau du processeur, (parmi lesquelles, les prédicats, le grand ensemble de registres, la spéculation sur les données, les systèmes de rotation des registres, etc.) permet d’implémenter facilement les techniques classiques et habituelles d’optimisation de code (comme les transformations de boucles) et également de réaliser efficacement de nouvelles optimisations (comme le dépliage spéculatif des boucles, le préchargement, etc.). 57 Livre Itanium.book Page 58 Mardi, 28. décembre 2004 3:05 15 58 Les processeurs Itanium Dans cette première section, nous allons présenter les optimisations de boucle proposées par le compilateur C/C++ d’Intel. Nous nous limiterons à deux facettes de ces optimisations : les restructurations des boucles et la gestion des opérations de préchargement. Le lecteur intéressé par une présentation plus détaillée pourra trouver dans [UgLx04] [UgWn04] un supplément d’informations. Optimisation du code par transformation des boucles D’après les statistiques obtenues sur plusieurs applications, un processeur passe en général environ 90 % de son temps sur 10 % de l’application [HennPatte03]. En d’autres termes, il y a des parties qui sont plus souvent exécutées que d’autres dans la quasi-majorité des applications. Ces parties sont en général représentées par des boucles. Il est donc intéressant de doter les compilateurs de moyens permettant une plus grande optimisation des boucles. C’est pourquoi une grande attention est donnée aux boucles par les concepteurs de compilateurs. La plupart des compilateurs modernes réalisent des transformations de boucles. Les techniques comme : le dépliage des boucles, la permutation, l’échange de boucles, la distribution des boucles et l’inversion des boucles, par exemple, sont présents aussi dans les autres compilateurs pour les autres processeurs. La nouveauté avec Itanium est qu’un certain nombre de ces optimisations est facilement réalisable ; ce qui offre un gain de performance important pour les applications. Remplacement des accès mémoire par des scalaires L’une des premières optimisations que les compilateurs réalisent en général consiste à supprimer l’évaluation redondante de la même expression. Cette technique est communément appelée PRE (Partial Redundancy Elimination). Dans l’Itanium, elle a été étendue pour éliminer aussi dans les boucles un certain nombre d’instructions de chargement et de stockage redondantes. Le remplacement de ces références mémoire par des scalaires créés par le compilateur consiste à remplacer dans une boucle, une instruction mémoire (load) par une référence à un registre. Cette optimisation est réalisée par l’option /Qscalar_rep du compilateur C/C++. Pour désactiver cette option, il faut mettre l’option /Qscalar_rep-. Par défaut cette option est activée. Même si les options /O1 et /O2 permettent de réaliser le remplacement de références mémoires par des références registres, l’utilisation conjointe des options /O3 et /Qscalar_rep donne l’occasion au compilateur de faire un remplacement plus agressif des expressions redondantes et des accès mémoires inutiles. À titre d’exemple, avec /O3 et /Qscalar_rep, le compilateur ira chercher des remplacements de référence mémoire par des registres même en présence de dépendance entre itérations au prix, bien sûr, d’un temps de compilation plus grand. Le code suivant donne un exemple de boucle où le remplacement par des scalaires est réalisé. // version d’origine en C for (i=1, i<n ;i++) { // ligne 7 Livre Itanium.book Page 59 Mardi, 28. décembre 2004 3:05 15 Exploiter EPIC dans les applications CHAPITRE 2 a[i] = a[i-1]+1 ; // ligne 8 b[i] = a[i]+a[i-1] ; //ligne 9 } Voici maintenant le code après transformation : //version t2 = a[0] for (i=1, t1 = a[i] b[i] t2 = } optimisée en C ; i<n ;i++) { t2+1; = t1 ; = t1+t2 ; t1 ; Comme on peut le voir, la version transformée ne nécessite aucune instruction de lecture mémoire dans la boucle, alors que la première version nécessite au moins deux instructions de lecture mémoire à chaque itération (pour a[i] et a[i-1]). En effet, la transformation profite du fait que tous les éléments des deux vecteurs a et b sont calculés puis stockés dans la boucle. Par conséquent, mis à part la valeur de a[0], les valeurs des éléments du vecteur a avant la boucle n’ont aucune influence sur les valeurs de a et b après la boucle. Le seul inconvénient de cette deuxième version est le nombre d’instructions nécessaires pour coder la boucle. Lorsque le corps de celle-ci est grand et/ou lorsque cette optimisation est appliquée à plusieurs variables dans la même boucle, cela risque d’augmenter le nombre de défauts dans les mémoires caches des instructions. Pour palier ce handicap, l’Itanium propose d’utiliser le système des registres rotatifs de RSE pour garder un corps de boucle de taille relativement réduite, en supprimant les instructions de transferts entre registres. Le code suivant donne la version Itanium du code de la boucle précédente. Dans ce code, t1 et t2 sont représentés (respectivement) par r32 et r33. Comme l’instruction t2=t1 est ici intégrée implicitement dans l’instruction br.ctop b1 (voir chapitre 1, section « Pipeline logiciel »), cela permet d’économiser une instruction dans le code. // code Itanium avec optimisation mov ar.lc = 99 // initialisation de lc par n-1, on suppose que n=100 b1 : add r32 = r33, 1 // t1 = t2 +1 st8[r2] = r32, 8 // a[i] = t1 add r4 = r32, r33 // r4 = t1+t2 st8 [r3] = r4, 8 // b[i] = r4 br.ctop b1 // rotation (r33=r32) et aller à b1 si lc > 0. Le compilateur Intel donne un code beaucoup plus long que celui qu’on vient de présenter. En effet l’option /O3 fait activer une autre optimisation, que l’on va présenter dans la suite de ce paragraphe. Celle-ci concerne le dépliage de boucles. Ainsi le compilateur que nous avons utilisé réalise un dépliage de 8 itérations. 59