• Aucun résultat trouvé

Pipeline d’exécution

Dans le document Architecture, Système, Réseau 1 Protopoly (Page 86-89)

Les bases

L’idée est simple : c’est celle du travail à la chaîne.

Faisons pour commencer l’hypothèse simplificatrice que notre processeur ne sait faire que des additions et des soustractions. On suppose donc qu’on n’a qu’une unité d’exécution, et on oublie momentanément les accès mémoire.

Alors, pendant que le processeur charge l’instructionndu programme (instruction fetchouIF), il peut décoder l’instructionn−1(decodeouD), lire les registres pour l’instructionn−2(operand loadouOL), exécuter l’instructionn−3(executeouEX), et écrire le résultat de l’instructionn−4 dans le registre destination (writebackouWB).

Exemple : On a du matériel qui fait l’IF en 1ns, une lecture et une écriture des registres en 1ns, et toutes les opérations en 1ns. Dessiner le diagramme d’exécution (enxle temps, eny les instructions dans l’ordre du listing du programme) pour le programme suivant :

R1 <- add R2,R3 R4 <- sub R5,1 R6 <- add R7,R8

On définit ainsi les étages du pipeline. Techniquement jusque là c’est très simple, il suffit d’avoir un paquet de registres pour chaque étage, qui transmet l’instruction en cours d’un étage à l’autre. Remarquez qu’il faut transmettre le long du pipeline toutes les infos utiles à la vie future de l’instruction, par exemple son registre de destination.

9.4. PIPELINE D’EXÉCUTION 87 Les accès mémoire

Maintenant parlons des accès mémoire. On va déjà supposer qu’on a un accès mémoire séparé programme/donnée, pour pouvoir EX une instruction mémoire en même temps qu’on IF une instruction subséquente. En pratique, on a effectivement deux accès mémoire distincts lorsqu’on a des mémoires caches séparées pour les instructions et pour les données (ICache et DCache). On verra en 12.1 que cela a plein d’autres avantages. Admettons pour le moment.

Ensuite on peut considérer qu’une instruction mémoire est une instruction comme les autres, qui s’exécute dans l’étage EX. Cela pose deux petits problèmes : le premier est que c’est vraiment une mécanique différente des autres qui s’occupe des accès mémoire. Le second est : adieu les modes d’adressages compliqués.

Donc on va plutot dire que toutes les instructions passent par un étage supplémentaire, dit MEM, après l’étage EX. Ciel, cela ajoute un cycle au temps d’exécution de chaque instruction. Oui mais on est en pipeline : si on considère le temps d’exécuter tout le programme de 100000 cycles, cela ne lui ajoute qu’un cycle aussi. Dessinez des chronogrammes de pipelines jusqu’à avoir bien compris la phrase précédente.

Les aléas de la vie

Considérons le programme suivant :

1 R0 <- xxxx // adresse du vecteur X 2 R1 <- yyyy // adresse du vecteur Y 3 R2 <- nnnn // taille des vecteurs

4 R3 <- 0

On a plein dedépendancesdans ce programme. On parle de dépendance lorsqu’une instruction dépend pour s’exécuter du résultat d’une instruction précédente. Une autre manière de voir est que s’il n’y a pas de dépendance entre deux instructions, on peut les exécuter dans n’importe quel ordre. Ou en même temps (en parallèle).

On distingue

dépendance de donnée qui sont de trois types :

— lecture après écritureouRAW. Par exemple l’instruction 7 doit attendre pour lire la va-leur de R11 que l’instruction 6 ait écrit cette vava-leur dans R11.

— écriture après lectureouWAR. Par exemple l’intruction 10 ne peut pas modifier R0 tant que l’instruction 5 n’a pas fini de la lire.

— écriture après écriture ouWAW. On n’en a pas ici, mais si on avait deux lignes com-mencant par R1 <- ..., on ne pourrait pas les échanger au risque de changer la sémantique du programme.

Seule la première (RAW) est une vraie dépendance, c’est à dire une dépendance d’un pro-ducteur vers un un consommateur. Les deux autres sont dites “fausses dépendances”, car elles ne proviennent que du fait qu’on a un nombre de registres limités. On pourrait tou-jours les supprimer en utilisant un autre registre la seconde fois, si on avait un nombre infini de registres. Par exemple, la dépendance WAR de 5 à 10 se résolverait en utilisant R100 à la ligne 10 (et en renommant R100 toutes les occurences suivantes de R0 dans le pro-gramme). Cette notion est importante à comprendre, car on va avoir plein de techniques

matérielles qui reviennent à renommer les registres pendant l’exécution du programme pour ne pas être gêné par les fausses dépendances.

Attention, il peut y avoir des dépendances de données entre deux instructions à travers d’une case mémoire. C’est plus difficile à détecter puisque cela ne se voit pas forcément à la compilation du programme.

dépendance de contrôle typiquement pour savoir quelle branche on prend en cas de saut conditionnel. Par exemple on ne peut pas décider ligne 12 du saut tant que la ligne 11 n’a pas produit la valeur du drapeau Z.

dépendance de ressources (encore une fausse dépendance qui pourrait être supprimée par plus de matériel) : si par exemple une lecture mémoire fait deux cycles et n’est pas pipeli-née, l’exécution de l’instruction 6 dépend de ce que l’instruction 5 ait libéré la ressource.

Et pourquoi je vous embête avec cette notion dans le chapitre sur le pipeline ? D’une part parce que c’est une notion centrale en informatique (elle parle de la sémantique d’un programme).

D’autre part parceque dans le pipeline, le W arrive longtemps après le R. Dans le cas de deux ins-tructions consécutives avec une dépendance RAW, si on ne fait pas attention, le W de la première pourrait se dérouler après le R de la seconde, qui lirait alors n’importe quoi.

On gère les dépendances

1. d’abord en les détectant. Convainquez-vous que c’est relativement facile, bien que coûteux en matériel. En fait, c’est à cela que sert l’étage ID(Instruction Decode)entre IF et EX. Sinon, dans notre processeur RISC orthogonal, le décodage d’instruction était quasi trivial. Le plus coûteux serait de gérer les dépendances de données à travers la mémoire, mais pour le moment le problème ne se pose pas car lecture et écriture mémoire se font dans le même étage, donc dans l’ordre du programme).

2. ensuite en bloquant le pipeline (plus d’IF) en cas de dépendance jusqu’à ce que l’instruc-tion bloquante ait avancé suffisamment pour que la dépendance soit résolue. On parle aussi d’insérer une bulle dans le pipeline (est-ce une métaphore ou une allégorie ?).

3. enfin en ajoutant descourt-circuitsà notre pipeline. Les deux principaux sont

— un court-circuit des données : on avait un signal qui remontait le pipeline pour al-ler stocker dans la boite à registre le résultat des opérations durant WB. Ce signal est désormais espionné par l’étage EX, qui y repérera les couples (numéro de registre, va-leur) qui l’intéressent. Ainsi, une dépendance de données ne bloque plus une instruc-tion dans l’étage OL mais dans l’étage EX. En fait, on va même court-circuiter l’étage MEM pour les instructions qui ne font pas d’accès mémoire, mais attention alors aux conflits possibles entre un couple qui viendrait de l’étage EX et un couple qui viendrait de l’étage MEM au même cycle. Les court-circuits se voient bien sur le diagramme temporel.

— Un court-circuit des drapeaux vers l’étage IF.

4. Une dernière technique pour gérer les dépendances de contrôle est l’exécution spéculative.

On prédit le résultat d’un branchement, on charge le pipeline en fonction de la prédiction, et quand on s’est trompé on doit jeter des instructions en cours d’exécution (fastoche, c’est le filresetdes registres du pipeline, et aucune instruction n’est définitive tant qu’elle n’a pas écrit dans les registres). En terme de cycles perdus, c’est comme si on avait bullé sur la dépendance de contrôle, mais on ne paye ce prix que lorsqu’on s’est trompé dans la prédiction.

Et comment qu’on prédit ?

— Heuristique simple : on prédit qu’un branchement est toujours pris. C’est (presque) vrai pour les branchements en arrière à cause des boucles, et vrai une fois sur deux pour les branchements en avant. Si on a toutes les instructions conditionnelles (ARM, IA64), les branchements en avant pour cause deifsont rares, et la prédiction est bonne a plus de 90% (1/nnnndans notre programme ci-dessus).

— On utilise un prédicteur de branchement : petit automate à 4 états (2 bits) associé à chaque adresse de branchement par une fonction de hachage simple (typiquement les

9.5. EXPLOITATION DU PARALLÉLISME D’INSTRUCTION : SUPERSCALAIRE 89

Dans le document Architecture, Système, Réseau 1 Protopoly (Page 86-89)