Où vont les supercalculateurs ? D’où on vient, quels sont les problèmes, où l’on va (1re partie)

Posté par  . Édité par Davy Defaud, Ontologia, Thomas Debesse, Nÿco, patrick_g, Benoît, Yala, Benoît Sibaud et palm123. Modéré par Nÿco. Licence CC By‑SA.
121
5
juil.
2013
Technologie

Il y a un bail, j’avais dit que je voulais un jour parler des architectures haute performance, et de leur potentiel futur. Je me lance donc ici, en espérant que certains se permettront de me corriger là où j’aurai fait des erreurs (sans doute nombreuses).

Je vais diviser ces explications en trois parties. La première (qui suit juste après) va juste faire un rappel sur les architectures « séquentielles » de base. La deuxième partie (à venir très bientôt) s’occupera de décrire les systèmes multi‐processeurs et multi‐cœurs, ainsi que la raison de leur existence. J’en profiterai pour aussi expliquer les problèmes récurrents liés à l’exploitation de systèmes haute performance. La dernière partie parlera des efforts effectués en ce moment pour fabriquer les supercalculateurs du futur (disons à l’horizon 2020-2025).

Sommaire

Micro‐architecture de base d’un micro‐processeur

Le RISC, c’est bien

La plupart des architectures qui nous intéressent sont soit de type RISC (microprocesseur à jeu d’instructions réduit), soit de type VLIW (microprocesseur à mots d’instructions très longs). Bien qu’Intel garde (pour des raisons de compatibilité ascendante) un jeu d’instructions CISC (microprocesseur à jeu d’instructions étendu), en pratique, chaque instruction est ensuite décomposée en micro-instructions qui suivent les principes du RISC.

La plupart des processeurs traitent les données dans l’ordre qui suit :

Instruction FetchInstruction DecodeExecuteMemoryWrite Back

En d’autres termes : on cherche la prochaine instruction à exécuter (Instruction Fetch, IF) ; puis on la décode (Instruction Decode, ID) ; on l’exécute (EX) ; si besoin on accède à la mémoire (Mem) ; et enfin, on écrit le résultat (soit en mémoire, soit dans les registres).

L’intérêt de ce découpage par étages (en pipeline) est qu’on peut récupérer la prochaine instruction à exécuter pendant qu’on décode celle qui a été récupérée au cycle précédent, et qu’on exécute celle qui est venue encore avant. Ainsi, l’exécution de 1 000 instructions dans un pipeline de longueur n va prendre 1000 + n instructions. Les cinq étages précédemment décrits sont les étages de base d’un processeur RISC. Certains processeurs ont eu jusqu’à 33 étages (comme le [[Pentium 4]], par exemple). De nouveaux étages peuvent être introduits pour la multiplication et l’addition entières, par exemple. Les unités de calcul flottant peuvent aussi nécessiter l’ajout d’étages additionnels.

Tout un tas d’optimisations existent à ce niveau (si je n’ai pas besoin d’accéder à la mémoire, par exemple, je peux simplement « sauter » l’étage mémoire du pipeline, pour aller directement écrire les résultats dans les registres).

Cette utilisation d’un pipeline est cruciale. Avant, l’utilisation de jeux d’instructions CISC était nécessaire pour deux raisons :

  1. La mémoire des machines étant extrêmement limitée, proposer un ensemble d’instructions qui permette d’effectuer plusieurs opérations en une permettait de gagner en concision lors de l’écriture du code, et de gagner de précieux octets dans le programme.
  2. Il n’y avait pas réellement de pipeline comme expliqué précédemment. En revanche, chaque instruction CISC était optimisée pour prendre le moins de temps possible.

Tout ça fonctionne impec’, à une condition…

L’approche RISC décrite ci‐dessus permet de plus ou moins garantir l’exécution d’une opération par cycle. Enfin, dans le meilleur des cas. En pratique, un programme a besoin de pouvoir emprunter des chemins différents en fonction des paramètres. Et donc, il doit être possible pour un programme de « sauter » dans une autre partie du programme (oui, on parle bien d’un if … else …).

Ici, l’exécution au niveau du pipeline est problématique, puisque le if nécessite de savoir si la condition évaluée est vraie, il faut donc :

  1. évaluer la condition ;
  2. vérifier sa valeur de vérité (vrai / faux) ;
  3. potentiellement effectuer le branchement vers la partie du programme concernée.

Visiblement, le programme ne peut pas continuer à tourner tant que ces trois étapes ne sont pas accomplies. Et, du coup, le pipeline est « bloqué » — « stalled », en anglais. Pas de panique, des gens plutôt malins ont trouvé une réponse à ceci : on va prédire quelle branche de la conditionnelle sera prise. On va appeler ça de la prédiction de branchement, et le mécanisme qui la met en œuvre un prédicteur de branchement. Le fonctionnement est plutôt simple, et c’est bien pour ça qu’il est possible de mettre un mécanisme pareil directement dans le matériel. En gros, le prédicteur va faire un choix arbitraire au début, par exemple, tous les if sont prédits comme étant pris (c’est‐à‐dire que si l’on rencontre un if, on considère que la condition qu’il évalue sera considérée vraie par défaut).

Grâce à cela, on n’a pas besoin d’attendre que l’évaluation se termine : on continue l’exécution comme si de rien n’était. Évidemment, l’évaluation de la condition doit être terminée avant que les instructions qui suivent le bloc « if … else » qui ont été exécutées finissent par être réellement effectives.

Deux cas de figure se posent :

  1. la prédiction était juste, on n’a rien à faire, les instructions sont réellement effectuées — « retired », en anglais ;
  2. la prédiction était fausse, il faut purger l’intégralité des étages du pipeline qui suivent le branchement, puisque les résultats qui en découlent sont faux, eux aussi.

Sans trop entrer dans les détails, les prédicteurs de branchement sont mis en œuvre grâce à une machine à états et une petite mémoire. Pour chaque instruction de branchement conditionnel, la mémoire se souvient de l’adresse du if, puis détermine si la condition sera « vraie », « plutôt vraie », « plutôt fausse », ou « fausse » (je simplifie un maximum, bien entendu). On part d’un extrême (vrai ou faux). Cette méthode (et ses améliorations) fonctionne plutôt bien, principalement pour deux raisons. La première est le fait que prédire que le branchement conditionnel d’une boucle est toujours pris est, en règle générale, payant (la prédiction est fausse uniquement si la boucle est terminée). La deuxième est que, très souvent, les « motifs » d’exécution — « patterns », en anglais — sont similaires d’une exécution d’un programme à une autre. Par exemple, si un if est pris 7 fois sur 10 dans un programme, à cause de la nature du programme, le prédicteur de branchement, bien qu’agnostique en ce qui concerne le programme lui‐même, aura malgré tout fait gagner au final un temps précieux en termes d’exécution.

Il existe une autre façon de faire : la spéculation de contrôle, via l’utilisation de prédicats. C’est, par exemple, une approche qu’on retrouve dans les processeurs Itanium, un processeur superscalaire et VLIW produit par Intel, ou certaines versions des processeurs ARM.

Le principe est simple : lorsqu’un branchement conditionnel est rencontré, on peut « prédicater » (barbarisme couramment utilisé dans les labos info) les branches du if. Par exemple, quelque chose du genre :

if ( condition1:p0 )
    [p0] instruction1
    [p0] instruction2
else
    [!p0] instruction3
    [!p0] instruction4

Les instructions 1 à 4 vont toutes être exécutées. Cependant, leurs résultats ne seront réellement écrits qu’une fois la condition du if évaluée. Cette façon de faire permet de garantir une absence de stalls, et est souvent utilisée dans les processeurs VLIW (dont je ne parlerai pas trop ici, mais qui, en gros, prennent des « super‐instructions » en entrée, composés de plusieurs instructions, par exemple : { load r1 = @X; load r2 = @X+4; r3 = r5 * r6 }).

L’approche de la prédication est très intéressante, car, en gros, le compilateur ou le programmeur expert peuvent décider de ce qu’il faut « prédicater » ou pas.

Cependant, le bilan énergétique peut être désastreux (après tout, toutes les instructions sont exécutées, qu’elles soient utiles ou pas !).

Je crois qu’on a fait le tour des processeurs scalaires (en fait, la prédication est un mécanisme plutôt utilisé dans le type de processeurs qui va suivre).

Faites‐moi le plein de super !

Tout ceci concerne un micro‐processeur scalaire, avec un seul pipeline d’instructions. Rien n’empêche de dupliquer les pipelines. C’est d’ailleurs ce qui est fait dans beaucoup de micro‐processeurs modernes. On parle de processeurs superscalaires.

Ce genre de processeurs a besoin de correctement réserver les ressources dont il a besoin : additionneur pour calculer une adresse, UAL (unité arithmétique et logique) pour des calculs sur entiers, unité de calcul flottant (FPU), etc. Il peut bien y avoir deux pipelines, mais cela ne signifie pas qu’il y a deux UAL, par exemple… De même, l’accès à la mémoire depuis un micro‐processeur n’est peut‐être possible que via un seul port, et, du coup, si un chargement mémoire — load — doit être effectué dans un pipeline, il est parfaitement possible qu’un autre chargement soit bloquant dans l’autre pipeline. Dans un contexte d’exécution dans l’ordre — in order —, la qualité du compilateur ou du programmeur expert est déterminante pour obtenir un programme qui s’exécute au moins (en fait, il s’agit de correctement ordonnancer le flot d’instructions de manière statique).

Il existe plusieurs façons de régler le problème au niveau matériel. L’une d’entre elles est de passer par une méthode nommée scoreboarding. Pour faire simple, lorqu’une instruction écrit dans un registre donné, disons r1, alors l’état de r1 est marqué comme « occupé ». Les instructions suivantes sont lues, ainsi que leurs dépendances sur les différentes ressources : lorsqu’une instruction qui veut lire r1 est émise, elle est placée dans une file d’attente, tant que r1 est « occupé ». Lorsque r1 est de nouveau libre, — i.e. l’instruction qui écrit dans r1 est terminée et a signalé le scoreboard —, alors l’instruction est « remise en place » dans le flot et peut s’exécuter.

Une autre méthode (bien plus connue) est l’exécution dans le désordre, appelée out‐of‐order execution (OoO), en anglais. Dans cette méthode, il existe un ensemble de « registres fantômes » — shadow registers, en anglais. L’idée est de renommer les registres dont les instructions ont besoin, afin de réduire au maximum les dépendances entre instructions. Par exemple :

a = 10;
// ...
b = a * 3;
// ...
a = 0;
// ...
c = a + 12;

Si on suit un schéma de dépendance strict, il existe une anti‐dépendance (une dépendance de nommage) entre la première utilisation de a et la deuxième, alors qu’en pratique leur utilisation est complètement indépendante.

En renommant a pour sa seconde utilisation, on permet l’exécution en parallèle de l’affectation à b et c :

a  = 10 || a1 = 0;
// ...
b = a * 3 || c = a1 + 12

L’objectif n’est pas d’expliquer l’intégralité de l’algorithme de Tomasulo ou du renommage de variables pour faire disparaître les fausses dépendances, mais ceci devrait permettre de donner une idée de ce que fait un moteur OoO. Une fois tous ces renommages effectués, il faut ensuite réordonner le flot d’instructions. C’est à cela que sert le « reordering buffer », qui, comme son nom l’indique, sérialise le résultat des instructions, écrit dans les « vrais » registres (pour ensuite pouvoir écrire en mémoire, par exemple), etc.

Ce dont je ne parlerai pas

Il existe tout un tas d’autres mécanismes qui existent pour augmenter la performance d’un programme. Certains sont destinés à être utilisés par le compilateur ; d’autres par le programmeur.

Un exemple de mécanisme à la fonctionnalité identique sur plusieurs architectures différentes, et avec un usage différent, est celui des registres rotatifs. Pour faire simple, il s’agit d’un ensemble de registres « glissants » qui sont automatiquement renommés lorsque certaines instructions sont utilisées. Les processeurs de type SPARC utilisent ces registres pour accélérer les changements de contexte entre threads ou processus. Lorsqu’un thread est interrompu par le système, nul besoin de sauvegarder ses registres en mémoire (ce qui est une opération coûteuse) : si une fenêtre de registres est disponible, il suffit de faire « glisser » la fenêtre courante vers la nouvelle fenêtre, et y placer les informations du nouveau thread ou de la nouvelle fonction. Encore mieux, si le thread à exécuter était déjà interrompu, il suffit de re‐glisser vers sa fenêtre (en une instruction ou presque).

L’Itanium utilise le même concept de registres rotatifs pour une autre raison : l’optimisation de boucles via l’utilisation de la technique de pipeline logiciel. Je ne vais pas m’étendre sur la technique. Disons simplement qu’elle permet de maximiser les ressources disponibles et de recouvrir tout un tas de latences (notamment mémoire).

J’ai jusqu’ici soigneusement évité de parler de la mémoire en détails. Du point de vue du programmeur séquentiel, la mémoire est un ensemble de « cases » ayant un identifiant (une adresse) dans lesquelles on peut lire ou écrire des valeurs. La mémoire est, bien entendu, nécessaire car les registres sont en quantité limitée (32 ou 64 en moyenne pour les processeurs récents).

En pratique, malgré les progrès effectués pour produire de la mémoire rapide (on en est quand même à la DDR5…), il y a un fossé entre la fréquence d’horloge des micro‐processeurs modernes (qui souvent tourne autour de 2 ou 3 GHz) et celle de la mémoire (entre 0,8 et 1,3 GHz). Pour réduire l’impact de la latence due aux accès à la mémoire principale (DRAM), on a logiquement décidé d’insérer des niveaux de mémoire plus rapides. On retrouve la fameuse « antémémoire » que tout le monde appelle « mémoire cache ».

Exploiter la localité des données

La mémoire cache permet d’obtenir deux types d’optimisations :

Localité temporelle

En supposant qu’un ensemble de mots mémoire va être accédé séquentiellement (ou avec un pas régulier et relativement petit), alors charger une ligne de cache (souvent de 32 ou 64 octets dans les processeurs récents) permet de payer le coût du transfert mémoire (depuis la DRAM vers le cache) une seule fois, mais d’avoir plusieurs mots mémoire disponibles une fois le chargement effectué. Ainsi, si une boucle C suit le schéma d’accès mémoire suivant :

for (int i = 0; i < N-2; i += 2) {
    a[i+2] = f(i);
}

Alors, un entier (généralement 4 octets sur des architectures modernes) sera chargé ainsi (je fais une super simplification avec un pseudo‐langage assembleur ultra pas optimisé) :

    ; on considère que r0 vaut toujours 0
    mov r10, $N ; R10 = N
    mov r11, r0 ; i = 0 
    mov r12, @a
    cmp r11, r10
    jge EXIT
LOOP:
    ; Convention à la con : r2, ..., r6 sont les six premiers arguments 
    ; passés aux fonctions ... le reste est passé sur la pile
    ; r1 contient la valeur de retour des fonctions
    mov r2, r11      ; premier argument de f()
    call f           ; r1      = f(i)
    mul r2, r11, $4  ; r2      = i * 4
    add r5, r12, r2  ; calcul de l'adresse dans a: r5 = (a + i * 4)
    store [r5], r1   ; a[i+2]  = r1
    add r2, r2, $2   ; i      += 2
    cmp r11, r10
    jlt LOOP
EXIT:
    ; Suite du programme

Ainsi, si l’on a une ligne de cache de 64 octets, il y a 16 entiers de 4 octets directement accessibles par une boucle. Du coup, plutôt que charger (et payer) N/2 fois le coût de chargement depuis la DRAM, on va envoyer 16 mots (dont 8 seulement seront utiles ici). Je passe sur les détails architecturaux, mais grâce à la mise en cache, on va passer de latences de plusieurs dizaines, voire centaines de cycles (généralement entre 50 et 300, en fonction des architectures), à seulement entre 2 et 10 cycles par accès quand la donnée est située dans le cache.

Bref. Les caches permettent de recouvrir les latences lors des accès mémoire et d’accélérer significativement l’exécution d’un programme. C’est ce qu’on appelle la « localité temporelle » : payer le coût de chargement d’une ligne de cache permet d’accéder à a[i+2], a[i+4], …, a[i+14], et donc, alors que le « temps » avance (c’est‐à‐dire l’indice de boucle est incrémenté), les données restent « proches » du processeur.

Localité spatiale

La « localité spatiale » est le fait de pouvoir réutiliser plusieurs mots de la même ligne de cache d’affilée. Je ne m’étends pas dessus, mais, en gros, dans un code, si l’on arrive à réutiliser a[i] plusieurs fois, alors on économise en « espace » (en nombre de registres ou de lignes de cache à utiliser).

À noter qu’il existe aussi des caches pour stocker les instructions les plus fréquemment utilisées, ou même des caches unifiés (qui contiennent code et données).

Reste un problème : lorsqu’une donnée ne se trouve pas en cache — un défaut de cache communément appelé « cache miss » —, alors on paie de nouveau le chargement de 64 octets pour chercher la prochaine ligne de cache, au prix fort.

Spéculer sur le chargement des données

Pour résoudre le problème évoqué précédemment, les concepteurs de micro‐processeurs ont ajouté un mécanisme supplémentaire : les préchargeurs de mémoire. En gros, si l’on connaît les caractéristiques du matériel (latence pour accéder à la DRAM, taille des lignes de cache, etc.), et si le motif d’accès à la mémoire est suffisamment régulier, alors un bon programmeur/compilateur peut insérer des instructions de prefetch aux endroits stratégiques (généralement, on en insère plusieurs avant le début d’une boucle, puis juste au début d’une itération). C’est ce qu’on appelle le préchargement logiciel — software prefetching. On peut le retrouver sur la plupart des processeurs modernes (ARM, x86, SPARC, POWER/PowerPC…).

Il existe une variante mise en œuvre de façon purement matérielle, logiquement appelée préchargement matériel — hardware prefetching. Il existe plusieurs variantes, aussi je vais en détailler deux, actuellement utilisées au moins dans certains processeurs x86. La première est appelée par Intel « adjacent line prefetching » (préchargement de ligne adjacente). Lors d’un cache miss, une ligne de cache est (logiquement) chargée depuis la mémoire principale. Cette méthode s’assure que la ligne suivante est chargée elle aussi, car statistiquement il y a de fortes chances que le programme en aura besoin lui aussi. À ma connaissance, tous les processeurs x86 (AMD, Intel) mettent en œuvre cette technique. La seconde technique est au moins utilisée dans les processeurs Intel (je ne me suis pas renseigné pour AMD). Il s’agit du stride prefetcher (préchargement par pas). L’idée est simple : le stride prefetcher retient en mémoire une liste des n dernières adresses accédées par le processeur. Si, pour une adresse donnée, on y accède par pas de K (comme mon a[i+2] de tout à l’heure), alors le prefetcher détectera le motif d’accès, et s’occupera de précharger correctement les lignes de cache correspondantes au pas. Le stride prefetcher est limité aux accès au sein d’une même page mémoire (4 Kio ou 4 Mio sur x86).

Conclusions

Voilà, j’ai plus ou moins fini mon rappel rapide des micro‐processeurs séquentiels. Il faudrait aussi parler des instructions vectorielles (MMX, Streaming SIMD Extensions, AltiVec, etc.), mais ça prendrait encore quelques paragraphes et j’ai la flemme. La prochaine fois, on passe aux multi‐processeur et multi‐cœur !

Aller plus loin

  • # nerdgasm !

    Posté par  . Évalué à 10. Dernière modification le 05 juillet 2013 à 10:08.

    MOAR

    "Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)

  • # Chouette article

    Posté par  . Évalué à 10.

    Merci beaucoup pour ce très intéressant article!
    Bon courage pour la 2ème partie, j'attends impatiemment.

    F

  • # prédication ?

    Posté par  . Évalué à 6.

    Tous ces processeurs seraient-ils engagés dans un mouvement de prosélytisme religieux ?
    Auraient-ils pour objectif de rejoindre le GSPC (Groupe Salafiste pour la Prédication et le Combat) ?

    À moins, qu'ils ne fassent de la prédiction de branchement.

    • [^] # Re: prédication ?

      Posté par  . Évalué à 8.

      J'avais relié ça à

      (barbarisme couramment utilisé dans les labos info)

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

      • [^] # Re: prédication ?

        Posté par  . Évalué à 6.

        À la lecture de l'article, j'avais compris que «(barbarisme couramment utilisé dans les labos info)» s'appliquait à «prédicater» (probablement parce que «prédire» est trop simple :( )

        • [^] # Re: prédication ?

          Posté par  . Évalué à 4.

          Pour répondre aux deux en même temps : « prédication » et « prédicater » sont deux barbarismes en Français. Par contre les deux termes sont utilisés en permanence en Anglais et dans les labos info, de la même manière que tout le monde dit « je préfetche la ligne, là, et … », et pas « je précharge la ligne ». :)

  • # Alerte qualitay !

    Posté par  . Évalué à 9.

    Ça me rappelle mes cours d'architecture processeur. J'adorais ça et j'attends la suite avec impatience.

  • # Je maintiens qu'il y a une partie qui n'est pas terrible

    Posté par  . Évalué à 10.

    Je maintiens que "en pratique, chaque instruction est ensuite décomposée en micro-instructions qui suivent les principes de RISC." ne veut pas dire grand chose.

    On a dit souvent n'importe quoi sur le RISC, pourtant Reduced Instruction Set Computer, c'est assez clair: il s'agit d'un type de jeu d'instruction pour les processeurs.
    De quel jeu d'instruction parles t'on? Et bien de celui du CPU, et qu'a t'il de spécial? Il a été conçu pour être bien exploité par les compilateurs (avant c'était plutôt par les programmeurs).

    Dire que le coeur x86 a derrière un RISC bof, ça n'a pas grand sens:
    1) déjà c'est un jeu d'instruction qui est visible uniquement en interne: le compilateur n'y a pas accès.
    Ce qui pose des contraintes par exemple pour l'accès aux registres: le compilateur peut-être obligé d'écrire un registre x86 en mémoire pour faire de la place alors que le CPU a bien assez de registres physiquement présent, mais ils sont cachés et inaccessible directement..

    2) les micro instructions suivent-elle vraiment les principes du RISC: load-store, orthogonal (pour que le compilateur puisse l'utiliser efficacement), taille fixe des instructions (etc), peut-être, peut-être pas, vous avez la doc d'Intel vous?

    L'implémentation interne des x86 et des RISC est-elle similaire?
    Oui! Ca ne veut pas dire pour autant que les x86 ont derrière un RISC..

    • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

      Posté par  . Évalué à 10.

      La seule partie « CISC » du x86 est le couple fetch-decode du front-end. Pour les processeurs jusqu'à la micro-architecture Nehalem (6 pour Sandy Bridge et aussi sans doute Ivy Bridge, mais je n'ai pas suivi), le décodeur a quatre ports (considère ça comme des entrées) : le premier décode toutes les instructions CISC de 1 octet ou plus; les trois autres s'occupent d'instructions de 1 octet très exactement. Une fois le décodage effectué, tout le reste est bel et bien résolu comme pour une machine RISC classique. Il y a longtemps (plus de 10 ans), mon prof d'architecture appelait ça du « CRISC » : front-end CISC (étages IF et ID du pipeline), mais back-end RISC (tous les autres étages). Pour un processeur de type Pentium III ou Pentium 4, ça veut dire qu'environ 17% du temps est passé dans un module « CISC » (dans mes souvenirs le PIII avait 12 étages), et environ 6% pour le P4. En pratique comme les core 2 et suivants sont des évolutions du PIII, on peut toujours considérer que ~16-17% est passé dans le front-end.

      Dire que le coeur x86 a derrière un RISC bof, ça n'a pas grand sens:
      1) déjà c'est un jeu d'instruction qui est visible uniquement en interne: le compilateur n'y a pas accès. […]

      C'est vrai.

      Ce qui pose des contraintes par exemple pour l'accès aux registres: le compilateur peut-être obligé d'écrire un registre x86 en mémoire pour faire de la place alors que le CPU a bien assez de registres physiquement présent, mais ils sont cachés et inaccessible directement..

      Je suis d'accord pour x86 non 64 bits (où on n'avait en gros que 15 registres adressables). À partir du moment où tu peux utiliser rax, rbx, …, r15, et en plus de cela tu peux aussi utiliser les xmm0, …, xmm15 comme s'il s'agissait de registres scalaires, tu as bien 32 registres. Je n'ai pas les références en tête, mais en gros, il a été montré qu'avec les heuristiques actuelles pour l'allocation de registres et l'ordonnancement d'instructions, sauf à avoir des codes vraiment très réguliers (donc typiquement du HPC ;-)), on n'exploitait pas correctement les registres et les solutions d'allocation sont souvent sub-optimales (ce qui est « normal » en soit : l'ordonnancement des instructions et l'allocation des registres sont deux problèmes NP-complets).

      2) les micro instructions suivent-elle vraiment les principes du RISC […]

      Oui. Là-dessus il n'y a absolument pas de doute possible.

      Il faut bien comprendre qu'avant l'introduction de RISC, la seule façon (à ma connaissance) de garantir une instruction par cycle était à travers les processeurs vectoriels, tels que le (mythique) Cray-I. C'est-à-dire : on garantit que deux longs vecteurs de données (pour le Cray-I, il faut compter 2 vecteurs de 32 mots maximum chacun) sont chargés en mémoire. Ensuite on applique la même opération (+, -, etc.) sur deux mots (un de chaque vecteur), et on stocke le résultat quelque part pour chaque cycle. Bref, on garantit que les opérandes seront disponibles cycle après cycle pendant n cycles (32 par ex.). Comme on pouvait « chaîner » certaines opérations (multiplications puis additions par exemple), on pouvait gagner énormément de temps au final. Ici il s'agissait plus d'une sorte de « pipeline mémoire » que d'un pipeline d'instruction au final. Un système de masquage de bits permettait aussi de gérer les branchements conditionnels tant qu'au final une opération vectorielle allait s'effectuer.

      Ce qu'a apporté le RISC comparé aux processeurs vectoriels, c'est la possibilité d'effectuer des instructions différentes cycle après cycle, et garantir qu'en moyenne, on aura bien un débit proche de une instruction par cycle (certes, grâce à un programmeur très compétent, ou un compilateur très intelligent).

      Enfin, je vais donner un argument d'autorité :

      les micro instructions suivent-elle vraiment les principes du RISC […] peut-être, peut-être pas, vous avez la doc d'Intel vous?

      J'ai the next best thing : j'ai bossé (et je continue de bosser) avec Intel. Je ne suis pas un employé par contre. J'ai passé quelques années à faire des micro-benchmarks pour caractériser les architectures Intel (Core 2, Nehalem en particulier), et ça passait entre autres par des trucs du genre « faire des programmes "RISC" en x86 », « utiliser des instructions exotiques que personne n'utilise », « utiliser des instructions que le compilateur utilise souvent (genre lea) », etc.

      Ensuite, la page wikipedia (langue anglaise) n'est pas sourcée lorsqu'elle affirme que derrière le front-end, on a un moteur RISC. AnandTech est d'accord avec moi et Wikipedia cependant, et d'un point de vue très pratique, faire un pipeline qui n'accepte pas des mots de taille identique est quelque chose d'assez compliqué à réaliser (et les bénéfices en termes de performance peuvent être éclipsés par l'énergie nécessaire pour mettre en œuvre ce genre de solution).

      Ce n'est pas pour dire qu'il n'y a pas des optimisations possibles pour la partie CISC du processeur. Si tu vas sur le site d'Agner, tu verras qu'il décrit plutôt bien la façon dont les pipelines des différents processeurs sont réalisés. Il existe des mécanismes de « macro-fusion » qui permettent de « factoriser » certains traitements pour accélérer le décodage des instructions, etc.

      • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

        Posté par  . Évalué à 2.

        Tu noteras que
        1) à la fin de mon poste j'ai noté que l’implémentation interne des RISC et des CISC était similaire.
        2) Ça ne change rien vis à vis des jeux d'instructions externe.

        Pour faire une analogie avec programmation, tu mélange l'API (RISC/CISC) et l'implémentation (moteur similaire derrière), les x86 ont changé leur moteur, ça ne change rien à leur API: ne pas surcharger inutilement les mots, ça aide a garder les idées claires.

        • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

          Posté par  . Évalué à 6.

          Je comprends ce que tu veux dire, mais la raison pour laquelle beaucoup de gens (dont moi) disons que ce n'est pas du « vrai » CISC c'est vraiment parce que la notion de pipeline est intrinsèquement liée à l'architecture. Comme le dit Nicolas plus bas, Intel a mis le paquet pour que ça reste du CISC grâce à son décodeur (jusqu'à 6 instructions CISC décodées par cycle, c'est vraiment impressionnant).

          Donc d'un côté je comprends ce que tu veux dire, et je ne peux qu’acquiescer; de l'autre, l'ISA d'un processeur donne généralement de gros indices sur la façon dont un processeur est architecturé. Les deux exceptions (pour moi) sont Intel/AMD avec leur version de x86 (mais comme je le dis plus bas, dès qu'on parle de performance, on revient à des programmes de type RISC avec séparation load/store et arithmétique), et Nvidia (avec PTX, une ISA « virtuelle », et dont la programmation « à la main » peut accélérer fortement les perfs d'un programme pour une version donnée d'une carte, et devenir médiocre pour la version suivante).

          En pratique, lorsque j'avais du code SIMD à écrire en assembleur, je devais l'écrire « façon RISC » pour obtenir la meilleure performance (donc séparation des load/store, des opérations arithmétiques, etc.). C'est aussi (à mon avis) parce qu'il s'agit d'instructions plus récentes et qui donc peuvent se « permettre » d'être plus proche de la « réalité architecturale ».

        • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

          Posté par  . Évalué à 6.

          Historiquement, il y avait beaucoup plus d'imbrication entre l'architecture d'un microprocesseur et son jeu d'instruction, surtout pour les CISC. C'est à cette époque que les concepts de RISC et de CISC ont commencé à être utilisé, et ses concepts aurait été relativement inintéressants s'il n'y avait pas eu un impact très fort sur l'architecture. Ainsi ces termes n'étaient pas confinés à la caractérisation du jeu d'instruction, mais servaient bien souvent à évoquer les avantages et inconvénients de l'architecture interne. Depuis le x86 a bien évolué et utilise en interne des tas de méthodes qui sont apparus sur RISC, et même pour certaines méthodes qui définissait en partie le RISC à une époque. C'est cela qu'on veut dire quand on note que le coeur des x86 modernes ressemble plus à du RISC qu'à du CISC; et même si on peut certes considérer cela comme un petit abus de langage, il est très largement répandu et très facilement compréhensible.

          C'est aussi quelque chose de primordial à saisir pour appréhender les obstacles qu'a le x86 à franchir pour la basse consommation, et comprendre que la stratégie que suit Intel depuis quelques années de tout miser sur le x86 n'est pas la plus facile…

          • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

            Posté par  (site web personnel) . Évalué à 4.

            Le x86 peut contenir des sortes de vliw internes, c'est un peu loin des RISC.

            "C'est aussi quelque chose de primordial à saisir pour appréhender les obstacles qu'a le x86 à franchir pour la basse consommation,"

            Le seul problème de intel est son inertie interne, c'est tout. Le x86 a par exemple une densité de code très élèvé, supérieur à l'arm. C'est un avantage énorme pour l'efficacité du cache de code.

            Intel a sorti, il y a 5 ans, un cpu qui consommait en sommeil autant qu'un ARM à 100%. Aujourd'hui, le dernier soc intel (medfield) consomme moins, soit moins de mips par watt qu'un arm.

            "La première sécurité est la liberté"

            • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

              Posté par  (site web personnel) . Évalué à 3.

              Les architectures CISC sont en effet en général plus puissants que les RISC, du moins à caractéristiques égales mais ont tendance à consommer plus.
              Car si Intel a rattrapé ARM au niveau de la consommation, de mémoire les processeurs ARM modernes ont une finesse de gravure bien plus élevée et utilisent des tensions internes plus hautes (à vérifier mais de mémoire c'est le cas).
              ARM pourrait technologiquement descendre plus bas encore.

              Intel perd beaucoup avec l'x86 notamment à cause de la compatibilité ascendante qui a un coût sur la puissance et la consommation globale de ses puces. Si Intel pouvait former une nouvelle architecture propre, moderne et correctement construite, les gains pourraient être énormes. Et je pense que Intel sera obligé de la faire un jour tellement la concurrence avec ARM va grossir notamment sur ses propres marchés, l'ARM64 devenant quelque chose d'intéressant sur les plates formes traditionnelles de INtel.
              Et si l'ARM parvient à bousculer l'x86, cela facilitera la création d'une autre architecture Intel car els programmes seront à cause de ARM multi-architectures et la compatibilité ascendante perdra de l'intérêt…

            • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

              Posté par  . Évalué à 4.

              Concernant l'efficacité du cache, même pas sûr: cf les caches de uops présent sur certains modèles, et considérant que sans ça il faut retraduire d'une manière coûteuse à chaque fois. Il faudrait vraiment avoir accès au design et à ses analyses pour savoir.

              Peut être bien qu'ils commencent aujourd'hui à pouvoir techniquement concurrencer ARM sur certains points (et ce en profitant de l'avance qu'ils ont en terme de process pour compenser des defaults). Sauf que c'est trop tard, et il va falloir attendre le prochain retournement de situation pour qu'ils reviennent potentiellement dans la course.

              Il ne faut pas non plus oublier que ça n'est même pas la partie conso en charge qui est la plus critique, mais la conso en veille. Et là ARM a pu évoluer très rapidement vers les besoins vu l'écosystème beaucoup plus ouvert. Même chose si on sort de la conso pour s’intéresser au fonctionnel: ça a été largement plus facile d’inonder le marché avec pléthores de SoC différenciés sur les périphs et qui licencient tous un cœur ARM facilement.

              • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

                Posté par  (site web personnel) . Évalué à 7.

                Les caches de µops étaient utilisé dans le Pentium 4, mais plus maintenant, cela prendre trop de place (sauf gestion de boucle).

                Si ARM ne fait pas quelques intructions spécialisées pour tout ce qui est couteux (controlleur DRAM dans le cpu, instruction pour les hash, la compression, la crypto), ils se feront manger par intel.

                La consomation statique est gérée depuis longtemps : on coupe tout (horloge et alimentation). Les téchniques sont connues. Le vrai défi c'est la consommation max à 100%, quand on navigue sur internet ou joue a Angry Bird.

                "La première sécurité est la liberté"

                • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

                  Posté par  . Évalué à 2.

                  La conso en veille à différents niveau n'est pas si facile que ça et il y a encore eu des ajouts récents il y a pas longtemps par Intel. Le problème n'est pas le régime permanent mais l'optimisation des transitions et le fonctionnement avec diverses parties de coupées mais pas d'autre, ou encore avec des horloges dynamiques dans divers domaine, voire des idées complètement exotiques (big.LITTLE). La conso à bloc absolu aura un impact plus faible sur le marché que la conso en veille ou hybride (pour un workload de navigation web par exemple); même si évidemment c'est pas non plus négligeable, ce n'est pas un argument marketing principal (et de loin) à l'heure actuelle, et pour que ça le devienne il y a du chemin à parcourir entre Intel et le consommateur… (et encore qu'il accroche une fois que ça arrive jusqu'à lui, même s'il y a nombre de joueurs sur téléphone / tablette, c'est plutôt un marché casual…)

      • [^] # Re: Je maintiens qu'il y a une partie qui n'est pas terrible

        Posté par  (site web personnel) . Évalué à 3.

        Le cœur des processeur x86 récent n'est pas vraiment RISC. Les instruction CISC sont bien décodées vers des micro-instructions qui forment un jeu plus simple que celui de l'architecture de base, mais ça reste loin d'être du RISC.

        Il suffit de regarder les manuel de Agner que cite pour s'en convaincre. La majorité des instructions sont décodé vers une seule micro-instruction qui en garde donc toute la complexité.

  • # Prédiction de branchement

    Posté par  . Évalué à 10.

    À noter qu'il est possible d'indiquer au compilateur quelle branche va être empruntée la plupart du temps. C'est fait dans le noyau Linux par exemple, au moyen de la directive __builtin_expect de GCC. Des macros likely et unlikely sont définies () :

    # define likely(x)      __builtin_expect(!!(x), 1)
    # define unlikely(x)    __builtin_expect(!!(x), 0)

    Elle sont utilisées de la façon suivante :

    if(unlikely(condition_tres_probablement_fausse)) {
        ...
    } else {
       ...
    }
    • [^] # Re: Prédiction de branchement

      Posté par  (site web personnel) . Évalué à 3.

      # define likely(x) __builtin_expect(!!(x), 1)
      # define unlikely(x) __builtin_expect(!!(x), 0)

      Une idée pour le "!!(x)" ?
      C'est pour être sûr que ce qui est passé en paramètre est un booléen ? (i.g. !42 == 0, !0 == 1 ==> !!(42) == 1 )

      • [^] # Re: Prédiction de branchement

        Posté par  . Évalué à 7.

        Oui, !!x c'est le (bool)x du pauvre.

        C'est pas super joli mais moins pire que x && 1…

        Please do not feed the trolls

      • [^] # Re: Prédiction de branchement

        Posté par  (site web personnel) . Évalué à 4.

        Disons que c'est pour transformer en bool et non être sûr que ce qui est passé en paramètre est un booléen.

        C'est par exemple assez utilisé en javascript pour savoir si un paramètre a été rentré ou non (ou ne possède pas une valeur fausse), même si c'est une string ou n'importe quoi d'ailleurs.

        Par exemple (fictif) :

        function overrideConfigKey(str) {
          var hasStr = !!str;
          // à partir de là tu peux utiliser hasStr comme un booléen
          config.isOverriden = hasStr;
          config.value = str;
        }
    • [^] # Re: Prédiction de branchement

      Posté par  (site web personnel) . Évalué à 4.

      J'aimerais bien voir des exemples où ça fait une difference notable (et sur quel archi) parce qu'à chaque fois que j'ai essayé de l'utiliser ça a été non mesurable

      • [^] # Re: Prédiction de branchement

        Posté par  . Évalué à 7.

        Je suppose que sur un CPU qui ne fait pas de prédiction de branchement, ou très mal c'est efficace, mais les x86 sont à plus de 90% de prédiction correcte depuis 10 ans… donc ouai ça sert à rien. Sur la doc gcc ils disent d'ailleurs que les programmeur sont particulièrement mauvais pour dire comment leur code va se comporter.

        Please do not feed the trolls

  • # Petite erreur

    Posté par  (site web personnel) . Évalué à 10.

    N'y aurait-il pas une erreur dans cette partie du code ?

    a  = 10 || a1 = 0;
    // ...
    b = a * 3 || c = a + 12
    

    Ne faudrait-il pas écrire ceci ?

    a  = 10 || a1 = 0;
    // ...
    b = a * 3 || c = a1 + 12
    
  • # oups mal lu

    Posté par  . Évalué à 2. Dernière modification le 05 juillet 2013 à 12:38.

    heureusement je peux effacer mes betises.

  • # De la nécessité de faire des incantations vaudou sur le code

    Posté par  (site web personnel) . Évalué à 8.

    Je continue à penser (mais peut être ais-je tord) que de nombreux mécanismes sont implémentés au sein du microprocesseur pour modifier le code que le compilateur n'a pas été capable d'optimiser tout seul.
    L'échec de l'Itanium est patent sur ce point.

    Un travail sur le code généré pour mieux exploiter les localité de code pourrait être largement améliorées si les compilateur avaient une meilleur connaissance sémantique du code (analyse de flot). Sachant que dans GCC, on peut préciser très précisément quel processeur on a en face, ce serait surement très profitable.

    le problème viens aussi du côté bas niveau du code, faire comprendre à un compilateur la sémantique de

    for (int i = 0; i < N-2; i += 2) {
        a[i+2] = f(i);
    }

    n'est pas évident, mais possible tant qu'on ne fasse pas de magie avec l'arithmétique des pointeurs.

    L'exemple d'une multiplication de matrice est typique : il faut éviter un parcours en long et en large et faire en sorte de "tiler" c'est à dire de construire des sous blocs rectangle et de paralléliser les calculs dessus.

    De là, il faut être capable de produire une IA capable de réécrire l'algorithme permettant de gérer ces blocs de lignes de cache, en étant sûr de la sémantique.
    Cela suppose d'avoir un langage assez pur pour permettre d'être absolument sûr de ce qu'on fait.

    Si un jour, on a ce genre d'outil, pléthore de transistors dédiés à modifier le code assembleur à la volée pourrait être consacré à faire un cœur de calcul de plus..

    « Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker

    • [^] # Re: De la nécessité de faire des incantations vaudou sur le code

      Posté par  . Évalué à 10. Dernière modification le 05 juillet 2013 à 14:03.

      Je pense que l'échec de l'Itanium tient juste au fait que trop de client d'Intel ont beaucoup de très vieux code assembleur ou qu'ils ne peuvent pas recompiler, et que l'itanium est la première puce qui ne permet pas d'accélérer l'exécution en émulant la génération précédente.

      Détecter les dépendances entre les instruction et les réordonner, gérer le prefetch… les compilateurs font déjà ça très bien, en statique.

      Encore une fois, le plus gros frein dans ce domaine me semble le logiciel privateur. Un code open source on recompile et basta, si c'est bien fait ça marche. Mais si on doit attendre le bon vouloir d'un éditeur pour porter son code sur une nouvelle architecture, ben on reste avec des vieux binaires pourris et on compte sur Intel pour faire une puce plus rapide, plus intelligente et qui assure la rétro compatibilité.

      petit ajout : Quand le gros goulet étranglement se trouvera être la cohérence des caches, ça va être rigolo. Là pour la première fois les puces feront un truc en moins, les compilateur géreront ça très bien, sans l'ombre d'un doute. Et le vieux code qui se repose dessus ne fonctionnera plus.

      Please do not feed the trolls

    • [^] # Re: De la nécessité de faire des incantations vaudou sur le code

      Posté par  (site web personnel) . Évalué à 7.

      le problème viens aussi du côté bas niveau du code, faire comprendre > à un compilateur la sémantique de

      for (int i = 0; i < N-2; i += 2) {
      a[i+2] = f(i);
      }

      n'est pas évident, mais possible tant qu'on ne fasse pas de magie > avec l'arithmétique des pointeurs.

      Alors qu'on compilateur aura beaucoup plus de faciliter à comprendre

      a[2:] = map(f, range(len(a) - 2)

      On peut déterminer si map est parallèle et si ça vaut le coup de paralléliser, en poussant un peu le bouchon et suivant l'utilisation faite de a par la suite, on peut transformer le map en itertools.imap etc

      J'avais même fait l'expérience avec un étudiant, et l'écriture d'un code C sans connaître toute les ficelles donnait lieu à un code moins performant que le même algo décrit dans un langage de haut niveau qu'un compilo a pu optimiser. Et en mettant un expert, il remettait la nique au compilo d'ailleurs (ouf!).

      L'idée sous-jacente serait qu'il est plus facile d'encoder du savoir faire spécifique dans un compilo pour un langage de haut niveau que dans un compilo pour langage de bas niveau. Un peu ce qui se passe avec les DSLs d'ailleurs.

    • [^] # Re: De la nécessité de faire des incantations vaudou sur le code

      Posté par  . Évalué à 6.

      Cela suppose d'avoir un langage assez pur pour permettre d'être absolument sûr de ce qu'on fait.

      Tiens sur ce type de sujet, ce papier est intéressant ( http://people.csail.mit.edu/jrk/halide12/ ), il décrit un découplage entre les algorithmes et les "recettes" d'implémentations.

    • [^] # Re: De la nécessité de faire des incantations vaudou sur le code

      Posté par  . Évalué à 6.

      Juste une remarque sur l'Itanium : il faut bien comprendre que la première génération était tout simplement ratée : Intel/HP étaient très en retard sur les délais, et ont dû sortir « un truc qui marche ». Bref, quand le Merced est sorti, bien sûr ça marchait, mais franchement, c'était plus un prototype qu'autre chose.

      Lorsque l'Itanium 2 est sorti, il s'agissait de ce que la première génération aurait dû être, mais le mal était déjà fait. De plus, la chaîne de compilation d'Intel a mis deux générations à correctement utiliser les registres rotatifs pour effectuer un bon software pipelining.

      Concernant ton exemple de la multiplication de matrices, j'ai envie de dire que c'est un « faux bon exemple » : le compilateur d'Intel (et probablement gcc, mais je n'ai pas vérifié) sait désormais appeler les routines BLAS qui vont bien une fois qu'il reconnaît un « idiome ».

      Par contre une particularité d'icc pour IA64 était que, si on pouvait appeler des intrinsics, il était absolument impossible d'inliner de l'assembleur (ça provoquait une erreur de compilation).

      Enfin, avec les versions 8 et ultérieures, icc avait des performances trois fois meilleures que gcc (je parle de la période 2005-2010). C'est normal cependant : un Itanium 2 ça coûte cher, c'est rare, et ce n'est utile que pour le calcul scientifique (les performances pour du calcul entier sont quand même assez minables). Forcément, optimiser gcc-IA64 n'était pas la priorité pour GCC…

  • # quelques trucs !

    Posté par  (site web personnel) . Évalué à 7. Dernière modification le 05 juillet 2013 à 16:01.

    "en pratique, chaque instruction est ensuite décomposée en micro-instructions qui suivent les principes de RISC."

    Le ISC de RISC et CISC, veut dire "instruction set computer". On parle du jeu d'instruction et pas du tout de la micro-architecture interne. Le but premier des mips et sparc, les 1ers RISC, était de réduire la taille du pipeline en ayant l'unité de load/store bien séparé de l'ALU. Le nombre réduit de mode d'adressage (le moyen de choper les data, ici par registre quasi exclusivement) était aussi un avantage de simplicité.

    "Decode → Execute → Memory" n'est clairement pas un "risc".

    Aujourd'hui, le x86 est toujours plus complexe, et il me semble que la grammaire de l'assembleur x86 est ambiguë. Le décodeur est donc un monstre, ce qui permet à intel de mettre une sacré barrière à l'entré sur son marché. De l'autre coté le PowerPC dispose de plein de mode d'adressage, qui' l'éloigne aussi du risc. La taille des instructions des nouveaux ARM est mixte 16/32 bits.

    On peut lire que les principes RISC ont "gagné", mais je pense surtout que les RISC sont devenus des CISC.

    "simplement « sauter » l'étage mémoire du pipeline"

    C'est simplement impossible car la mémoire peut aussi générer des données. Ce genre de bypass implique que l'étage suivant à des entrées multiples, et c'est tout, sauf simple.

    "VLIW : Cependant, le bilan énergétique peut être désastreux (après tout, toutes les instructions sont exécutées, qu'elles soient utiles ou pas !)."

    C'est même pire que ça, puisque le cpu peut passer sont temps à exécuter des instructions inutiles. Le fait de ne pas pouvoir faire correctement d’ordonnancement statique par le compilateur, en sans doute la raisons principale de l'échec de l'itanium qui devait prendre le relève du x86 pour le 64 bits. Mais il fallait des caches internes monstrueux à l'itanium pour aller vraiment plus vite que les Xeon (sauf en flottant).

    " Les processeurs de type SPARC utilisent ces registres pour accélérer les changements de contexte entre threads/processus."

    Perdu, les banc de registres à fenêtre , c'est pour les appels de fonctions. Sur SPARC, il y a 32 registres, 8 globaux et 3 paquets de 8 glissant sur une fenêtre de 128 registres ou plus. A chaque call, tout se décale de 8 registres, cela permet d'offrir 8 registres tout frais, sans faire de sauvegarde dans la pile (moins besoin de registre "callee save"). Par contre, une fois qu'il n'y plus de place, il y a une interruption pour tout sauver en mémoire. sur de gros programme avec beaucoup d'appel, cela peut tuer l'avantage de la simplification des fonctions.

    "La première sécurité est la liberté"

    • [^] # Re: quelques trucs !

      Posté par  . Évalué à 5.

      La taille des instructions des nouveaux ARM est mixte 16/32 bits.

      Des anciens ARM tu veux dire: ARMv8 a perdu le mode 16-bit..
      MIPS a aussi une extension 16 bit si ma mémoire est bonne.

      mais je pense surtout que les RISC sont devenus des CISC.

      Les RISCs se sont complexifiés OK, mais entre des instructions 16/32 bits et des instructions a longueurs très variable comme les x86 et VAX (d'un octet à plusieurs dizaines d'octets pour les cas pathologiques (qu'on ne trouve pas en pratique)), il y a quand même une grosse différence..
      Et même si le PowerPC a beaucoup de modes d'adressages, ça reste quand même une architecture avec des instructions séparée pour accéder à la mémoire, je pense que le qualificatif load/store s'applique toujours.

      • [^] # Re: quelques trucs !

        Posté par  (site web personnel) . Évalué à 2.

        Je pensais à thumb2 pour arm.

        La dizaine d'octet se trouve surtout avec les instructions qui peuvent avoir 2 adresses mémoire immédiat en dure. C'est long mais pas trop complexe.

        "La première sécurité est la liberté"

        • [^] # Re: quelques trucs !

          Posté par  . Évalué à 3.

          Je pensais à thumb2 pour arm.

          J'avais compris et je disais que Thumb/Thumb2 n'existe pas le mode 64bit d'ARM: ARMv8.

          • [^] # Re: quelques trucs !

            Posté par  . Évalué à 1.

            Au quel cas, ça donne quand même un avantage de compacité de code à x86_64 sur les processeurs à nombreux coeurs (comme les Xeon Phi).
            En effet, avec 61 coeurs, on gagne énormément à avoir tout le code+données de travail qui tienne dans la mémoire cache du coeur (512K pour le Xeon Phi), puisque sinon, le bus de la RAM serait saturé.

            En plus, un des défauts des CISC modernes (pipeline profond) est partagé par les ARM modernes.

    • [^] # Re: quelques trucs !

      Posté par  . Évalué à 4.

      Perdu, les banc de registres à fenêtre , c'est pour les appels de fonctions

      Oups. L'idée était la bonne, la finalité non. Ensuite pour le « flush » oui, il faut bien le faire à un moment s'il n'y a plus de fenêtre de registre disponible. Je n'en ai pas parlé, mais parce que je trouvais ça évident.

      Merci pour la correction !

    • [^] # Re: quelques trucs !

      Posté par  . Évalué à 5.

      "Decode → Execute → Memory" n'est clairement pas un "risc".

      J'ai oublié de relever : ça par contre je ne comprends pas. C'est un peu l'archétype de l'architecture RISC. Il y a simplement des moyens de faire un « forward » d'un étage à l'autre. C'est aussi ce qu'explique la page Wikipedia sur le pipeline RISC classique, ainsi que le Hennessy et Patterson (Hennessy est le monsieur derrière MIPS, donc je pense qu'il sait de quoi il cause…).

      Donc l'étage « EX » peut être simplement le calcul d'adresse, et « Mem » l'accès effectif à cette adresse. Ensuite, « WB » sera utilisé ou pas (par exemple, si on fait un load, il faut bien écrire la valeur chargée dans un registre quelconque…).

      • [^] # Re: quelques trucs !

        Posté par  (site web personnel) . Évalué à 2.

        Je pense aussi que je me trompe, et que l'étage Ex dans le cas d'un load sert à faire des calculs d'adresse.

        Dans un cpu moderne, c'est complètement séparé.

        "La première sécurité est la liberté"

  • # c'est peut être pas parfait

    Posté par  . Évalué à 5.

    Mais pour un néophyte tel que moi, c'est que du plaisir : )

    Vivement la suite !

  • # Principe de localité

    Posté par  (site web personnel) . Évalué à 9.

    Euh, tu n'aurais pas inversé la description des principes de localité pour la mémoire cache ? Normalement :
    - La localité spatiale signifie que l'on travaille généralement sur des données groupées en mémoire, donc si on a accédé à une adresse, on va probablement accéder juste après aux adresses proches. Ceci correspond à ton exemple de boucle où tu accèdes successivement à des indices différents du tableau a[i].
    - La localité temporelle signifie que quand on travaille sur une donnée, on effectue généralement plusieurs opérations dessus, donc on accède plusieurs fois à la même donnée à des intervalles de temps rapprochés. Ceci correspondrait à utiliser plusieurs fois le même a[i].

    Dans ton article, tu dis le contraire alors que Wikipedia dit la même chose.

    • [^] # Re: Principe de localité

      Posté par  . Évalué à 5.

      Si, aussi. C'est le problème d'écrire bourr… tard le soir. Tu as tout à fait raison : réutiliser la même donnée en mémoire relève de la temporalité, alors que bénéficier du fait qu'une ligne de plusieurs mots sont chargés lors d'un cache miss relève de la spatialité.

  • # Commentaire bookmark

    Posté par  . Évalué à 6.

    Je profite du sujet pour indiquer aux curieux la série d'articles de LWN sur la mémoire.
    Le premier.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.