Intel Core 2 Duo

Publié le 22/06/2006 par et
Imprimer
Les branchements
Les branchements constituent, après les accès mémoire, le second plus important facteur de ralentissement dans le fonctionnement du processeur en cas de mauvaise prédiction.

Un branchement consiste, dans un flux d’instructions, en un saut vers une nouvelle adresse dans le code. Deux types de branchements existent :
  • Les branchements directs pour lesquels l’adresse du saut est explicitement mentionnée dans le code, sous la forme d’une opérande. L’adresse de destination est résolue lors de la compilation. Les branchements directs sont dans la plupart des cas des sauts de boucle.

  • Les branchements indirects sautent vers une adresse qui varie dynamiquement au cours de l’exécution du programme, les destinations possibles sont donc multiples. On les retrouve par exemple dans les tests de type « switch / case », et sont également souvent utilisés dans les langages orientés objets, sous forme de pointeurs de fonctions.
  • Qu’ils soient directs ou indirects, les branchements constituent un obstacle dans le fonctionnement optimal du pipeline de traitement. Dès lors que l’instruction de saut entre dans le pipeline, celui-ci ne peut, en théorie, plus accueillir de nouvelle instruction tant que l’adresse de destination n’est pas calculée, c’est-à-dire lorsque l’instruction de saut atteint les dernières étapes de traitement. Le pipeline insère alors des bulles, ce qui nuit fortement à son rendement. Le rôle du prédicateur de branchement est donc d’essayer de deviner l’adresse de destination, afin que les instructions suivant le saut soient chargées sans tarder.

    Il n’existe en fait pas un mais plusieurs prédicateurs. Le plus simple et le plus ancien est le prédicateur statique, dont le fonctionnement repose sur l’assertion que la branche sera toujours prise ou à l’inverse jamais prise. Ainsi, dans une boucle, le mécanisme statique prédit correctement tous les sauts, sauf le dernier ! Son taux de succès dépend bien entendu du nombre d’itérations.

    Le prédicateur statique montre ses limites dans les branchements de type si … alors … sinon, pour lesquels il a une chance sur deux de se tromper. Le processeur a alors recours à une prédiction dynamique, qui consiste à stocker un historique des résultats des branchements dans une table (la BHT : branch history table). Lorsqu’une branche est rencontrée, la BHT stocke le résultat du saut, et si la branche est prise l’adresse de destination est stockée dans un buffer dédié, le BTB (branch target buffer) (si la branche n’est pas prise aucune adresse n’est évidemment stockée, car la destination est alors l’instruction suivant la branche). Deux types de prédicateurs dynamiques existent au sein d’un processeur, qui se différencient par la portée des branches dont ils stockent l’historique, et ce afin d’améliorer la granularité du mécanisme de prédiction.

    L’action combinée des prédicateurs statiques et dynamiques offrent, selon la taille des buffers de stockage, un taux de succès de la prédiction entre 95 et 97% sur les branches directes. En revanche leur efficacité tombe à environ 75% de prédictions correctes sur les branches indirectes, qui, du fait de la multiplicité des destinations possibles, ne sont pas adaptées au stockage de l’information binaire « pris / non pris » des BHT. Mobile a ainsi inauguré un mécanisme de prédiction de branchements indirects. Le prédicateur stocke dans le BTB les différentes adresses auxquelles le branchement a abouti, ainsi que le contexte qui a conduit à cette destination (c’est-à-dire les conditions qui ont accompagné le saut). La décision du prédicateur n’est donc plus limitée à une adresse en cas de branche prise, mais en une série de destinations « préférées » de la branche indirecte. La méthode donne de bons résultats mais est très consommatrice de ressources, le BTB contenant alors plusieurs adresses par branche.

    Mobile a également introduit une technique innovante appelée « détecteur de boucle ». Ce détecteur scrute les branches à la recherche du schéma typique de fonctionnement d’une boucle : toutes les branches prises sauf une (ou l’inverse, selon la condition de sortie). Si ce schéma est détecté, une série de compteurs est affectée au branchement concerné, assurant ainsi un taux de succès de 100%.

    Core bénéficie bien entendu de tous ces raffinements, en plus de petites améliorations diverses, mais sur lesquelles aucune information n’a filtré.
    Les mécanismes de fusion
    Core comporte un certain nombre de techniques qui visent, pour un nombre d’instructions donné, à réduire le nombre de micro-opérations générées. Effectuer la même tâche avec moins de micro-opérations, cela signifie la faire plus rapidement (augmentation de l’IPC) tout en consommant moins d’énergie (augmentation de la performance par watt consommé).

    Initialement introduite sur Mobile, la micro-fusion est l’une de ces techniques. Voyons en quoi elle consiste sur un exemple, l’instruction x86 : add eax,[mem32].
    Cette instruction effectue en réalité deux opérations distinctes : une lecture mémoire, et une addition. Elle sera ainsi décodée en deux micro-opérations :
    load reg1,[mem32]
    add reg2,reg1
    Cette décomposition suit également la logique de l’organisation du processeur : la lecture et l’addition sont prises en charge par deux unités différentes. Dans un schéma classique, les deux micro-opérations seront traitées dans le pipeline, le moteur OOO s’assurant de gérer les dépendances.
    La micro-fusion consiste dans notre cas en l’existence d’une « super » micro-opération se substituant aux deux précédentes, en l’occurrence :
    add reg1,[mem32]
    Ce n’est ainsi plus qu’une unique micro-instruction qui traversera le pipeline de traitement. Lors de l’exécution proprement dite, une logique dédiée à la gestion de cette micro-opération sollicitera de façon parallèle les deux unités concernées. La méthode présente en outre l’intérêt de nécessiter moins de ressources (un seul registre interne est désormais nécessaire dans notre exemple).

    Core ajoute à cette technique celle de la macro-fusion. Là où la micro-fusion transforme deux micro-opérations en une seule, la macro-fusion décode deux instructions x86 en une seule micro-opération. Elle intervient donc en amont de la phase de décodage, à la recherche de paires « fusionnables » dans la file d’attente des instructions. A titre d’exemple, la séquence d’instruction :
    cmp eax,[mem32]
    jne target
    est détectée comme telle, et se décode en la seule micro-opération :
    cmpjne eax,[mem32],target
    Cette micro-opération bénéficie d’un traitement de faveur car elle est prise en charge par une ALU améliorée, capable de la traiter en un seul cycle (si la donnée [mem32] est dans le cache L1).


    Une unité de calcul améliorée de Core est en charge des micro-opérations issues de la macro-fusion.

    Il est assez délicat de quantifier le gain de performance apporté par ces mécanismes de fusion. Cela étant, nous avons pu mesurer sur Yonah qu’en moyenne 10% des instructions sont micro-fusées ce qui réduit d’autant le nombre de micro-opérations à traiter. On peut estimer sans trop de risque que l’utilisation simultanée de la macro-fusion étend cette proportion à plus de 15%.
    Vos réactions

    Top articles