Fonctionnement d'un ordinateur/Les dépendances de données et l'exécution dans le désordre
Dans les chapitres précédents, nous avons parlé des techniques d'émission dans l'ordre. L'idée est d’exécuter une nouvelle instruction à chaque cycle, dans des unités de calcul séparées. Les instructions sont émises consécutivement et l'unité d'émission bloque l'émission en cas de dépendance, mais les instructions s’exécutent en parallèle dans des unités de calcul séparées. De plus, elles peuvent se finir dans le désordre, en absence de dépendances, si les exceptions sont imprécises. Le point important est qu'on doit exécuter des instructions indépendantes dans des unités de calcul séparées.
Les techniques d'exécution dans le désordre que nous allons voir dans ce chapitre sont dites à émission dans le désordre. Elles se distinguent des précédentes sur un point précis : l'unité d'émission bloque l'émission d'une instruction, elle seule est bloquée, les instructions suivantes peuvent poursuivre. L'émission des instructions peut donc se faire dans le désordre, dans le sens où une instruction peut être émise alors qu'une instruction précédente ne l'est pas encore.
L'avantage de l'émission dans le désordre est qu'une instruction s'exécute dès que ses opérandes sont disponibles. Le processeur incorpore de quoi déterminer quand un résultat est disponible, de quoi savoir quand un opérande est prête. Par prête, on veut dire présente soit dans le réseau de contournement, soit enregistrée dans les registres. Dès qu'une instruction a ses opérandes disponibles, elle est émise et exécutée. C'est là la seule contrainte : les dépendances WAR, WAW et autres sont gérées par un ROB ou toute autre technique d'exception précise, seules les dépendances RAW limitent l'exécution.
- Dans la suite, nous utiliserons parfois l'abréviation OOO pour parler d'exécution dans le désordre. L'abréviation est celle de Out Of Order Excution.
Mais avant de poursuivre, précisons une chose importante : l'exécution dans le désordre traite les accès mémoire à part. La majorité des processeurs OOO assez anciens exécutent les accès mémoire dans l'ordre, seules les instructions entières/flottantes/branchements sont exécutées dans le désordre. L'exécution dans le désordre des accès mémoire est courante sur les processeurs récents, mais c'est l'unité d'accès mémoire qui se charge de changer l'ordre des accès mémoire toute seule dans son coin. Nous allons volontairement mettre de côté la gestion des accès mémoire dans ce chapitre.
Les fenêtres d’instruction : généralités
modifierPour implémenter l'exécution dans le désordre, il faut ajouter une sorte de mémoire qui met en attente les instructions bloquées, à émettre. Les instructions attendent dans cette mémoire le temps que leurs opérandes soient disponibles. De plus, il faut un circuit capable de gérer les dépendances RAW. L'unité d'émission qui bloquait les instructions, ne bloque plus rien, car elle faisait office de dépendance structurelle qui est éliminée.
Les processeurs sans exécution dans le désordre ont une unité d'émission qui bloque tout le pipeline dès qu'une instruction ne peut pas être émise. Pour limiter l'impact de ce blocage, quelques rares processeurs incorporent une mémoires FIFO appelée la file d'instruction, aussi appelée file de micro-opération. Elles permettent d'émettre des instructions dans l'ordre, à savoir qu'elles quittent la file d'instruction dans l'ordre d'ajout, dans l'ordre de décodage. Le schéma ci-dessous illustre une telle file d'attente couplée à un scoreboard.
Les techniques modernes d'OOO utilisent aussi des files de micro-opération améliorées, comme on le verra plus bas. De telles files de micro-opération permettent d'émettre des instructions dans le désordre, ce qui corrige le problème mentionné plus haut avec le scoreboard. Si une instruction bloque le pipeline, les instructions suivantes peuvent être émises.
Il existe dans les grandes lignes 4 grandes techniques d'exécution dans le désordre. Elles peuvent se classer sur deux critères : est-ce que les instructions sont émises dans l'ordre, et combien il y a de files de micro-opération.
Emission dans l'ordre | Emission dans le désordre | |
---|---|---|
Une file de micro-opération | Scoreboard | Fenêtre d'instruction centralisée |
Plusieurs files de micro-opération | Plusieurs FIFOs, plusieurs Scoreboard indépendants | Fenêtre d'instruction décentralisée |
L'usage de plusieurs files d'instruction
modifierLa première méthode évoluée d'OOO utilise plusieurs files de micro-opération. L'idée est qu'une fois décodée, les instructions sont accumulées dans des files de micro-opération différentes. Il y a typiquement une file de micro-opération pour les opérations entières, une autre pour les opérations flottantes, une autre pour les accès mémoire. D'autres processeurs utilisent une file pour les accès mémoire et une autre pour les autres instructions, comme le faisait le Pentium 4 d'Intel.
Les files de micro-opération sont des mémoires FIFO, ce qui fait qu'elles conservent l'ordre des instructions. Et chaque FIFO est couplée à un scoreboard qui vérifie les dépendances à chaque cycle. La présence de scoreboard nous dit que l'intérêt n'est pas un mécanisme d'émission plus compliqué, mais que la technique se repose sur le fait d'avoir plusieurs files de micro-opération. L'intérêt d'avoir plusieurs FIFOs est que si une dépendance bloque une FIFO, les autres peuvent continuer d'émettre des instructions.
Les files de micro-opération émettent leurs instructions de manière indépendante, ou presque. Elles ne savent pas si telle instruction dans une autre file doit passer avant ou après l'instruction qu'elles émettent. Par contre, les unités d'émission communiquent entre eux pour gérer la disponibilité des registres. Quand un registre est lu ou écrit par une instruction, les autres files d'instruction doivent être prévenues et mettre à jour leurs unités d'émission. La logique d'émission est donc plus complexe que prévu. Le processeur Pentium 4 avait deux FIFOs séparées : une pour les instructions d'accès mémoire et une autre pour les autres instructions. Si jamais une instruction mémoire bloquait le pipeline, l'autre FIFO pouvait continuer à exécuter des instructions entières indépendante de la lecture bloquée.
Les FIFOs sont simples à implémenter et ont un cout en circuit modéré. Par contre, le gain en performances est assez limité avec cette méthode, surtout comparé aux méthodes qui vont suivre. Le problème est qu'il est facile de se retrouver avec toutes les files de micro-opération bloquées. Quand l'une est bloquée, les autres tendent à se vider rapidement, particulièrement quand c'est la file pour les accès mémoire qui se bloque. Les techniques qui vont suivre font totalement disparaitre ce genre de blocage.
La fenêtre d'instruction centralisée
modifierLes processeurs OOO modernes utilisent une file d'attente modifiée, où les instructions sont insérées dans l'ordre, mais peuvent sortir dans le désordre, être émises dans le désordre. La file d'attente en question est appelée la fenêtre d'instruction. Les instructions décodées sont accumulées dans la fenêtre d'instruction, où elles attendent que leurs opérandes soient disponibles. La fenêtre d'instruction sert donc de file d'attente. A chaque cycle, l'unité d'émission consulte la fenêtre d'instruction pour voir quelles instructions peuvent s'exécuter, quelles instructions ont leur opérandes de disponibles. Elle choisit une instruction exécutable et l'envoie aux ALU. La fenêtre d'instruction doit spécialement être conçue pour, c'est une sorte de mémoire associative très complexe.
Le cas le plus simple n'utilise qu'une seule fenêtre d'instruction. Avec elle, l'unité d'émission, détecte les dépendances et répartit les instructions sur les unités de calcul. Les instructions décodées sont ajoutées en parallèle dans la fenêtre d'instruction, et le ROB. La raison est que les instructions quittent la fenêtre d'instruction dans le désordre, l'ordre des instructions est perdu après l'unité de décodage/renommage de registre. Aussi, pour remplir le ROB, la seule opportunité est en sortie de l'unité de décodage. Dans le cas où une instruction est décodée en plusieurs micro-opérations, elles sont toutes insérées en même temps dans la fenêtre d'instruction et le ROB.
Il en est de même pour ce qui est de l'unité mémoire. Les micro-opérations mémoire doivent s'exécuter dans l'ordre les unes par rapport aux autres, ou du moins en donner l'illusion. Les processeurs grand public anciens n'autorisaient pas d'exécution dans le désordre des accès mémoire. Pour cela,n l'unité mémoire intégrait une file d'attente, pour forcer l'exécution dans l'ordre des accès mémoire. Les processeurs modernes ajoutent des techniques d'exécution dans le désordre des accès mémoire, mais remettent ensuite ces accès mémoire dans l'ordre avec une sorte de mini-ROB intégré à l'unité mémoire. Dans les deux cas, les micro-opérations mémoire doivent être envoyées à l'unité mémoire dans l'ordre du programme, dans l'ordre de décodage.
Les fenêtres d'instruction décentralisées
modifierUne autre solution utilise plusieurs fenêtres d'instruction. Pour faire la différence avec l'usage d'une fenêtre d'instruction unique, nous allons parler de fenêtre d'instruction centralisée s'il n'y en a qu'une dans le processeur, de fenêtre d'instruction décentralisée s'il y en a plusieurs. Dans le cas général, chaque fenêtre d'instruction est associée à un type précis d'opération/instruction. La tendance moderne est d'utiliser une fenêtre d'instruction pour les instructions de calcul entières, une autre pour les flottantes. Il s'agit d'un choix simple et efficace, mais quelques processeurs font autrement, pour répondre à des contraintes très diverses.
L'usage de fenêtres décentralisées simplifie la répartition des instructions sur les différentes unités de calcul. Par exemple, utiliser des fenêtres séparées pour les ALU et les FPU facilite la répartition des instructions de calcul sur les ALU. Une fenêtre centralisée demande de faire la différence entre instruction entière/flottante, puis de choisir sur quelle unité de calcul entière/flottante utiliser. Une fenêtre décentralisée fait les deux choix séparément : d'abord on envoie l'instruction dans la bonne fenêtre suivant si c'est une instruction flottante ou entière, et ensuite on gère la disponibilité des ALUs/opérandes. Et on peut adapter la même idée en séparant les unités d'accès mémoire des ALU entières, etc.
Un point important est que l'émission est découpée en deux étages avec des fenêtres d'instruction décentralisées. La première étape envoie l'instruction décodée vers la fenêtre d'instruction adéquate, la seconde gère l'émission proprement dit. La première est l'étage de dispatch qui envoie l'instruction dans la fenêtre d’instruction adéquate, selon que c'est une instruction flottante, entière, autre. La seconde est l'étage de scheduling, qui gère la disponibilité des opérandes et des unités de calcul. En clair, on trie les instructions suivant leur type, suivant le type d'unité de calcul adéquate, puis on l'émet proprement dit. Faire ainsi a plusieurs avantages, avec cependant peu d'inconvénients.
Lors de l'étape de dispatch, l'instruction émise est ajoutée au tampon de réordonnancement, s'il existe. Sans cela, impossible de conserver l'ordre des instructions. Les instructions sont émises dans l'ordre au niveau de l'unité de dispatch, pas au niveau de l'étage de scheduling. Aussi, pour remplir le ROB, cela doit se faire au dernier moment où les instructions sont émises dans l'ordre, soit en sortie de l'unité de dispatch. De plus, les micro-opérations mémoire sont traitées à part des autres, elles sont envoyées directement à l'unité mémoire. La gestion des calculs d'adresse est quelque peu complexe, mais ils peuvent être fait soit dans l'unité mémoire, soit dans les ALU entières, peu importe. Il y a aussi un système de contournement complexe entre ALU/FPU et unité mémoire, qui n'est pas représenté dans le schéma ci-dessous.
Il faut noter que quelques processeurs utilisent des méthodes intermédiaires entre les trois solutions précédentes. Par exemple, les processeurs AMD Zen utilisent une fenêtre d'instruction décentralisée un peu particulière. Les unités de calcul entières disposent d'une fenêtre d'instruction rien que pour elles, sur ce processeur. Les instructions flottantes sont quant à elles alimentées par une file de micro-opération, pas une fenêtre d'instruction ! La raison à ce choix est un compromis entre performance et cout en circuit. L'exécution dans le désordre est peu efficace sur les instructions flottantes, car les CPU n'ont que peu d'ALU flottantes séparées. Vu le cout en circuits d'une fenêtre d'instruction, les concepteurs du processeur ont préféré utiliser une file de micro-opération pour les opérations flottantes.
Avantages et inconvénients de chaque implémentation
modifierUn processeur contient soit une fenêtre d'instruction unique, soit plusieurs fenêtres d'instruction séparées. Typiquement, les fenêtres d’instruction séparées sont spécialisées, dans le sens où on a une fenêtre pour les instructions flottantes, une autre pour les instructions entières, une autre pour les accès mémoire, etc. Pour résumer, on a le choix entre une grosse fenêtre généraliste et plusieurs fenêtres spécialisées, sauf que la fenêtre centralisée est lente et complexe, contrairement aux fenêtres spécialisées. Les deux méthodes ont des inconvénients et des avantages différents.
Dans les grandes lignes, l'avantage des fenêtres décentralisées est qu'elles sont plus petites qu'une grosse fenêtre d'instruction. Cela simplifie la gestion du contournement et de la répartition des instructions sur les unités de calcul, comme on le verra dans quelques paragraphes. Elles sont donc moins complexes et plus rapides. L'autre avantage, c'est qu'il est possible de démarrer l'exécution de plusieurs instructions simultanément : une instruction par fenêtre décentralisée, contre une par fenêtre d'instruction.
Mais les fenêtres décentralisées sont souvent sous-utilisées, partiellement remplies, contrairement aux fenêtres d'instruction. Il arrive qu'une fenêtre d'instruction soit remplie, alors que les autres sont vides. Par exemple, prenons un processeur avec une fenêtre d'instruction reliée à la FPU, et une autre reliée aux autres ALUs. Si le processeur n'exécute que des opérations flottantes, la fenêtre reliée à la FPU sera pleine, alors que l'autre sera vide. L'exécution des instructions dans le désordre est alors limitée par la petite taille de la fenêtre, qui ne peut plus accepter de nouvelle instruction flottante. Avec une fenêtre unique, on n'aurait pas eu ce problème : on aurait eu une énorme fenêtre d'instruction remplie d'instruction flottante, au lieu d'une petite fenêtre spécialisée.
Il est maintenant temps de voir en quoi sont faites les fenêtres d’instruction, qu'elles soient centralisées ou décentralisées. Nous allons aussi voir quelle est la logique d'émission associée.
L'implémentation d'une fenêtre d'instruction : généralités
modifierUne fenêtre d'instruction est une mémoire qui mémorise des micro-opérations. Elle est cependant plus complexe qu'une mémoire RAM ou qu'une FIFO et ressemble un petit peu à une mémoire cache. Elle dispose d'un port d'écriture et d'un port de lecture, au minimum.
Le port d'écriture permet à l'unité de décodage d'insérer une micro-opération dedans, d'ajouter la micro-opération qui vient d'être décodée. Si une instruction machine est décodée en plusieurs micro-opération, deux possibilités. La première est de les insérer un par un, ce qui fait qu'on peut se contenter d'un seul port d'écriture. L'autre possibilité est de les ajouter en même temps, ce qui demande plusieurs ports d'écriture.
Le port de lecture sert à émettre une instruction. On part du principe que la fenêtre d'instruction ne peut émettre qu'une seule instruction à la fois, car les processeurs que nous avons vu précédemment sont de ce type. Mais nous verrons dans quelques chapitre qu'il existe des processeurs dits superscalaires, qui sont capables d'émettre plusieurs instructions par cycle. Dans ce cas, la fenêtre d'instruction a un seul port de lecture. Un processeur superscalaire, quant à lui, doit avoir un port de lecture par unité de calcul, ce qui fait beaucoup plus.
Les entrées d'une fenêtre d’instruction
modifierLes fenêtres d'instruction sont composées d'entrées, des mots mémoire qui stockent une instruction. Une instruction réserve une entrée après son décodage et la libère dès qu'elle est envoyée aux unités de calcul. Une entrée contient l'opcode (les signaux de commande à envoyer à l'ALU), le registre de destination du résultat, les registres de chaque opérande, et éventuellement des signaux de commande en plus. De plus, chaque opérande est couplée à un bit de disponibilité, qui indique si elle est disponible. Quand un opérande est écrit dans les registres, le bit de présence correspondant est mis à jour. Enfin, chaque entrée possède un bit empty qui indique si elle est vide, cette information étant utile pour réserver des entrées.
Nous verrons dans quelques chapitres que certaines fenêtres d'instruction sont capables de mémoriser les opérandes des instructions. De telles fenêtres d'instruction seront appelées, dans ce cours, des stations de réservation. Les entrées des stations de réservation sont assez semblables à celle des fenêtres d'instruction, à un détail près : les opérandes sont enregistrées directement dans des champs séparés. Il y a aussi deux champs, un par opérande, pour stocker l'opérande elle-même, lue depuis les registres ou la mémoire. Les deux champs sont ajoutés en plus des champs pour mémoriser les noms de registres opérandes, encore que les deux peuvent être fusionnés.
Précisons que la terminologie n'est pas très fiable. Les termes "fenêtre d'instruction" et "station de réservation" ne regroupent pas du tout la même chose d'un papier de recherche à l'autre, d'un bouquin à l'autre, d'un chercheur à l'autre, d'un ingénieur à l'autre, d'un professeur à l'autre. Le terme initial vient pourtant de l'article original sur l'algorithme de Tomasulo, où il désignait une fenêtre d'instruction associée à une unité de calcul précise. Mais le terme a ensuite été réutilisée, ce qui fait que certains processeurs avec plusieurs unités de calcul sont décrit comme ayant une unique station de réservation. En bref : c'est le bazar ! Mais nous en reparlerons dans le chapitre sur le renommage de registres, car c'est la place idéale pour les aborder.
Toujours est-il que j'ai fait le choix de confondre les concepts de fenêtre d'instruction et de station de réservation avec les concepts de lecture avant émission et de lecture après émission. La différence entre les deux est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation. Dans le cas "avant émission", les opérandes sont mémorisées dans la station de réservation, pas avec une fenêtre d'instruction. En somme, une fenêtre d'instruction ne contient que des instructions (et donc pas les opérandes), alors que les stations de réservations permettent de réserver une ALU pour une instruction, avec tout ce qu'il faut pour l'exécuter, opérandes inclus. Et cette différence a une influence sur le pipeline du processeur, le banc de registres et tout ce qui s'en suit. Mais pour le moment, la différence n'est pas pertinente, elle ne le sera que dans le chapitre sur les processeurs superscalaires.
Il a été proposé de fusionner le tampon de ré-ordonnancement avec la fenêtre d'instruction dans une structure unique. La méthode a notamment été utilisée sur les processeurs d'architecture K6 d'AMD. Le tout donnait une structure appelée le DRIS (deferred scheduling register renaming instruction shelf). La différence avec une fenêtre d’instruction normale est que les entrées sont libérée non pas quand l'instruction est exécutée/émise, mais quand elle termine son exécution et quitte le ROB. La fenêtre d'instruction doit alors incorporer des bits pour savoir si telle entrée a émis sont instruction ou non.
La logique d'éveil et de sélection
modifierDans le cas qui va suivre, on suppose que le processeur ne peut émettre qu'une instruction à la fois. Chaque cycle, une instruction est sélectionnée et est envoyée dans le chemin de données, soit à une ALU, soit à une unité d'accès mémoire. La sélection d'une instruction est effectuée par l'unité d'émission, qui est aussi appelée le scheduler. Elle gère la disponibilité des opérandes et la disponibilité des unités de calcul adéquate. Le choix de l'instruction émise doit être le plus pertinent possible, pour des raisons de performances.
Pour le premier point, l'unité d'émission détermine quelles instructions sont candidates pour l'émission. Les instructions candidates sont celles dont tous les opérandes sont prêts. Le processeur contient un circuit qui détecte les instructions candidates : la logique d’éveil (wake-up logic). Une fois que la logique d'éveil a fait son travail, une instruction est choisie pour s’exécuter en fonction de la disponibilité des unités de calcul adéquates. Ce rôle est dévolu à un circuit qu'on appelle la logique de sélection, ou select logic.
La logique de sélection doit gérer le fait que les unités de calcul du processeur ne sont pas toutes identiques. Déjà, un processeur a généralement des unités séparées pour les instructions entières et flottantes, des unités spécialisées dans les accès mémoire, parfois des unités spécialisées pour les branchements. Une instruction doit être attribuée à la bonne unité de calcul, on ne doit pas envoyer une instruction entière dans une unité de calcul flottante. De plus, parmi les unités de calcul entières, toutes ne sont pas identiques. Par exemple, il est fréquent d'avoir plusieurs unités de calcul spécialisées dans les opérations simples (additions, soustractions, opérations logiques) avec une unité pour les multiplications/divisions séparées, parfois une unité spécialisée pour les décalages/rotations, etc. Là encore, allouer la bonne instruction à la bonne ALU est complexe. Plus les unités de calcul sont hétérogènes, plus la logique de sélection est compliquée.
Les logiques d'éveil comme de sélection sont assez complexes. Comme on le verra plus bas, la logique de sélection la plus simple, qui gère une seule unité de calcul, est basée sur un encodeur à priorité couplé à d'autres circuits. Et pour gérer plusieurs ALUs, il faut mettre des encodeurs en cascade. Le résultat est que même en prenant une logique de sélection simple, elle sera assez lente et gourmande en circuits. Aussi, la logique de sélection met souvent un cycle d'horloge entier pour faire son travail, elle a son propre étage de pipeline attribué. La logique d'éveil est dans le même cas, ce qui fait qu'émettre une micro-opération demande deux cycles d'horloge.
Le défaut est que cela rajoute des cycles d'horloges entre la fenêtre d'instruction et l'ALU, ce qui complique la gestion du pipeline. Ces cycles de retard sont à prendre en compte au moment d'émettre les instructions, les scheduler doit en tenir compte pour des performances optimales. Sans tenir compte de ces cycles de retard, les instructions arrivent en retard de quelques cycles à l'ALU, cycles de retard durant lesquels l'ALU n'a pas fait de calcul et a été sous-utilisée, sans compter que le système de contournement n'est pas utilisé au mieux. Et le problème est encore plus important sur les processeur avec des stations de réservation, qui utilisent la "lecture après émission" : cela rajoute un cycle d'horloge en plus, un temps de retard en plus.
La logique d'éveil : l'émission en avance
modifierLa logique d'éveil est plus complexe qu'au premier abord. Idéalement, on voudrait démarrer une nouvelle instruction sur l'unité de calcul dès qu'elle est libre. Mais pour cela, la logique d'éveil doit tenir compte du fait qu'il y a plusieurs étages entre l'émission et l'exécution sur une unité de calcul. Par exemple, un processeur a souvent entre un et à cinq étages entre la file d'attente et les unités de calcul. Il faut lire les registres, gérer le contournement, et cela peut prendre quelques cycles d'horloge. Sur le Pentium 4, on trouve 6 étages entre la fenêtre d’instruction et l'entrée de l'ALU.
Si une instruction réutilise le résultat d'une autre, cela peut poser problème. Imaginons que l'on ait trois cycles entre la file d'attente et les ALU. Si une instruction attend la disponibilité de l'opérande, elle serait émise, puis parcourrait plusieurs cycles avant d'arriver à l'ALU et de s'exécuter, alors qu'elle aurait du s'exécuter immédiatement après la première instruction pour profiter au mieux du contournement. Même chose si les deux instructions n'ont aucune dépendance : mieux vaut exécuter une seconde instruction dès que l'ALU est libre, immédiatement après la première instruction. L'idéal aurait été que la seconde instruction eu été réveillée quelques cycles en avance, afin de s'exécuter immédiatement après la première et éventuellement de profiter du contournement au mieux possible.
- Il faut noter que le problème se pose plus avec des fenêtres d'instruction qu'avec des stations de réservation. Les premières ont un étage en plus entre émission et ALU, vu qu'il faut lire des registres après l'émission.
La solution consiste à démarrer des instructions en avance, quelques cycles avant que les opérandes soient tous disponibles. L'unité d'émission n'attend pas que les opérandes soient enregistrées dans les registres avant d'émettre une nouvelle instruction vers l'ALU. Idem pour les fenêtres d'instruction : la logique d'éveil est conçue pour émettre des instructions en avance si elles sait que l'opérande manquante sera bientôt disponible. Le signal d'éveil est généré en tenant compte de cela. La technique porte le nom d'émission anticipée.
Le nombre de cycles d'avance est facile à connaitre : c'est le nombre d'étages entre l'unité de calcul et la fenêtre d'instruction. Le cas le plus simple est celui des instructions qui ont une durée fixe : on gère la situation en ajoutant quelques circuits dans l'unité d'émission. La logique d’éveil des processeurs modernes est conçue pour réveiller les instructions en attente pour éliminer ces temps morts. Leur conception dépend d'une connaissance fine de la durée de chaque opération.
L'implémentation varie, deux solutions sont possibles. La première est de générer le signal d'éveil dans l'unité de calcul qui fournira le résultat, mais en avance de quelques cycles. Par exemples, si on a deux étages entre l'ALU et la fenêtre d’instruction, le signal de réveil sera généré deux cycles avant que le résultat soit effectivement calculé.
Une autre technique génère le signal de réveil dans une structure centralisée, spécialisée dans la génération des signaux d'éveil. L'unité de composée d'un registre à décalage par registre. Imaginons qu'une instruction est émise, que cette instruction mette N cycles à s'exécuter, et qu'elle écrive dans un registre de destination. L'unité sélectionne alors le registre à décalage associé au registre de destination, et met le bit numéro N à 1. Le registre décalé d'un cran à chaque cycle. Quand le bit sortant est un 1, alors le signal de validité pour l'opérande est généré.
Les deux méthodes précédentes partent du principe que l'instruction a une durée fixe, connue à l'avance. Mais autant c'est une hypothèse plus que raisonnable pour les instructions arithmétiques, logiques, branchements, et autres, autant les accès mémoire ne sont pas dans ce cas. Les accès mémoire sont gérés à part, ils ont des systèmes d'émission en avance, mais qui seront vus dans le chapitre sur l'unité mémoire. Pour le moment, contentons-nous de dire que l'émission en avance pour les accès mémoire est purement spéculative : le processeur émet des instructions sans savoir si la donnée sera disponible ou non, et annule l'instruction en cas de problèmes.
La logique de sélection : âge ou position ?
modifierLa logique de sélection est primordiale pour de bonnes performances. Il existe deux méthodes principales pour sélectionner l'instruction à exécuter. La première est la plus simple : elle se base sur lé numéro de l'entrée. En effet, une fenêtre d'instruction est avant tout une sorte de mémoire, un mix entre mémoire associative, mémoire RAM, et FIFO. Et chaque entrée a un numéro, une adresse mémoire. Nous parlerons de numéro dans ce qui suit, car ce sera plus simple, le terme d'adresse mémoire étant peut-être un peu exagéré dans le contexte des fenêtres d'instruction.
Toujours est-il que la fenêtre d'instruction est adressable dans le sens où on peut lui envoyer le numéro de l'entrée voulue, et la fenêtre d’instruction renvoie le contenu de l'entrée sur son port de lecture. Et c'est ce que fait la logique de sélection : elle génère le numéro de l'entrée à lire. Le numéro est alors envoyé sur l'entrée d'adresse de la fenêtre d’instruction, et elle fournit la micro-opération associée sur son port de lecture, son port d'émission.
La première méthode de sélection, donc. Elle est basée sur sur le numéro de l'entrée. Si plusieurs entrées sont éveillées, la logique de sélection prend celle avec le plus petit numéro. Elle porte le nom de sélection par position, sous-entendu position dans la fenêtre d'instruction. Vous l'avez peut-être deviné, mais la logique de sélection est alors un encodeur à priorité. Le circuit de sélection ne se résume donc pas seulement à un encodeur à priorité, mais c'est le circuit central. L'avantage d'une telle méthode de sélection est qu'elle est simple à implémenter : un encodeur à priorité est un circuit connu, facile à implémenter. Mais on remarque un défaut : un encodeur est un circuit assez rapide, mais qui utilise beaucoup de transistors, beaucoup de portes logiques, surtout si on veut qu'il soit rapide. Ce qui explique que la logique de sélection ait son propre étage de pipeline rien que pour elle.
La seconde méthode s'appelle la sélection par âge, au nom assez transparent. L'idée est de privilégier l'émission de l'instruction la plus ancienne. Elle demande que la fenêtre d'instruction trie les micro-opérations par ordre d'insertion dans la fenêtre d'instruction, ce qui en fait une pseudo-FIFO. Le tri est partiel, car les instructions peuvent sortir de la fenêtre d’instruction à tout moment. Compacter la fenêtre d'instruction, à savoir regrouper toutes les entrées valides est une idée, mais elle est compliquée à implémenter. Aussi, elles préfèrent encoder des informations sur l'ordre d'émission dans chaque entrée. L'encodeur à priorité doit alors travailler non pas sur des nombres entiers liés à l'ordre d'insertion dans la pseudo-FIFO. Elle est encore plus gourmande en circuits que la méthode précédente.
Les processeurs haute performance utilisent souvent des méthodes hybrides. Par exemple, les processeurs AMD de microarchitecture Bulldozer utilisaient une méthode intermédiaire. Ils utilisaient une fenêtre d'instruction couplée à un encodeur à priorité pour la sélection par position, mais complémentaient le tout avec une unité qui mémorisait l'instruction la plus ancienne dans la fenêtre d’instruction. Le mécanisme de sélection par âge était utilisé en priorité. Mais si l'instruction la plus ancienne n'était pas prête, le processeur basculait sur le mécanisme de sélection par position.
La compaction des fenêtres d'instruction
modifierÀ chaque cycle, les instructions décodées sont ajoutées dans la fenêtre d'instruction, dans des entrées vides. Vu que les instructions quittent celle-ci dans le désordre, ces vides sont dispersés dans la fenêtre d'instruction, ce qui pose problème pour déterminer où placer les nouvelles instructions. La solution la plus triviale consiste à conserver une liste des vides, mise à jour à chaque insertion ou émission d'instruction. Une autre solution consiste à éliminer les vides en compactant la fenêtre d'instruction à chaque cycle d'horloge. Des circuits se chargent de détecter les vides et de regrouper les instructions en un unique bloc. Il faut signaler que certaines processeurs arrivent à se passer de cette étape de compactage, mais au prix de fenêtres d'instruction nettement plus complexes.
Autre problème : quand il faut choisir quelle instruction émettre, il y a toujours plusieurs candidats. Si on choisit mal, des instructions restent en attente trop longtemps parce que d'autres instructions plus jeunes leur passent devant. Pour éviter cela, les instructions les plus vielles, les plus anciennes, sont prioritaires. Pour cela, on peut utiliser une FIFO un peu spéciale pour la fenêtre d'instruction. Si les ajouts d'instruction se font dans l'ordre, les instructions ne quittent pas forcément la fenêtre d'instruction dans l'ordre imposé par une FIFO : les instructions restent triées dans leur ordre d'ajout, même s'il y a des vides entre elles. Dans ces condition, il est préférable que le compactage conserve l'ordre FIFO des instructions. Dans ces conditions, l'instruction la plus ancienne est celle qui est située à l'adresse la plus faible : le circuit de sélection peut donc être fabriqué avec des encodeurs, et est relativement simple.
Les fenêtres d'instruction basées sur une matrice de dépendances
modifierL'implémentation la plus simple étend le fonctionnement d'un pipeline dynamique usuel. Rappelez-vous le chapitre sur les pipeline dynamiques. Nous avions vu que de tels pipelines émettaient une instruction par cycle, quitte à émettre des bulles de pipeline en cas de dépendance bloquante. Et il est possible d'améliorer leur unité d'émission pour qu'elle soit compatible avec l'exécution dans le désordre.
Rappels sur le registre de réservation/disponibilité
modifierL'émission d'une instruction est gouvernée par un registre de réservation qui indique quels registres sont réservés par une instruction, et ceux qui sont libres. Un registre réservé est un registre qui sera écrit par une instruction en cours d'exécution, mais qui n'a pas encore écrit son résultat. Les instructions candidates doivent avoir leurs opérandes dans des registres libres. Sinon, c'est signe que leurs opérandes sont réservées en écriture, donc pas encore écrites, donc pas disponibles. Si une instruction candidate veut lire ce registre, c'est signe qu'il y a une dépendance RAW : elle veut lire un registre réservé, qui est destiné à être écrit par une instruction en vol. Si elle veut écrire, c'est signe qu'elle veut écrire dans un registre où une autre instruction veut écrire : c'est une dépendance WAW. Dans les deux cas, l'instruction n'est pas émise.
Le registre de réservation encode les registres réservés comme suit : chaque bit est associé à un registre. Le bit numéro 0 est associé au registre numéro 0, le bit numéro 1 au registre numéro 1, etc. Là, En clair, les numéros de registres réservés sont encodés en représentation non pas binaire, mais en représentation one-hot (vue au premier chapitre).
Registre 7 | Registre 6 | Registre 5 | Registre 4 | Registre 3 | Registre 2 | Registre 1 | Registre 0 |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
Avant d'émettre une instruction, l'unité d'émission extrait les registres opérande/destination et les convertis dans la même représentation que le registre de réservation, à savoir en représentation one-hot. Le circuit de traduction binaire vers one-hot est, pour rappel, un simple décodeur. Puis, on fait un OU logique entre les sorties des décodeurs. Le résultat est un masque qui indique quels registres sont lus par l'instruction, encodé en représentation one-hot. Appelons-le le masque d'opérandes. Le masque est alors comparé au registre de réservation pour vérifier si l'instruction peut être émise.
Si l'émission est autorisée, le registres de réservation est là aussi mis à jour. Le registre de réservation est aussi mis à jour dès qu'une instruction enregistre son résultat dans les registres, ou alors dès qu'il est disponible pour le contournement. Le bit associé au registre repasse alors à 0 (ou à 1). Sans contournement, la mise à jour se fait alors à la toute fin de l'instruction, quand elle se termine, lors de la dernière étape d'enregistrement, à la fin de son pipeline. Avec, elle se fait quand l'instruction quitte l'unité de calcul.
L'extension à l'exécution dans le désordre : l'éveil par matrice de bits
modifierLa différence entre un pipeline dynamique et l'exécution dans le désordre est que l'on passe d'une instruction à émettre à plusieurs. Plusieurs instructions sont mises en attente dans une fenêtre d’instruction, et y attendent leur tour. L'idée est alors d'envoyer le registre de disponibilité à toutes les entrées, à toutes les instructions en attente. Ainsi, on sait quelles sont les instructions qui peuvent s'exécuter et celles qui ne le peuvent pas. L'implémentation est assez simple, elle ressemble beaucoup à l'implémentation d'un pipeline dynamique simple, si ce n'est que des circuits sont dupliqués.
L'implémentation utilise des fenêtres d'instruction légèrement différentes de celles introduites plus haut. Elles n'utilisent notamment pas de bits de disponibilité, mais autre chose. Lorsqu'une instruction est ajoutée à la fenêtre d'instruction, le masque de disponibilité des opérandes est stocké dans la fenêtre d'instruction. A chaque cycle, le registre de disponibilité est envoyée à toutes les entrées, et est comparé avec tous les masques de disponibilité des opérandes. Si une comparaison renvoie un 1, alors l'instruction de l'entrée est une instruction candidate à l'émission.
L'ensemble est implémenté avec l'aide d'une matrice de bits, où chaque ligne correspond à une entrée et chaque colonne à un registre. À chaque croisement entre une ligne et une colonne, on trouve un bit. Si le bit de la ligne n et de la colonne m est à 1, cela veut dire : l'instruction dans l'entrée n a besoin de lire le registre m. S’il est à 0, alors cela veut dire que l'instruction stockée dans la ligne n n'a pas besoin de la donnée dans le registre m.
Lorsqu'une instruction réserve une entrée, elle initialise la ligne en fonction des registres qu'elle souhaite lire. Pour vérifier si une instruction a ses opérandes prêts, le processeur compare le registre de disponibilité à la ligne associée à l'instruction/entrée. A chaque intersection ligne-colonne, se trouve un comparateur de 1 bit, qui détecte si le registre est demandé et disponible. Les résultats de cette porte sont ensuite envoyés à une porte ET, qui fait le gros du travail.
Les fenêtres d'instruction basées sur une mémoire associative
modifierLes processeurs modernes utilisent une mémoire associative pour la fenêtre d'instruction. Les mémoires associatives ont été abordées il y a quelques chapitres, mais faisons quelques rappels. Les mémoires associatives servent à accélérer la recherche de données dans un ensemble. Or, une fenêtre d'instruction vérifie régulièrement si chaque entrée est prête. Il s'agit d'un processus de recherche d'une instruction qui respecte une condition bien précise dans la mémoire, ce qui fait que les mémoires associatives sont donc tout indiquées.
Comme vous le savez, les signaux de commande d'une instruction sont propagés avec celle-ci dans le pipeline, le nom du registre de destination ne faisant pas exception. Le nom de registre est envoyé à la fenêtre d'instruction lors de l'écriture du résultat dans les registres. S'il y a correspondance, l'opérande est disponible et son bit de disponibilité est mis à 1. On peut adapter cette méthode pour tenir compte du contournement assez simplement.
En conséquence, le circuit de détection des dépendances est constitué d'un grand nombre de comparateurs : un par champ « nom de registre » dans chaque entrée. La logique d'éveil/wake up regroupe tous les comparateurs, la logique de sélection est un circuit situé en-dehors de la mémoire associative. Les entrées sont dans la mémoire associative elle-même.
L'implémentation la plus simple est celle du schéma précédent, où on utilise une mémoire associative unique. Une version plus élaborée sépare la fenêtre d'instruction en deux parties : une mémoire qui mémorise les registres des opérandes, une autre mémoire pour le reste. La mémoire pour les registres opérandes est la mémoire associative proprement dite, c'est elle qui est associées aux comparateurs, à la logique de wake-up/réveil, etc. Par contre, l'autre mémoire est une mémoire RAM simplifiée, qui mémorise les informations qui n'ont pas besoin des comparateurs. L'opcode de l'instruction est là-dedans, par exemple.
Les optimisations de la consommation d'énergie : la désactivation des portions inutilisées
modifierLe problème avec des fenêtres d'instruction de ce type est leur forte consommation énergétique, ainsi que le grand nombre de portes logiques utilisées, qui deviennent prohibitif pour des fenêtres d'instruction un peu grosses. Pour résoudre ce problème, certains ont optimisé les comparateurs. D'autres ont tenté de profiter du fait que la majorité des bits dans les entrées sont composés de zéros. Bref, les optimisations purement matérielles sont légion.
Une première solution est de désactiver les entrées qui sont inutilisées, vides, sans instruction. Pour cela, on fait précéder les comparateurs par un circuit qui les connecte ou déconnecte des lignes de bit. Le circuit est commandé par le bit empty qui indique si une entrée est vide ou non. L'avantage est que la consommation d'énergie de la fenêtre d'instruction devient alors proportionnelle au nombre d'entrées non-vides, et non au nombre d'entrées total. Du moins, en apparence, les entrées non-utilisées doivent quand même être alimentées pour fonctionner, mais l'activité de comparaison disparait dans les entrées vides. Il est possible d'étendre cette technique aux entrées dont les opérandes sont prêtes. Les gains sont généralement assez bons, avec une réduction de consommation variant entre 30 et 50%, avec une implémentation très simple et très économe en circuits.
Une autre solution, assez simple à mettre en place, consiste à désactiver une partie de la fenêtre d'instruction sin elle est inutilisée. Pour cela, il faut segmenter la fenêtre d'instruction avec la technique du wire partitionning. Pour rappel, une fenêtre d'instruction est une mémoire associative. Et comme toute mémoire associative, elle contient des fils, les lignes de bit, qui relient les entrées/sorties à toutes les cellules mémoire. Plus une ligne de bit est longue, plus elle consomme d'énergie et plus le temps de lecture/écriture est long.
L'idée est de segmenter les lignes de bits en plaçant des répéteurs qui recopient la tension d'un segment au suivant. Les répéteurs sont des tampons trois-états, ce qui permet de déconnecter les portions inutilisées de la ligne de bit. Ce faisant, la fenêtre d'instruction est découpée en segments, qui peuvent être désactivés si besoin. Si il y a peu d'instructions chargées dans la fenêtre d'instruction, les portions inutilisées sont désactivées. La technique marche d'autan mieux si les instructions sont compactées au début de la fenêtre d'instruction.
Tout le problème est de savoir quelles portions désactiver. La technique est assez simple si la fenêtre d'instruction est compactée, à savoir si toutes les instructions sont régulièrement regroupées au début de la mémoire CAM. Mais même avec cette optimisation, la décision de désactiver une portion de la fenêtre d'instruction n'est pas à prendre à la légère. Il ne faut pas désactiver une portion contenant des instructions pouvant être émise sous peu. La décision dépend de paramètres variés : proportion d'entrée vides, quantité d'entrées récemment activées/désactivées récemment, présences d'entrées avec un match d'opérandes récent, etc.
Il est important de préciser que les techniques en question fonctionnent aussi sur les autres types de fenêtre d'instruction, qui ne sont pas basées sur des mémoires associatives. Nous allons voir celles-ci dans la suite du chapitre, mais gardez à l'esprit que ces optimisations, à savoir désactiver les entrées vides/prêts et partitionner la fenêtre d'instruction, sont des optimisations générales qui marchent avec presque tout.
L'ajout d'une FIFO pour les instructions déjà prêtes à l'émission
modifierD'autres techniques permettent de réduire aussi bien le cout en circuits que la consommation d'énergie de la fenêtre d'instruction. L'idée est d'utiliser une mémoire associative quand elle est réellement nécessaire, mais d'utiliser une fenêtre d'instruction "normale" dès que possible.
La méthode la plus simple tient compte du fait que certaines instructions ont déjà toutes leurs opérandes de disponibles à l'émission, mais doivent quand même être mises en attente, parce que l'ALU adéquate est occupée, parce que des dépendances WAW/WAR sont encore là, ou pour tout autre raison. Dans ce cas, elles sont mises en attente non pas dans la fenêtre d'instruction, mais dans une mémoire FIFO toute simple. L'idée est qu'au lieu d'avoir une énorme mémoire associative pour la fenêtre d'instruction, on déplace quelques entrées dans une petite FIFO annexe. Le résultat est un gain en terme d'énergie et de circuits, au prix d'une perte de performance mineure. La perte de performance en question se manifeste si aucune instruction n'a ses opérandes de prêtes à l'émission : le programme n'utilise pas la FIFO et voit alors une fenêtre d'instruction plus petite.
Une amélioration de cette technique tient compte du fait que certaines instructions sont dans un cas intermédiaire. Elles ont déjà une opérande de prête lors de l'émission, mais pas l'autre. Le nombre d'opérandes varie suivant l’instruction : certaines n'ont besoin que d'un seul opérande. De plus, il arrive qu'une opération tout juste décodée ait déjà un ou plusieurs de ses opérandes de prêts. Cette constatation permet d'éviter d'utiliser des comparateurs pour les opérandes déjà prêts. Par exemple, on peut parfaitement utiliser trois fenêtres d'instruction :
- une pour les instructions dont tous les opérandes sont prêts, mais qui sont quand même mises en attente, sans comparateur ;
- une pour les instructions dont un seul opérande manque, qui n'utilise qu'un comparateur par entrée ;
- une pour les instructions dont deux opérandes manquent à l'appel, qui utilise deux comparateurs par entrée.
C'est beaucoup plus économique que d'utiliser une seule grosse fenêtre d'instruction qui contiendrait autant d'entrées que les trois précédentes réunies, avec deux comparateurs par entrée. Certains sont même allés plus loin, et ont proposé de supprimer la fenêtre d'instruction avec deux opérandes par entrée. Les instructions dont deux opérandes sont inconnus au décodage sont stockées dans la fenêtre d'instruction pour instructions avec un comparateur par entrée. Un circuit de prédiction se charge alors de prédire l'opérande manquant, cette prédiction étant vérifiée plus loin dans le pipeline. Il serait cependant étonnant qu'une telle proposition ait donné lieu à la moindre implémentation réelle.
Les optimisations de la consommation d'énergie avancées
modifierD'autres chercheurs ont conservé une fenêtre d'instruction unique, avec autant de comparateurs par entrée qu'il y a d'opérandes possibles par instruction. Simplement, le processus de détection des opérandes prêts est légèrement ralenti : on ne vérifie qu'un opérande par cycle. Pour vérifier les deux opérandes d'une entrée, on doit attendre deux cycles.
Enfin, certains chercheurs ont proposé des fenêtres d'instruction segmentées. Les instructions circulent à chaque cycle d'un segment vers le suivant : en conséquence, certains segments conservent les instructions les plus anciennes, un autre les instructions les plus jeunes, etc. Seul le denier segment, celui qui contient les instructions les plus vielles, peut émettre une instruction : la détection des opérandes se fait seulement dans le dernier segment, qui contient les instructions les plus anciennes. On économise ainsi beaucoup de comparateurs.
Certains chercheurs ont tenté de pipeliner l'étape de sélection des opérandes, ainsi que l'étage d'arbitrage. Mais en faisant cela, il faut plusieurs cycles pour détecter qu'une instruction a ses opérandes prêts, ce qui pose problème face à de nombreuses dépendances RAW. Pour éviter de trop perdre en performances, certains chercheurs ont décidé d'utiliser des techniques pour prédire quelles seront les instructions dont les opérandes seront bientôt prêts. Si détecter qu'une instruction est prête prend n cycles, le processeur devra tenter de prédire la future disponibilité des opérandes n cycles en avance pour obtenir des performances optimales.
Remplacer la mémoire associative par une RAM
modifierUne autre optimisation possible est de remplacer la fenêtre d'instruction par une mémoire RAM, dont chaque mot mémoire correspondrait à une entrée. Une telle optimisation permet en théorie de se passer des comparateurs associés à chaque entrée, mais au prix de l'ajout de circuits annexes potentiellement couteux.
La première de ces techniques, la recherche directe par étiquette (direct tag search) fut créée par Weiss et son collègue Smith. Ils cherchaient à améliorer l'algorithme de Tomasulo, un algorithme qu'on expliquera dans quelques chapitres. Rappelons que chaque instruction produit un résultat, qui devra être rapatrié dans une ou plusieurs entrées de la fenêtre d'instruction. L'optimisation de la recherche directe par étiquette fonctionne dans le cas où le résultat de l'instruction n'est utilisé que par une seule entrée, et pas plusieurs.
Le principe est simple : chaque champ « opérande » d'une entrée est adressable via le nom de registre de cette opérande. Pour faire le lien entre entrée et nom de registre, la recherche directe par étiquette ajoute une table de correspondances matérielle, une mémoire RAM qui mémorise les adresses des champs « opérande » des entrées. Les adresses envoyées dans cette mémoire sont les noms de registres des résultats. Lors de l'émission, la table de correspondances est mise à jour. Les numéros des champs « opérande » réservés lors de l'émission sont mémorisés dans les mots mémoire qui correspondent aux deux registres sources. Toutefois, une petite vérification est faite lors de l'émission : si il y a déjà une correspondance dans la table pour un registre source, alors une autre instruction compte lire ce registre. Pour éviter d'écraser les données de l'instruction précédente dans la table de correspondances, l'émission de l'instruction est bloquée.
Les fenêtres d’instruction avec préplanification
modifierAvec la préplanification (prescheduling), la fenêtre d’instruction est composée de mémoires tampons dans lesquelles les instructions sont triées dans l'ordre d'émission. Il n'y a pas de logique d’émission proprement dite, celle-ci se bornant à vérifier si l'instruction située au début de la fenêtre d’instruction peut s’exécuter. Par contre, le préplanificateur détecte les dépendances et en déduit où insérer les instructions dans le tampon d'émission, à l'endroit le plus adéquat.
L'usage de FIFO multiples
modifierLa technique de préplanification la plus simple consiste à scinder la fenêtre d’instruction en plusieurs FIFO. Quand une instruction a une dépendance avec une instruction présente dans une FIFO, elle est ajoutée dans celle-ci. Ainsi, le préplanificateur se charge de détecter les chaines d'instructions dépendantes, et place les instructions d'une même chaine dans une seule FIFO. Si jamais l'instruction située au tout début de la FIFO n'a pas ses opérandes disponibles, alors la FIFO est bloquée : l'instruction attend que ses opérandes soient disponibles, bloquant toutes les instructions précédentes. Mais les autres FIFO ne sont pas bloquées, laissant de la marge de manœuvre.
La technique du tampon trié
modifierUne autre technique, celle du tampon trié, trie les instructions selon le temps d'attente avant leur exécution. Les instructions qui sont censées s’exécuter bientôt étant au tout début de cette mémoire, tandis que les instructions qui s’exécuteront dans un long moment sont situées vers la fin. Des techniques similaires se basent sur le même principe, avec un ordre des instructions différent, tel les relations de dépendance entre instructions.
Comme précédemment, si jamais l'instruction au bout de la mémoire, celle prête à être émise, n'a pas ses opérandes prêts, elle est mise en attente et bloque toute la fenêtre d’instruction. Le temps avant exécution dépend du temps de calcul de ces opérandes et du temps avant exécution des instructions qui produisent ces opérandes. Évidemment, celui-ci n'est pas toujours connu, notamment pour les instructions qui accèdent à la mémoire, ou des instructions dépendantes de celles-ci. Pour résoudre ce genre de problèmes, le préplanificateur doit faire des prédictions.
La technique du tampon de scoreboard
modifierLa technique précédente peut être améliorée. Au lieu de bloquer totalement la fenêtre d’instruction lorsqu'une instruction émise n'a pas tous ses opérandes disponibles, on peut réinsérer celle-ci dans la fenêtre d’instruction. Ainsi, l'instruction fautive ne bloque pas la fenêtre d’instruction, et retente sa chance autant de fois qu'il le faut.
Les techniques hybrides
modifierIl est possible de marier les techniques précédentes (préplanification et autres formes de fenêtres d’instruction), en utilisant deux fenêtres d’instruction : une gérée par la préplanification, et une autre pour les instructions dont le temps d'attente ne peut pas être déterminée.
Certains processeurs utilisent ainsi une fenêtre d’instruction qui précède la préplanification, afin de gérer les temps d'attente des instructions d'accès mémoire et des instructions dépendantes. Quand le temps d'attente d'une instruction devient connu, celle-ci est migrée dans le tampon de préplanification.
Une autre technique consiste à d'abord tenter de prédire le temps d'attente de l'instruction avec la préplanification, et de basculer sur une fenêtre d’instruction normale si la prédiction s’avère fausse.