# Hiérarchie de la mémoire dans les processeurs modernes : des registres à la DRAM
Dans les systèmes modernes, les misses de cache coûtent 100–200 cycles, tandis que les hits prennent 1–4 cycles. Cela détermine les performances du code. Considérons un cas réel : l'optimisation d'un pilote d'interface réseau sur RISC-V à 1 GHz. Traitement de paquets attendu — 2 millions/s à 500 instructions par paquet (500 ns). Atteint en réalité : 200 mille/s.
Le profilage a révélé :
$ perf stat -e cycles,instructions,cache-misses ./driver_test
Performance counter stats:
5,000,000 cycles
500,000 instructions
45,000 cache-misses
500 mille instructions nécessitent 500 mille cycles à IPC=1, mais 5 millions ont été dépensés. La différence — 45 mille misses × 100 cycles = 4,5 millions de cycles. Les calculs ont pris 10 %, l'attente mémoire — 90 %.
Structure de la hiérarchie de la mémoire
La mémoire est organisée en une hiérarchie avec une vitesse décroissante et une taille croissante :
| Niveau | Type | Latence | Taille |
|---------|---------------|-------------|-----------|
| Registres | 32 registres | 1 cycle | ~128 B |
| Cache L1 | Instructions/Données | 3–4 cycles | 32–64 KB |
| Cache L2 | Unifié | 12–15 cycles | 256–512 KB |
| Cache L3 | Partagé | 40–50 cycles | 2–32 MB |
| DRAM | Principale | 100–200 cycles | GB–TB |
Propriétés clés :
- La vitesse diminue de 1 à 200 cycles.
- La taille augmente de 128 B à TB.
- La DRAM est 100–200 fois plus lente que le L1.
Dans les microcontrôleurs (RISC-V RV32IMC, 100 MHz) la hiérarchie est simplifiée :
| Niveau | Type | Latence | Taille |
|---------|-------------------|-------------|------------|
| Registres | 32 registres | 1 cycle | 128 B |
| I-cache L1 | Instructions | 1 cycle | 16 KB |
| D-cache L1/SRAM | Données | 1–2 cycles | 8–32 KB |
| Flash | Code | ~10 cycles | 128 KB–1 MB |
| DRAM | Données | 50–100 cycles | 8–64 MB |
Différences : caches petits, pas de L2/L3, Flash au lieu de DRAM pour le code, taille limitée.
Lignes de cache et accès aux données
Les caches fonctionnent par blocs de 64 octets. Accéder à 1 octet charge toute la ligne.
Exemple d'accès à un int :
int x = array[0]; // 4 bytes at 0x1000
// Loads 64 bytes: 0x1000–0x103F (16 ints)
Accès séquentiel efficace :
for (int i = 0; i < 16; i++) {
sum += array[i]; // 1 miss, 15 hits
}
Aléatoire — non :
for (int i = 0; i < 16; i++) {
sum += array[random_index[i]]; // Many misses
}
Organisation du cache : Ensembles et Voies
Le cache est divisé en ensembles et voies. Mappé direct :
Bits d'adresse : [Tag | Index | Offset]
Pour 32 KB, 64 B/ligne : 512 lignes, index 9 bits, offset 6 bits, tag 17 bits (adresse 32 bits).
Conflits : tableaux a[1024] (0x10000) et b[1024] (0x18000) mappent aux mêmes ensembles.
Associatif par ensembles (N-voies) : un ensemble contient N lignes, réduit les conflits.
Paramètres typiques :
- L1 : 8-voies, 32–64 KB.
- L2 : 8–16 voies, 256–512 KB.
- L3 : 16-voies, 2–32 MB.
Embarqué : direct ou 2-voies, haute probabilité de conflit.
Localité d'accès
Spatiale : adresses adjacentes.
Bon :
for (int i = 0; i < n; i++) sum += array[i];
Mauvais :
for (int i = 0; i < n; i++) sum += array[random[i]];
Temporelle : accès répétés.
Bon :
int temp = array[0];
for (int i = 0; i < 1000; i++) result += temp * i;
Mauvais :
for (int i = 0; i < 1000; i++) result += array[i % 10] * i;
Code optimal (multiplication de matrices) :
for (int i = 0; i < N; i++) {
for (int k = 0; k < N; k++) {
int r = A[i][k];
for (int j = 0; j < N; j++) {
C[i][j] += r * B[k][j];
}
}
}
Préchargeur et ses limitations
Le préchargeur précharge les données en se basant sur des motifs.
- Séquentiel : pour for(i) sum += array[i];
- Pas : pour sum += array[i*2];
Limitations :
- Ne fonctionne pas sur les accès aléatoires.
- Limité à 10–20 lignes.
- Trompé par l'entrelacement de tableaux.
Dans les microcontrôleurs — simple ou absent.
Bande passante et multi-cœurs
DDR4-3200 : 25,6 GB/s/canal, double canal — 51,2 GB/s. L1 : ~1000 GB/s.
Embarqué : SRAM 1–4 GB/s, DRAM 100–500 MB/s. Maintenir l'ensemble de travail en SRAM.
Protocole MESI (x86/ARM) :
- M : Modifié.
- E : Exclusif.
- S : Partagé.
- I : Invalide.
Faux partage :
struct { int c0, c1; } shared; // Ping-pong
Solution :
struct { int c0; char pad[60]; int c1; } shared;
RISC-V : modèle mémoire faible, utiliser fence :
sw a0, 0(a1)
fence w, w
sw a2, 0(a3)
Atomiques : lr.w/sc.w.
Ce qui est important
- Les misses de cache dominent : 90 % du temps en attente.
- Lignes de 64 B : accès séquentiel donne 15 hits gratuits.
- Localité (spatiale/temporelle) — clé de l'optimisation.
- Systèmes embarqués : hiérarchie simplifiée, pas de préchargeur.
- MESI et padding empêchent le ping-pong en multi-cœurs.
— Editorial Team
Aucun commentaire pour le moment.