Précédent : Présentation générale et
objectifs Remonter : Projet CAPS, Compilation, Architectures
Parallèles Suivant : Grands domaines d'application
Résumé : Les activités de recherche du projet CAPS s'appuient sur des bases issues de plusieurs communautés scientifique: architecture, compilation et système. Nous avons choisi de présenter ici brièvement quelques fondements de nos recherches: les principes et défis liés à l'exécution spéculative, le problème de la simulation de processeurs et de la collecte de trace, un apperçu des techniques de transformation de programmes, et enfin les principes de la mémoire virtuelle partagée.
Résumé : Les pipelines d'exécution des processeurs superscalaires sont de plus en plus longs. Afin de limiter les ruptures de charge dans les pipelines dues aux instructions de branchement, des mécanismes de prédiction de branchement sont mis en oeuvre dans les processeurs, et les instructions prédites sont exécutées spéculativement.Pour atteindre un niveau de performance élevé sur les processeurs superscalaires de large degré qui devraient apparaitre vers l'an 2000, il est nécessaire de charger des instructions non-contiguës en mémoire, mais aussi de rompre les chaînes de dépendances entre instructions par la prédiction de valeurs.
Sur tous les processeurs superscalaires actuels, des mécanismes de prédictions de branchement sont mis en oeuvre pour continuer le séquencement spéculatif des instructions après un branchement sans attendre sa résolution: la cible et la direction du branchement sont prédites. En cas de mauvaise prédiction, les instructions séquencées (et parfois même déjà exécutées) doivent être annulées et le séquencement est repris sur le chemin réellement utilisé par l'application. Étant donné la très lourde pénalité payée en cas de mauvaise prédiction de branchement, la performance effective d'un processeur dépend de la précision de la prédiction. Des schémas de prédiction de plus en plus sophistiqués sont donc mis en oeuvre dans les processeurs. Parmi les informations utilisées pour prédire un branchement, on peut citer l'adresse du branchement, l'historique des derniers branchements exécutés, l'historique des derniers passages dans ce branchement [Yeh93],.. Cependant les recherches continuent dans plusieurs directions, parmi lesquelles on peut citer, la réduction des interférences sur les tables de prédictions de branchement [18] et la prédiction des branchements indirects [CHP97].
Les processeurs actuels exécutent les instructions de manière spéculative et dans le désordre. La génération actuelle de processeurs peut exécuter jusqu'à 4, parfois 6, instructions par cycle. Il est d'ores et déjà possible d'implémenter des processeurs pouvant lancer 10 voire 16 instructions par cycle. Cependant l'obtention de telles performances ne peut pas être envisagée en utilisant les mécanismes de séquencement actuels: seules des instructions consécutives sont chargées, or sur beaucoup d'applications, plus d'une instruction sur 5 ou 6 est un branchement. Pour permettre de réduire ce goulôt d'étranglement, il est nécessaire de prédire plusieurs branchements par cycle [6].
Une autre difficulté surgit avec la possibilité d'exécuter un grand nombre d'instructions indépendantes en parallèle. Souvent les applications n'exhibent pas ces instructions indépendantes: or l'exécution d'un programme doit respecter les dépendances entre les instructions. La prédiction de branchement est un premier accroc à ce respect des dépendances: toute instruction postérieure à un branchement est dépendante de ce branchement; cette dépendance est``cassée'' par la prédiction, mais les instructions sont validées dans l'ordre du programme. Récemment, il a été noté que le même principe pouvait être appliqué pour aussi ``casser'' les dépendances de données sur les programmes: on peut ainsi prédire le résultat d'une instruction ou d'un calcul d'adresse [LS96,SVS96].
Résumé : La validation des nouvelles idées en architecture de processeur passe par la simulation la plus précise possible du microprocesseur et de tout son environnement. Cette simulation doit être faite cycle par cycle et doit tenir compte de l'ensemble des interactions à l'intérieur du processeur. De plus cette simulation doit être faite sur des applications si possible représentatives de la charge d'un processeur dans son environnement potentiel d'utilisation.Deux approches sont utilisées, la simulation dirigée par l'exécution et la simulation dirigée par les traces. Nous décrivons ici ces deux approches, leurs intérêts et limitations réciproques.
Cette simulation doit être la plus précise possible et tenir compte de l'ensemble des interactions à l'intérieur du processseur. Deux approches peuvent être utilisées: la simulation dirigée par les traces et la simulation dirigée par l'exécution.
La simulation d'architecture dirigée par les traces présente l'avantage de décorréler la simulation de l'architecture de la collecte de traces [UM97]. Ainsi on pourra simuler une architecture en lui fournissant la trace de l'exécution d'une application c-à-d par exemple la liste des instructions exécutées et des adresses accédées en mémoire. Cette approche a été utilisée depuis très longtemps en architecture de processeur. Les traces peuvent être soit collectées par matériel, soit collectées par logiciel.
La collecte de trace d'exécution par matériel (analyseur logique) a été utilisée tant que les données et instructions circulaient sur les pattes d'entrées/sorties des processeurs. Sur les processeurs actuels, la collecte de trace ne peut plus être faite de cette manière. Ce qui explique que la collecte de traces par instrumentation logicielle soit la plus utilisée par la recherche en architecture (et aussi par l'industrie). Des outils adaptés à chaque jeu d'instructions sont aujourd'hui disponibles (Pixie, ATOM, spy, EEL, ..). Ces outils présentent le défaut de ne pouvoir tracer qu'une seule application et ne permettent pas en général de tracer l'activité système du processeur. Enfin le ralentissement des applications tracées est considérable (facteur 10-100) et ne permet pas d'envisager le traçage réaliste d'applications de grande taille (plusieurs centaines de milliards d'instructions). Enfin, elle est inappropriée pour la simulation réaliste de processeurs permettant l'exécution spéculative (c'est-à-dire prédisant les branchements et exécutant dans le désordre): l'exécution spéculative requiert l'accès (en lecture) aux instructions de la fausse branche ainsi qu'aux données en mémoire de l'application tracée.
La simulation dirigée par l'exécution nécessite l'exécution par le simulateur de l'application tracée elle-même. Cette approche permet contrairement à la simulation dirigée par les traces de simuler l'impact des instructions exécutées spéculativement. Cependant cette approche peut s'avérer extrèmement lourde puisqu'il faut être capable de simuler non seulement le code directement écrit par le développeur, mais aussi les appels à des bibliothèques dynamiques et les appels systèmes, c-à-d toutes les opérations susceptibles de modifier le contenu de la mémoire associée à l'application tracée. Cette approche a été suivie dans le simulateur SimOS [RHWG95]. SimOS est le simulateur complet d'une station MIPS ``bootant'' le système IRIX. L'avantage de SimOS est ainsi de permettre de simuler un processeur avec l'ensemble de son système d'exploitation. Par contre, les performances de la simulation restent très limitées et ne permettent pas d'envisager la simulation des ``grosses'' applications (plusieurs centaines de milliards d'instructions).
Le constat global est que la majeure partie des études pour les architectures de demain sont faites sur des traces d'applications dont on a souvent réduit le volume pour permettre des temps de simulation acceptables. Ceci peut conduire à des erreurs majeures pour le dimensionnement de structures telles que prédicteurs de branchement, antémémoire ou TLBs.. Le challenge en recherche pour la simulation réaliste d'architectures de processeurs est en fait aujourd'hui de parvenir à simuler le comportement des applications en vraies grandeurs et dans leur environnement système.
Résumé : L'efficacité de l'exécution d'une application tant sur une machine multiprocesseur que sur un PC ou une staion de travail dépend très fortement de la structure des programmes. Cette structure est imposée par le programmeur mais comporte des degrés de liberté que des techniques logicielles, appelées optimisations de code, peuvent exploiter pour augmenter la performance des applications. Nous présentons un rapide aperçu des transformations de code disponibles pour implémenter un compilateur optimiseur. Ces transformations peuvent être mises en oeuvre tant au niveau du code source que du code machine.
Les transformations de code destinées à l'amélioration de
performances peuvent intervenir à plusieurs étapes dans un
processus de compilation. La figure montre
l'organisation générale d'un compilateur. Des transformations de
code peuvent être effectuées aussi bien au niveau du code source
qu'au niveau du code machine.
Les optimisations effectuées au niveau du code machine sont principalement les optimisations ``Peephole'', qui consistent à remplacer des séquences d'instructions par des séquences plus rapides, et surtout l'application des techniques d'ordonnancement de code. Cet ordonnancement doit prendre en compte les caractéristiques fines de l'architecture telles que le nombre de registres disponibles, l'usage des ressources des processeurs, etc.
Par exemple, pour l'exploitation du parallélisme
d'instructions au niveau logiciel, les méthodes les plus simples
se restreignent à l'exploitation du parallélisme entre les
instructions d'un même bloc de base. Cependant, le nombre limité
d'instructions dans un bloc de base réduit l'efficacité de ce
type de techniques. En pratique, surtout dans le cas des boucles,
il faut extraire le parallélisme entre des instructions de
plusieurs blocs de base, par exemple en utilisant la technique du
pipeline logiciel. Cette technique, fondée sur l'exploitation du
parallélisme disponible entre les instructions d'itérations
différentes, consiste à segmenter le code des boucles d'une
manière similaire à celle utilisée par les pipelines
matériels.
Au niveau code source, les transformations de programmes utilisent toutes les informations sémantiques disponibles tant au niveau du contrôle de flot que de l'usage des variables. A ce niveau, des réorganisations majeures du code peuvent être effectuées telles que par exemple le remplacement de l'appel d'une procédure par le corps de celle-ci ("inlining"). C'est aussi sur le code source que l'on peut appliquer les techniques de parallélisation automatique et les méthodes d'optimisation de la localité. Par exemple, la performance d'une hiérarchie mémoire dépend très fortement des caractéristiques de localité des accès aux données effectués par un programme. La prise en compte de la hiérarchie mémoire par un compilateur consiste à considérer les trois aspects fondamentaux suivants :
Il existe un nombre très important de transformations du code source pouvant être utilisées pour améliorer le comportement de la hiérarchie mémoire sur une application et/ou la paralléliser [BGS94]. La plupart de ces optimisations s'appliquent aux boucles. Parmi celles-ci on peut citer:
Ces transformations sont aujourd'hui relativement bien comprises individuellement. Le challenge est aujourd'hui de maitriser l'interaction de toutes ces transformations et leurs impacts sur les performances.
Résumé : La programmation des architectures parallèles à mémoire distribuée est rendue difficile notamment par la présence de plusieurs espaces d'adressage disjoints. L'opération de distribution des données d'une application parmi ces différents espaces d'adressage est une tâche difficile. Le mécanisme de mémoire virtuelle partagée offre un seul espace d'adressage logique permettant d'éviter cette distribution explicite. Pour une implémentation efficace, le mécanisme de MVP s'appuie sur l'utilisation de caches dont la cohérence doit être assurée. Le choix d'un protocole de cohérence s'appuie sur les modèles de consistance mémoire qui en constituent les fondements scientifiques.
Le concept de MVP fournit une vision globale de la mémoire dans laquelle les calculateurs peuvent lire ou écrire. Vis à vis de l'utilisateur, il offre également un modèle mémoire qui caractérise le comportement de la mémoire lorsque plusieurs calculateurs effectuent des accès simultanés. De façon intuitive, l'utilisateur souhaite que la mémoire fournisse toujours le dernier résultat qui a été écrit dans la mémoire. Cependant, dans un système parallèle, la notion de ``dernier accès'' est ambiguë. Il oblige à définir un ordre total sur tous les accès mémoire, ce qui n'est pas souvent nécessaire. Le modèle de cohérence séquentielle est un exemple de modèle mémoire dont les accès sont consistants avec un ordre total. Un système mémoire possède la propriété de cohérence séquentielle si tous les processus voient les accès mémoire comme si ils avaient été exécutés sur un calculateur séquentiel multiprogrammé. Du point de la vue de la mise en oeuvre d'une MVP, un tel modèle impose de nombreuses communications (accès aux pages, invalidation, etc.). Plusieurs travaux ont été réalisés afin de concevoir de nouveaux modèles mémoire pouvant être implémentés plus efficacement sur des systèmes parallèles.
Parmi ceux-ci, le modèle de cohérence à la libération
[GLL90] a
été un des plus étudiés. Le principe de ce modèle mémoire repose
sur le constat que les accès aux données, effectués par un
programme parallèle, sont souvent synchronisés. Le modèle mémoire
à consistance à la libération est fondé sur l'utilisation de deux
classes d'opérations sur la mémoire. La première classe regroupe
les opérations classiques de lecture et d'écriture tandis que la
deuxième classe contient les opérations de synchronisation :
libération et acquisition. Le rôle de ces deux opérations est de
propager les modifications qui ont été réalisées par les
opérations d'écriture. Une opération de libération indique qu'un
processeur a effectué des modifications et que celles-ci doivent
être communiquées à tout processeur qui effectuera une opération
d'acquisition. De même, une opération d'acquisition indique qu'un
processeur va exécuter des opérations qui nécessitent la
connaissance des modifications effectuées par les processeurs
ayant exécuté une opération de libération. Deux formes de
cohérence à la libération ont été proposées [KCZ92]. La première forme est appelée
cohérence à la libération impatiente. Les modifications,
réalisées depuis la dernière opération d'acquisition, sont
propagées à tous les autres processeurs lors de la libération. La
deuxième forme, appelée cohérence à la libération paresseuse,
diffère de la précédente par le moment choisi pour diffuser les
modifications. Plutôt que de le faire à la libération, les
modifications sont propagées lors de l'opération d'acquisition.
Lors de l'acquisition, le processeur détermine quelles sont les
modifications valides dont il a besoin en fonction de la
définition du modèle de cohérence à la libération. Cette approche
permet ainsi de réduire fortement le nombre de messages Une
première mise en oeuvre de la cohérence à la libération
paresseuse a été effectuée au sein de la MVP TreadMarks[KDCZ94]. KOAN et MYOAN implémentent
une autre forme de cohérence permettant la modification
simultanée par de multiples écrivains d'une même page [5].