cursus.

Cours 4 · Mémoire et périphériquesLeçon 2 sur 2

Entrées/sorties et performance

5 h de lecture9 sections Version PDF

À la fin de cette leçon, vous saurez

Contrôleurs et bus ; scrutation, interruptions et accès direct à la mémoire ; mesure de performance et loi d'Amdahl ; introduction au pipeline et au parallélisme.

Un processeur à 3 GHz exécute environ trois milliards d'opérations élémentaires par seconde. Entre deux frappes au clavier d'un utilisateur rapide, il en a le temps de trois cents millions. S'il attendait cette frappe en surveillant le clavier, il consumerait trois cents millions de cycles à ne rien faire d'utile.

Ce dernier chapitre traite de cette disproportion, et de la question qu'elle pose : comment une machine dialogue-t-elle avec un monde des milliers de fois plus lent qu'elle sans s'y aligner ? Puis il donne les outils pour répondre à la question qui clôt le cours : cette machine, finalement, est-elle rapide ?

Contrôleurs et bus

Le processeur ne parle jamais directement à un périphérique. Entre les deux se trouve un contrôleur, circuit spécialisé qui connaît les détails électriques et mécaniques du matériel et présente au processeur une interface uniforme : quelques registres.

Trois registres suffisent à décrire presque tout contrôleur. Un registre de commande, où le processeur écrit ce qu'il veut. Un registre d'état, qu'il lit pour savoir où en est l'appareil — prêt, occupé, en erreur. Un registre de données, par lequel l'information transite.

Reste à savoir comment le processeur atteint ces registres. Deux écoles. Les entrées/sorties mappées en mémoire leur donnent des adresses ordinaires, dans le même espace que la mémoire : un lw ou un sw du chapitre 6 suffit, sans instruction spéciale, et une partie de l'espace d'adressage est réservée à cet usage. Les entrées/sorties par ports leur donnent un espace d'adressage séparé, avec des instructions dédiées. La première approche domine, parce qu'elle n'ajoute rien au jeu d'instructions — et le cours de systèmes montrera qu'elle permet à un pilote de manipuler un périphérique comme une structure en mémoire.

Trois façons d'attendre

Voilà le cœur du chapitre. Le processeur a demandé une lecture au disque ; celle-ci prendra dix millisecondes, soit trente millions de cycles. Que fait-il pendant ce temps ?

La scrutation (polling). Il boucle sur le registre d'état jusqu'à ce que le bit « prêt » passe à 1. C'est trivial à programmer et parfaitement inefficace : trente millions de cycles brûlés à relire la même case. La scrutation garde pourtant deux usages légitimes — les systèmes sans interruptions, et les périphériques si rapides que le détour par une interruption coûterait plus cher que l'attente.

Les interruptions. Le processeur lance l'opération et passe à autre chose. Quand le périphérique a fini, il lève un signal sur le bus de contrôle. Le processeur termine l'instruction en cours, sauvegarde son état — compteur ordinal, registres, registre d'état —, exécute la routine de traitement de cette interruption, puis restaure l'état et reprend exactement où il en était.

Ce mécanisme est bien plus qu'une optimisation d'entrées/sorties : c'est la condition d'existence du multitâche. Une interruption d'horloge périodique permet au système d'exploitation de reprendre la main à intervalles réguliers, donc de retirer le processeur à un programme qui ne le rend pas. Le chapitre 4 du cours de systèmes en fera le fondement de la préemption.

L'accès direct à la mémoire (DMA). Les interruptions règlent l'attente, pas le transfert : sans DMA, le processeur doit encore copier lui-même chaque mot du contrôleur vers la mémoire. Pour un fichier de dix mégaoctets, c'est deux millions et demi de transferts. Le contrôleur DMA prend ce travail en charge — le processeur lui indique adresse, taille et sens, puis retourne à son calcul ; une seule interruption signale la fin du bloc entier.

MéthodeAttenteTransfertInterruptions par bloc
Scrutationprocesseur bloquépar le processeur0
Interruptionsprocesseur librepar le processeur1 par mot
DMAprocesseur librepar le contrôleur1 par bloc
Quiz · vérifiez votre compréhension Sans réponse

Un système utilise déjà les interruptions pour ses entrées/sorties disque, mais reste lent lors du chargement de gros fichiers. Quelle explication est correcte ?

Mesurer la performance

Le chapitre 5 a donné la formule ; il est temps de s'en servir et d'en tirer les conséquences.

T=Ninstructions×CPIfT = \frac{N_{\text{instructions}} \times \text{CPI}}{f}

Trois facteurs, et trois responsables différents. Le nombre d'instructions dépend du programme, du compilateur et du jeu d'instructions. Le CPI dépend de la microarchitecture — un processeur pipeliné vise 1, voire moins. La fréquence dépend de la technologie de gravure.

D'où la mise en garde centrale : comparer deux machines sur leur seule fréquence n'a pas de sens, puisqu'il manque deux facteurs sur trois. Un processeur à 2 GHz avec un CPI de 1 est plus rapide qu'un processeur à 3 GHz avec un CPI de 2.

Les indicateurs synthétiques ont le même défaut. Les MIPS — millions d'instructions par seconde — ne comparent que des machines de même jeu d'instructions, sans quoi on compte des instructions qui ne font pas la même chose. Les FLOPS ne mesurent que le calcul flottant. Seule mesure honnête : le temps d'exécution d'un programme réel et représentatif de l'usage visé, ce que cherchent les jeux d'essai normalisés comme SPEC.

La loi d'Amdahl

Reste le résultat le plus utile du chapitre, et le plus souvent ignoré en pratique. On accélère une portion d'un programme d'un facteur kk ; cette portion représente une fraction pp du temps total. L'accélération globale vaut :

A=1(1p)+pkA = \frac{1}{(1-p) + \dfrac{p}{k}}

Le passage à la limite est brutal. Si kk tend vers l'infini — la portion devient instantanée — l'accélération plafonne à 1/(1p)1/(1-p). Ce qu'on n'accélère pas fixe le plafond.

Un exemple, avec p=0,6p = 0{,}6 et k=10k = 10 : on divise par dix les trois cinquièmes du temps, et l'accélération globale n'est que de 1/(0,4+0,06)=2,171/(0{,}4 + 0{,}06) = 2{,}17. Même en rendant cette portion instantanée, on ne dépasserait jamais 2,5.

Ces valeurs supposent une accélération INFINIE de la portion visée : ce sont des plafonds, jamais atteints. Un programme parallélisable à 90 % ne dépassera pas un facteur 10, quel que soit le nombre de cœurs — c'est le seul chiffre à retenir avant d'acheter une machine à 64 cœurs.

La leçon pratique tient en une phrase : mesurer avant d'optimiser. Diviser par cent une fonction qui occupe 2 % du temps fait gagner 2 %. L'intuition du programmeur sur l'endroit où son programme passe son temps est, statistiquement, fausse.

Pipeline et parallélisme

Une dernière question : comment descendre le CPI en dessous de 1 par instruction ?

Le pipeline répond en découpant l'exécution en étages — typiquement recherche, décodage, exécution, accès mémoire, écriture du résultat — et en les faisant travailler simultanément sur des instructions différentes. C'est la buanderie : pendant que le second tambour lave, le premier sèche, et le troisième se plie. La latence d'une instruction n'a pas diminué, mais le débit est multiplié par le nombre d'étages.

Trois obstacles, appelés aléas, l'empêchent d'atteindre ce facteur idéal. Les aléas de données : une instruction a besoin d'un résultat que la précédente n'a pas encore écrit. Les aléas de contrôle : après un branchement, on ne sait pas quelle instruction charger — et le chapitre 6 a montré que les branchements sont partout, d'où la prédiction de branchement, qui parie sur l'issue et annule les instructions engagées si le pari est perdu. Les aléas structurels : deux étages veulent la même ressource matérielle au même cycle.

Au-delà du pipeline, trois formes de parallélisme se cumulent. Le superscalaire duplique les unités pour lancer plusieurs instructions par cycle. Les instructions vectorielles appliquent une même opération à plusieurs données à la fois. Et le multicœur met plusieurs processeurs complets sur la même puce — la voie choisie depuis 2005, quand l'augmentation des fréquences s'est heurtée au mur de la consommation thermique.

Le multicœur ramène directement à Amdahl, et c'est sur ce lien que le cours se referme : doubler le nombre de cœurs ne double la performance que si le programme est parallélisable d'un bout à l'autre. Il ne l'est jamais. La partie séquentielle — lecture du fichier d'entrée, synchronisation entre tâches, écriture du résultat — impose son plafond, et c'est elle, pas le matériel, qui décide de ce que la machine vaudra.

Quiz · vérifiez votre compréhension Sans réponse

Un programme passe 80 % de son temps dans une portion parfaitement parallélisable. Quelle accélération peut-on espérer au maximum, avec un nombre illimité de cœurs ?

À vous

L'exercice met en chiffres les deux résultats du chapitre. D'abord la loi d'Amdahl, appliquée à plusieurs scénarios d'optimisation, dont un piège classique : accélérer énormément une portion minuscule. Ensuite un compteur de cycles de pipeline, qui compare l'exécution séquentielle, le pipeline idéal, et le pipeline avec les bulles qu'imposent les aléas.

Le second calcul donne le chiffre que le cours cherchait depuis le chapitre 5 : le CPI réel, et l'écart entre la promesse du pipeline et ce qu'il tient.

Exercice · JavaScript · à vous de jouer

Calculez l'accélération d'Amdahl, puis le CPI réel d'un pipeline à cinq étages.

En attente
// ── 1. La loi d'Amdahl ────────────────────────────────────────────────────
// p : fraction du temps occupée par la portion accélérée
// k : facteur d'accélération de cette portion
function amdahl(p, k) {
  return 0;   // ← à écrire : 1 / ((1 - p) + p / k)
}

const SCENARIOS = [
  { nom: "60 % du temps, accéléré x10", p: 0.60, k: 10 },
  { nom: "60 % du temps, accéléré x1000", p: 0.60, k: 1000 },
  { nom: "2 % du temps, accéléré x100", p: 0.02, k: 100 },
  { nom: "95 % du temps, accéléré x8", p: 0.95, k: 8 },
];

for (const s of SCENARIOS) {
  const a = amdahl(s.p, s.k);
  const plafond = 1 / (1 - s.p);
  console.log(s.nom.padEnd(34) + "accélération x" + a.toFixed(2) +
              "   (plafond x" + plafond.toFixed(1) + ")");
}

// ── 2. Cycles d'un pipeline ───────────────────────────────────────────────
// n instructions, e étages. Sans pipeline chacune prend e cycles. Avec
// pipeline, le premier résultat sort après e cycles puis un par cycle —
// sauf quand un aléa impose des bulles.
function cycles(n, e, bulles) {
  const sequentiel = n * e;
  const pipeline = 0;   // ← à écrire : e + (n - 1), plus les bulles
  return { sequentiel, pipeline };
}

// Le programme du chapitre 6 : 12 instructions exécutées, dont 3 branchements.
// Un branchement mal prédit coûte 2 bulles.
const N = 12, ETAGES = 5, BRANCHEMENTS = 3;

// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Écrivez amdahl et cycles.
// 2. Comparez le CPI avec une prédiction parfaite, puis avec une prédiction
//    qui se trompe une fois sur deux. Que devient l'écart au pipeline idéal ?

const r = cycles(N, ETAGES, 0);
console.log("séquentiel :", r.sequentiel, "cycles | pipeline idéal :", r.pipeline);

Console de sortie
Le résultat s'affiche dans la console

En travaux pratiques

Travaux pratiques 8 · sur machine

Ce que coûte de sortir du processeur

Mesurer le prix d'un appel système et d'un accès disque, pour ranger dans le bon ordre les grandeurs qui décident de la performance d'un programme réel.

2 h
Avant de commencer
  • Le TP 7 : ordres de grandeur des accès mémoire
  • strace, ou un équivalent traceur d'appels système
  1. 1. Un octet à la fois

    Écrivez un mégaoctet dans un fichier, octet par octet, avec l'appel système write. Chronométrez.

  2. 2. Par blocs

    Recommencez en écrivant par blocs de 4096 octets. Chronométrez et calculez le rapport.

  3. 3. Compter les appels

    Comptez les appels système des deux versions avec strace. Divisez le temps gagné par le nombre d'appels évités : vous obtenez le coût d'un appel système.

  4. 4. Le tampon gratuit

    Refaites la version octet par octet, mais avec fputc au lieu de write. Chronométrez, comparez aux deux précédentes, et expliquez.

  5. 5. Forcer l'écriture

    Ajoutez un appel à fsync après chaque bloc, puis mesurez à nouveau. Expliquez l'effondrement, et dites quel programme accepte ce coût.

  6. 6. La table des grandeurs

    Rassemblez vos mesures des TP 7 et 8 dans un seul tableau : accès L1, mémoire centrale, appel système, lecture disque, aller-retour réseau. Ramenez tout à la même unité.

C'est réussi quand
  • Vous mesurez le coût d'un appel système à quelques centaines de nanosecondes près
  • Vous savez expliquer pourquoi fputc ne coûte pas un appel système par caractère
  • Votre tableau de grandeurs couvre au moins six ordres de grandeur

Ce que la suite en fait

Ce chapitre clôt l'architecture, mais il ouvre le cours de systèmes d'exploitation, et de façon très directe. L'interruption d'horloge y devient l'ordonnancement préemptif du chapitre 4. Le contrôleur et ses registres y deviennent le pilote de périphérique du chapitre 8. Les entrées/ sorties mappées en mémoire y rencontrent la pagination du chapitre 6.

Et la disproportion par laquelle ce chapitre commençait — trois cents millions de cycles entre deux frappes — y trouve sa véritable réponse : elle ne se comble pas, elle se remplit, en donnant le processeur à un autre programme pendant l'attente. C'est le multitâche, et c'est tout le sujet du cours suivant.

À retenir

Flashcards · 1 / 5Toucher pour retourner
Fin de la leçon

Vous avez parcouru les 9 sections.

Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.