Retour à l'accueil

Hiérarchie de mémoire CPU : Latences et optimisation

Chapitre sur la hiérarchie de mémoire expliquant pourquoi les cache misses dominent les performances. Décrit les niveaux des registres à la DRAM, lignes de cache de 64 B, localité d'accès, prefetcher et protocoles de cohérence MESI/RVWMO. Exemples pour ordinateurs de bureau et systèmes embarqués RISC-V.

Latences mémoire : de 1 cycle L1 à 200 DRAM
Advertisement 728x90

# 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 %.

Google AdInline article slot

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 |

|---------|---------------|-------------|-----------|

Google AdInline article slot

| 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 |

Google AdInline article slot

| 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

Advertisement 728x90

Lire ensuite