Fonctionnement d'un ordinateur/Les architectures découplées

Les processeurs actuels utilisent des optimisations relativement lourdes pour gagner en performances. L’exécution dans le désordre, le renommage de registres, la prédiction de branchements et toutes les autres techniques d’exécution spéculative, sont clairement gourmandes en circuits, en portes logiques, en caches et autres tables de correspondance. Évidemment, les chercheurs ont déjà trouvé quelques alternatives : processeurs VLIW et EPIC, techniques d’exécution dans le désordre, etc. Mais il existe une alternative théorique relativement mal connue : les architectures découplées. Il existe plusieurs architectures de ce type, que nous allons aborder via quelques exemples.

Le découplage des accès mémoire modifier

L'idée qui se cache derrière les architectures découplées est de découpler l’exécution des instructions et le chargement des opérandes depuis la RAM. Les calculs et le chargement des opérandes se font chacun dans deux flux d'instruction séparés. Le découpage du programme en plusieurs flux peut s'effectue à la compilation. Les deux flux sont chacun exécutés sur un processeur séparé, un le processeur access en charge des accès mémoire et un processeur execute pour les calculs et des branchements. Le transfert des opérandes entre processeurs se fait par l'intermédiaire de mémoires tampons de type FIFOs, manipulées par le programmeur. Les transferts se font dans les deux sens : du processeur access vers le processeur execute pour les lectures, dans l'autre sens pour les écritures. Les branchements demandent cependant une synchronisation entre les deux flux d'instruction, afin de garantir qu'un branchement s’exécute bien au même moment dans les deux flux. Et toute la difficulté est de synchroniser les deux processeurs.

Cette technique n'est pas compatible avec l’exécution dans le désordre telle que vue dans les chapitres précédents, mais elle en reprend certains avantages. L'avantage principal est que la séparation en deux flux permet de profiter d'une forme limitée d’exécution dans le désordre, mais avec beaucoup moins de structures matérielles, moins de registres, moins de circuits. Les deux flux étant séparés, l'un peut continuer à s’exécuter alors que l'autre est en train d'attendre. Par exemple, le flux de calcul peut continuer à faire des calculs pendant que l'autre est coincé dans un accès mémoire de longue durée. Ou alors, le flux d'accès mémoire peut charger des données à l'avance, pendant que le flux de calcul est bloqué dans un calcul long (une division, par exemple). Cette forme de parallélisme est certes plus limitée que celle permise par l’exécution dans le désordre, mais le principe est là.

Dans ce qui suit, le processeur access sera noté processeur A, et le processeur execute sera noté processeur X, pour plus de simplicité.

Les échanges de données entre processeurs modifier

Prenons le cas d'une lecture : la lecture est démarrée par le processeur A, puis est transmise au processeur X. La transmission au processeur X se fait par l'intermédiaire d'une mémoire FIFO, la file de lecture de X (X load queue, abréviée XLQ). Quand le processeur X a besoin d'un opérande lu depuis la mémoire, il vérifie si elle est présente dans l'XLQ et se met en attente tant que ce n'est pas le cas.

Pour les écritures, l'adresse de l'écriture est calculée par le processeur A, tandis que la donnée à écrire est calculée par le processeur X, et les deux ne sont pas forcément disponibles en même temps. Pour cela, les adresses et les données sont mises en attente dans deux mémoires tampons qui coopèrent entre elles. La première est intégrée dans le processeur A et met en attente les adresses calculées : c'est la file d’écriture des adresses (store address queue, ou SAQ). La seconde met en attente les données à lire, et relie le processeur X au processeur A : c'est la file d’écriture de X (X store queue, ou XSQ). Quand l'adresse et la donnée sont disponibles en même temps, l'écriture est envoyée à la mémoire ou au cache.

Les échanges entre processeurs peuvent aussi se faire sans passer par des files de lecture/écriture. Cela peut servir pour diverses scénarios. Par exemple, le calcul d'une adresse sur le processeur A peut utiliser des données calculées par le processeur X. Pour cela, les échanges s'effectuent par copies inter-processeurs de registres. Un registre peut être copié du processeur A vers le processeur X, ou réciproquement. Pour utiliser cette unité de copie, chaque processeur dispose d'une instruction COPY.

Les branchements modifier

Les branchements sont présents à des endroits identiques dans les deux flux, ce qui fait que les structures de contrôle présentes dans un programme sont présentes à l'identique dans l'autre, etc. Or, les branchements doivent donner le même résultat dans les deux processeurs. Pour cela, le résultat d'un branchement sur un processeur doit être transmis à l'autre processeur. Pour cela, on trouve deux files d'attente :

  • la file A vers X (access-execute queue, abréviée AEQ), pour les transferts du processeur A vers le processeur X ;
  • la file X vers A (execute-access queue, abréviée EAQ), pour l'autre sens.

Il faut noter que le processeur peut se trouver définitivement bloqué si l'AEQ et l'EAQ sont toutes deux totalement remplies ou totalement vide. Pour éviter cette situation, les compilateurs doivent compiler le code source d'une certaine manière.

 
Résumé d'une architecture découplée de type access-execute.

Pour en savoir, voici quelques liens utiles.

Le découplage des branchements modifier

L'idée qui consiste à séparer un programme en deux flux a été adaptée pour séparer d'un côté un flux de branchements et un flux de calculs/accès mémoires. L'article « The effectiveness of decoupling » décrit l'architecture ACRI-1, qui découple accès mémoire, branchements et calculs. Cette architecture a été implémentée en matériel quelques années après la publication de l'article. Outre les processeurs X et A, on trouve un processeur maitre pour les branchements, qui répartit le travail à effectuer sur les deux autres processeurs. Les processeurs A et X ne peuvent qu'exécuter des boucles assez spéciales : elles ne doivent pas contenir de branchements ou de boucles imbriquées à l'intérieur. Le processeur maitre gère la répartition des boucles sur les deux autres processeurs, ce qui permet de se passer des files d'attentes pour les branchements. Pour demander l’exécution d'une boucle, le processeur maître envoie ce qu'on appelle un bloc de chargement d’instructions (instruction fetch block ou IFB), qui contient un pointeur sur la première instruction de la boucle, le nombre d'instructions de la boucle et le nombre d'itérations. L'envoi d'un IFB sur les mémoires tampons est réalisé sur ordre d'une instruction machine spécialisée.

 
Architecture à découplage total du contrôle.