Leçon
La théorie — Localité mémoire : pourquoi l’ordre de lecture compte
Un cache est une petite mémoire rapide qui conserve des copies de données de la mémoire principale dans des lignes. Chacune contient un bloc aligné de L octets. Une lecture dont le bloc est déjà présent dans une ligne est un succès. Toute autre lecture provoque un défaut, et le bloc entier est chargé. Dans un cache à correspondance directe de S lignes, le bloc contenant l’adresse a ne peut occuper qu’une seule ligne : la ligne ⌊a / L⌋ mod S. Une boucle qui lit successivement des adresses voisines effectue plusieurs lectures dans chaque bloc chargé : elle bénéficie de la localité spatiale.
Ce que signifie chaque symbole
L- la taille d’une ligne en octets : 16 ici, 64 sur la plupart des processeurs d’ordinateurs de bureau et de serveurs
S- le nombre de lignes : 8 ici
- Suppose
- Le modèle utilise un cache à correspondance directe initialement vide, des entiers de 4 octets et une matrice stockée ligne par ligne, comme en C. Fortran stocke les matrices par colonnes : le parcours le plus rapide y est donc celui par colonnes. Seules les lectures de la matrice sont comptées. Les caches de données réels de premier niveau sont plus grands et associatifs par ensembles, généralement à 8 voies : un bloc peut occuper n’importe laquelle des 8 lignes de son ensemble. Cette organisation élimine la plupart des conflits illustrés ici, mais ne résout pas un manque de capacité. La prélecture matérielle, qui charge la ligne suivante avant qu’elle soit demandée, n’est pas prise en compte.
Problème entièrement résolu
-
Quel trafic mémoire coûte un parcours dans le mauvais ordre ? 4 étapes
Cliquez sur 8 × 8 par colonnes et expliquez les 0 succès. Considérez ensuite une matrice de 1024 × 1024 entiers de 4 octets et un cache à correspondance directe de 32 Kio, composé de lignes de 64 octets. Combien d’octets sont transférés depuis la mémoire pour calculer la somme par lignes ? Et par colonnes ? Ajouter une ligne de cache de remplissage après chaque ligne de la matrice améliore-t-il le résultat ?
Ouvrez ce problème dans l'outil
-
Dans la simulation, une ligne de la matrice occupe 8 × 4 = 32 octets, soit deux lignes de cache. La ligne i commence donc dans la ligne de cache 2i mod 8, et la colonne 0 n’utilise que les lignes 0, 2, 4 et 6. a[4][0] prend la place de a[0][0]. Lorsque la boucle atteint a[0][1], son bloc a disparu : 0 succès sur 64. Par lignes, chaque bloc de 16 octets provoque un seul défaut. Il y a 16 blocs, donc 48 succès, soit 1 − 4/16 = 75 %.
-
Le grand cache compte 32 768 / 64 = 512 lignes. Par lignes, chaque ligne de cache de 64 octets contient 16 entiers et seule leur première lecture provoque un défaut : 1 048 576 / 16 = 65 536 défauts, un taux de succès de 1 − 4/64 = 93,75 % et 65 536 × 64 = 4 194 304 octets transférés. La matrice est chargée exactement une fois.
-
Par colonnes, une ligne de la matrice occupe 4 096 octets, soit 64 lignes de cache. La ligne i commence donc dans la ligne de cache 64i mod 512, et une colonne n’utilise que 8 des 512 lignes. a[8][0] évince le bloc de a[0][0], bien avant que la boucle revienne à a[0][1]. Chacune des 1 048 576 lectures provoque un défaut : 67 108 864 octets sont transférés, soit 16 fois plus.
-
Ajouter 16 entiers de remplissage après chaque ligne porte sa longueur à 4 160 octets, soit 65 lignes de cache. 65 et 512 sont premiers entre eux : une colonne utilise désormais les 512 lignes. Elle ne donne toujours aucun succès. Elle parcourt 1 024 blocs, alors que le cache ne peut en contenir que 512 ; chaque bloc est donc évincé avant que la colonne suivante en ait besoin.
Réponse
4 Mio par lignes, 64 Mio par colonnes. Le remplissage répartit les blocs d’une colonne sur tout le cache, sans obtenir de succès. Il résout les conflits, mais les blocs nécessaires à chaque colonne de cette matrice dépassent la capacité totale du cache.
-
Parcours
Comment un ordinateur exécute un programme
Références (1)
- The first insight's measured case, the line size in the definition, and the 8-way first-level caches in `assumes`: Ulrich Drepper, "What Every Programmer Should Know About Memory," Red Hat, Inc., version 1.0, 21 November 2007. §3.2 (p. 15), §3.3.1 (pp. 17–18), §6.2.1 (pp. 49–50).