Projet Caps

previous up next contents
Précédent : Présentation générale et objectifs Remonter : Projet CAPS, Compilation, Architectures Parallèles Suivant : Grands domaines d'application



Fondements scientifiques

Panorama

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.

L'exécution spéculative

Mots-clés : Prédiction de branchement, exécution spéculative


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.


Les pipelines d'exécution des processeurs sont de plus en plus longs: 12 cycles sur l'Intel Pentium II. Les processeurs sont capables d'exécuter plusieurs instructions par cycle. Le séquencement des instructions devrait être interrompu à chaque instruction de branchement en attendant le calcul effectif de la condition et/ou de la cible: hors sur beaucoup d'applications, plus d'une instruction sur 5 ou 6 est un branchement.

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].

Simulation de processeurs et collecte de traces

Mots-clés : collecte de traces, simulation


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.


Afin de valider, au niveau performance, les architectures de processeurs, la simulation est le seul outil accepté aussi bien par l'industrie que par la communauté de recherche. Cette simulation doit être faite avant le début de la conception matérielle.

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.

Compilation pour architectures hautes performances

Mots-clés : Hautes performances, compilation, hiérarchie mémoire, optimisation,transformation de code


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.

L'efficacité des mécanismes matériels pour l'exploitation de la localité des références mémoire et du parallélisme, tant au niveau multiprocesseurs qu'instruction (``Instruction Level Parallelism''), 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. Ces optimisations de code sont fondées sur des transformations de programmes, qui respectent la sémantique des codes, mais réorganisent les calculs pour une meilleure exploitation d'une architecture donnée.
   Figure: Organisation d'un compilateur.

\begin{figure} \begin{center} \leavevmode \includegraphics[width=10cm]{orgaTrans.eps} \end{center}\end{figure}


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 :

Détection et estimation de la localité :
La détection de la localité est fortement liée au calcul des dépendances de données. En effet, si une dépendance existe, alors il y a réutilisation de données. Le deuxième aspect de cette question est de déterminer la proportion de références mémoire qui peuvent être évitées par l'exploitation effective de cette localité.
Exploitation de la localité :
L'exploitation de la localité consiste essentiellement à déterminer le niveau de la hiérarchie mémoire qui tirera parti de la localité présente et à adapter la génération de code en conséquence.
Optimisation de la localité :
Ces transformations de code ont pour but de restructurer les calculs pour permettre l'exploitation effective, par un niveau choisi de la hiérarchie, de la localité présente.

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:

Blocage de boucles :
Dans le cadre de l'optimisation de la localité, cette transformation permet de diviser l'espace d'itérations en pavés de telle sorte que les données réutilisées puissent être contenues dans un niveau de la hiérarchie mémoire.
Distribution de boucle :
Les instructions d'une boucle sont réparties dans plusieurs boucles ayant le même espace d'itération que l'original. Cette transformation est utilisée pour diminuer la pression sur les registres ou extraire des calculs parallèles d'une boucle séquentielle.
Fusion de boucles :
Les instructions de deux boucles sont fusionnées dans une seule boucle. Elle est par exemple utilisée pour améliorer les réutilisations de données.
Dépliage de boucle :
Cette transformation consiste à répliquer le corps de la boucle. Cette transformation, toujours légale, permet de diminuer le coût de gestion de la boucle et augmente le parallélisme d'instructions potentiellement exploitable par les processeurs.
Strip-mining :
Le ``strip-mining'' découpe l'espace d'itérations de boucle en blocs. Il permet d'ajuster la granularité des opérations dans le cas de la parallélisation ou de la vectorisation.

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.

Mémoire virtuellement partagée

Mots-clés : MVP, gestion mémoire


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.

La gestion des données dans une architecture parallèle à mémoire distribuée (APMD) est rendue complexe par la distribution physique des mémoires. Ce caractère distribué de la mémoire oblige l'utilisateur ou un compilateur ``intelligent'' à distribuer les données de l'algorithme devant s'exécuter en parallèle. Cette distribution des données est une opération complexe qui demande une très bonne connaissance de l'application à paralléliser. Cette distribution explicite peut être évitée grâce à des mécanismes de gestion de données permettant la migration de celles-ci en fonction des calculs effectués par chaque processeur. La mémoire virtuelle partagée (MVP) [Li86] est un exemple de mécanisme de gestion de données. Un tel concept offre un espace d'adressage global pour une architecture parallèle ayant un ensemble d'espaces d'adressage disjoints (APMD). L'implémentation d'un mécanisme de MVP s'appuie sur des mécanismes de gestion mémoire virtuelle au sein ou au dessus d'un système d'exploitation. L'espace d'adressage global est une région de mémoire virtuelle composée de pages migrant à la demande entre les processeurs selon les accès mémoire. Chaque mémoire locale agit comme un large cache, ou mémoire attractive, contenant les pages précédemment accédées. Comme tout dispositif fondé sur l'utilisation de caches, le problème de la cohérence de ces caches se pose.

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 [GLL$^{+}$90] 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].



Notes:

...base
Une séquence d'instructions comportant un seul point d'entrée (la première instruction) et un seul point de sortie (la dernière instruction).


previous up next contents Précédent : Présentation générale et objectifs Remonter : Projet CAPS, Compilation, Architectures Parallèles Suivant : Grands domaines d'application