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 :
| Composant | Rôle | Impact sur le calcul |
|---|---|---|
| CPU (processeur) | Exécute les instructions | Vitesse des calculs |
| RAM (mémoire vive) | Stocke les données actives | Taille des problèmes traitables |
| Stockage (SSD/HDD) | Stockage permanent | Persistance des données |
| Bus | Connexions entre composants | Dé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ée | Processeur | Nombre de transistors |
|---|---|---|
| 1971 | Intel 4004 | 2 300 |
| 1993 | Pentium | 3,1 millions |
| 2010 | Intel Core i7 | 731 millions |
| 2023 | Apple M2 Ultra | 134 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.
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ération | Cycles approximatifs |
|---|---|
| Addition entière | 1 |
| Multiplication entière | 3-4 |
| Addition flottante | 3-5 |
| Multiplication flottante | 4-6 |
| Division flottante | 10-20 |
| Racine carrée | 15-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
| Niveau | Taille typique | Latence | Localisation |
|---|---|---|---|
| Registres | ~1 Ko | 1 cycle | Dans le CPU |
| Cache L1 | 32-64 Ko | ~4 cycles | Par cœur |
| Cache L2 | 256 Ko - 1 Mo | ~12 cycles | Par cœur |
| Cache L3 | 8-64 Mo | ~40 cycles | Partagé |
| RAM | 8-128 Go | ~200 cycles | Externe |
Impact sur le calcul numérique
L'organisation des données en mémoire a un impact majeur sur les performances :
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 :
| Bits | Valeurs possibles | Plage (non signé) |
|---|---|---|
| 8 bits | 0 à 255 | |
| 16 bits | 0 à 65 535 | |
| 32 bits | 0 à ~4,3 milliards | |
| 64 bits | 0 à ~18 quintillions |
Types de données et leur taille
En Python, NumPy expose explicitement les types avec leur taille :
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 à :
- La taille des registres : les registres 64 bits peuvent stocker des nombres plus grands
- La largeur du bus d'adresses : détermine la quantité maximale de RAM adressable
- La taille native des opérations : les opérations sur 64 bits sont directes
| Aspect | 32 bits | 64 bits |
|---|---|---|
| RAM maximale | 4 Go | 16 exaoctets (théorique) |
| Entier natif | (~2 milliards) | (~9 quintillions) |
| Pointeurs | 4 octets | 8 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 :
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
- Computer Architecture - Wikipedia
- What Every Programmer Should Know About Memory (Ulrich Drepper)
- Floating Point Arithmetic - Oracle (David Goldberg)