Architecture des ordinateurs

Cette leçon présente les concepts fondamentaux de l'architecture des ordinateurs qui influencent directement le calcul numérique. Comprendre comment fonctionne un processeur permet de mieux anticiper les limitations et les comportements des algorithmes numériques.

Vue d'ensemble d'un ordinateur

Un ordinateur moderne est composé de plusieurs éléments qui travaillent ensemble :

ComposantRôleImpact sur le calcul
CPU (processeur)Exécute les instructionsVitesse des calculs
RAM (mémoire vive)Stocke les données activesTaille des problèmes traitables
Stockage (SSD/HDD)Stockage permanentPersistance des données
BusConnexions entre composantsDébit des transferts

Le processeur (CPU)

Le CPU (Central Processing Unit) est le "cerveau" de l'ordinateur. C'est lui qui exécute les instructions de vos programmes, y compris les opérations mathématiques.

Les transistors : la brique de base

Un processeur est construit à partir de milliards de transistors — de minuscules interrupteurs électroniques qui peuvent être "allumés" (1) ou "éteints" (0). Ces transistors sont organisés en circuits logiques qui effectuent des opérations.

AnnéeProcesseurNombre de transistors
1971Intel 40042 300
1993Pentium3,1 millions
2010Intel Core i7731 millions
2023Apple M2 Ultra134 milliards

Cette augmentation exponentielle du nombre de transistors (connue sous le nom de loi de Moore) a permis d'augmenter la puissance de calcul de manière spectaculaire.

La fréquence d'horloge (GHz)

Le processeur fonctionne au rythme d'une horloge qui cadence les opérations. La fréquence est mesurée en GHz (gigahertz) :

  • 1 GHz = 1 milliard de cycles par seconde
  • Un processeur à 3 GHz effectue 3 milliards de cycles par seconde
💡

Attention aux simplifications

Un cycle d'horloge ne correspond pas forcément à une opération complète. Certaines instructions prennent plusieurs cycles, d'autres peuvent s'exécuter en parallèle. La fréquence seule ne détermine pas la performance.

Les cœurs (cores)

Les processeurs modernes contiennent plusieurs cœurs — des unités de calcul indépendantes capables d'exécuter des instructions en parallèle.

  • Un processeur 4 cœurs peut théoriquement exécuter 4 tâches simultanément
  • Un processeur 8 cœurs peut en exécuter 8, etc.
detecter_coeurs.pypython
import os

# Nombre de cœurs logiques disponibles
nb_coeurs = os.cpu_count()
print(f"Ce système a {nb_coeurs} cœurs logiques")

Impact sur le calcul numérique : Pour exploiter plusieurs cœurs, il faut que le programme soit conçu pour le parallélisme. Un algorithme séquentiel n'utilisera qu'un seul cœur, même sur un processeur 16 cœurs.

L'unité arithmétique et logique (ALU)

Au cœur du processeur se trouve l'ALU (Arithmetic Logic Unit) — le circuit qui effectue réellement les calculs.

Opérations de base

L'ALU peut effectuer :

Opérations arithmétiques :

  • Addition, soustraction
  • Multiplication, division
  • Opérations sur les nombres flottants (via la FPU)

Opérations logiques :

  • ET, OU, NON, XOR
  • Comparaisons (égalité, supérieur, inférieur)
  • Décalages de bits

La FPU : calculs en virgule flottante

La FPU (Floating Point Unit) est une partie spécialisée du processeur dédiée aux opérations sur les nombres à virgule flottante. Elle implémente la norme IEEE 754 que nous étudions dans ce cours.

⚠️

Coût des opérations

Toutes les opérations n'ont pas le même coût. La division est significativement plus lente que la multiplication. C'est pourquoi on préfère souvent multiplier par l'inverse plutôt que diviser.

OpérationCycles approximatifs
Addition entière1
Multiplication entière3-4
Addition flottante3-5
Multiplication flottante4-6
Division flottante10-20
Racine carrée15-30

La mémoire et la hiérarchie des caches

Le problème de la vitesse mémoire

Le processeur est beaucoup plus rapide que la mémoire RAM. Si le CPU devait attendre la RAM pour chaque donnée, il passerait la majorité de son temps à attendre.

Solution : Une hiérarchie de caches — des mémoires intermédiaires de plus en plus rapides (mais de plus en plus petites).

La hiérarchie des caches

NiveauTaille typiqueLatenceLocalisation
Registres~1 Ko1 cycleDans le CPU
Cache L132-64 Ko~4 cyclesPar cœur
Cache L2256 Ko - 1 Mo~12 cyclesPar cœur
Cache L38-64 Mo~40 cyclesPartagé
RAM8-128 Go~200 cyclesExterne

Impact sur le calcul numérique

L'organisation des données en mémoire a un impact majeur sur les performances :

cache_impact.pypython
import numpy as np
import time

n = 10000

# Matrice stockée en mémoire
A = np.random.rand(n, n)

# Parcours par lignes (suit l'ordre mémoire - RAPIDE)
start = time.time()
somme_lignes = 0
for i in range(n):
  for j in range(n):
      somme_lignes += A[i, j]
temps_lignes = time.time() - start

# Parcours par colonnes (saute en mémoire - LENT)
start = time.time()
somme_colonnes = 0
for j in range(n):
  for i in range(n):
      somme_colonnes += A[i, j]
temps_colonnes = time.time() - start

print(f"Parcours par lignes  : {temps_lignes:.3f} s")
print(f"Parcours par colonnes: {temps_colonnes:.3f} s")
print(f"Ratio: {temps_colonnes/temps_lignes:.1f}x plus lent")
💡

Localité des données

Les algorithmes qui accèdent aux données de manière séquentielle (localité spatiale) ou qui réutilisent les mêmes données (localité temporelle) sont beaucoup plus efficaces car ils exploitent mieux les caches.

Représentation binaire des nombres

Pourquoi le binaire ?

Les transistors n'ont que deux états : allumé ou éteint. Il est donc naturel de représenter l'information en base 2 (binaire) :

  • 0 = transistor éteint (basse tension)
  • 1 = transistor allumé (haute tension)

Le bit et l'octet

  • Bit (binary digit) : la plus petite unité d'information (0 ou 1)
  • Octet (byte) : groupe de 8 bits

Avec bits, on peut représenter valeurs distinctes :

BitsValeurs possiblesPlage (non signé)
8 bits0 à 255
16 bits0 à 65 535
32 bits0 à ~4,3 milliards
64 bits0 à ~18 quintillions

Types de données et leur taille

En Python, NumPy expose explicitement les types avec leur taille :

types_numpy.pypython
import numpy as np

# Entiers
print("=== Entiers ===")
print(f"int8  : {np.iinfo(np.int8).min} à {np.iinfo(np.int8).max}")
print(f"int16 : {np.iinfo(np.int16).min} à {np.iinfo(np.int16).max}")
print(f"int32 : {np.iinfo(np.int32).min} à {np.iinfo(np.int32).max}")
print(f"int64 : {np.iinfo(np.int64).min} à {np.iinfo(np.int64).max}")

# Flottants
print("\n=== Flottants ===")
print(f"float16 : précision ~3 chiffres, max ~{np.finfo(np.float16).max:.0e}")
print(f"float32 : précision ~7 chiffres, max ~{np.finfo(np.float32).max:.0e}")
print(f"float64 : précision ~15 chiffres, max ~{np.finfo(np.float64).max:.0e}")

Architecture 32 bits vs 64 bits

Qu'est-ce que ça signifie ?

L'architecture 32 ou 64 bits fait référence à :

  1. La taille des registres : les registres 64 bits peuvent stocker des nombres plus grands
  2. La largeur du bus d'adresses : détermine la quantité maximale de RAM adressable
  3. La taille native des opérations : les opérations sur 64 bits sont directes
Aspect32 bits64 bits
RAM maximale4 Go16 exaoctets (théorique)
Entier natif (~2 milliards) (~9 quintillions)
Pointeurs4 octets8 octets

Impact sur Python

Python utilise par défaut des entiers 64 bits sur les systèmes modernes. Les entiers Python peuvent même dépasser cette limite grâce à l'arithmétique en précision arbitraire :

entiers_python.pypython
import sys

# Taille d'un petit entier
petit = 42
print(f"Taille de {petit} : {sys.getsizeof(petit)} octets")

# Taille d'un grand entier (précision arbitraire)
grand = 2**1000
print(f"Taille de 2^1000 : {sys.getsizeof(grand)} octets")
print(f"2^1000 a {len(str(grand))} chiffres décimaux")

Implications pour le calcul numérique

1. Précision finie

Le nombre de bits limite la précision des calculs. Avec 64 bits pour un flottant :

  • ~15-16 chiffres décimaux de précision
  • Plage de à

2. Vitesse des opérations

Certaines opérations sont intrinsèquement plus lentes :

  • Préférer les multiplications aux divisions
  • Les opérations vectorisées exploitent mieux le matériel (SIMD)
  • L'accès mémoire peut être le goulot d'étranglement

3. Parallélisme

Pour exploiter les processeurs multi-cœurs :

  • Utiliser des bibliothèques parallélisées (NumPy, SciPy)
  • Concevoir des algorithmes parallélisables
  • Attention aux problèmes de synchronisation

4. Localité mémoire

L'organisation des données affecte les performances :

  • Stocker les données de manière contiguë
  • Parcourir les tableaux dans l'ordre de stockage
  • Réutiliser les données tant qu'elles sont en cache
💡

À retenir

L'architecture matérielle impose des contraintes fondamentales sur le calcul numérique :

  • Précision finie → erreurs d'arrondi inévitables
  • Mémoire limitée → taille des problèmes bornée
  • Hiérarchie mémoire → importance de la localité des données
  • Multi-cœurs → opportunités de parallélisme

Comprendre ces contraintes permet d'écrire des algorithmes plus efficaces et d'anticiper leurs limitations.

Pour aller plus loin