Les règles de base sont en fait assez simples. Là où les choses se compliquent, c'est dans la manière dont elles s'appliquent à votre code.
Le cache fonctionne sur deux principes : La localité temporelle et la localité spatiale. Le premier principe repose sur l'idée que si vous avez récemment utilisé une certaine quantité de données, vous en aurez probablement à nouveau besoin prochainement. Le second principe signifie que si vous avez récemment utilisé les données à l'adresse X, vous aurez probablement bientôt besoin de l'adresse X+1.
Le cache essaie de s'adapter à cela en se souvenant des derniers morceaux de données utilisés. Il fonctionne avec des lignes de cache, généralement d'une taille de 128 octets environ, de sorte que même si vous n'avez besoin que d'un seul octet, la ligne de cache entière qui le contient est placée dans le cache. Ainsi, si vous avez besoin de l'octet suivant par la suite, il sera déjà dans le cache.
Cela signifie que vous voudrez toujours que votre propre code exploite ces deux formes de localité autant que possible. Ne sautez pas partout dans la mémoire. Travaillez autant que vous le pouvez sur une petite zone, puis passez à la suivante et travaillez-y autant que vous le pouvez.
Un exemple simple est la traversée de tableau 2D que la réponse de 1800 montrait. Si vous le parcourez ligne par ligne, vous lisez la mémoire de manière séquentielle. Si vous le faites colonne par colonne, vous lirez une entrée, puis vous sauterez à un endroit complètement différent (le début de la ligne suivante), vous lirez une entrée, et vous sauterez à nouveau. Et quand vous reviendrez enfin à la première ligne, elle ne sera plus dans le cache.
Il en va de même pour le code. Les sauts ou les branches signifient une utilisation moins efficace du cache (car vous ne lisez pas les instructions séquentiellement, mais vous sautez à une adresse différente). Bien sûr, de petites instructions if ne changeront probablement rien (vous ne sautez que quelques octets, donc vous finirez toujours dans la région de cache), mais les appels de fonction impliquent généralement que vous sautez à une adresse complètement différente qui peut ne pas être mise en cache. Sauf si elle a été appelée récemment.
L'utilisation du cache d'instruction est généralement beaucoup moins problématique. Ce dont vous devez généralement vous préoccuper, c'est du cache de données.
Dans une structure ou une classe, tous les membres sont disposés de manière contiguë, ce qui est bien. Dans un tableau, toutes les entrées sont également disposées de manière contiguë. Dans les listes liées, chaque nœud est alloué à un emplacement complètement différent, ce qui est mauvais. Les pointeurs en général ont tendance à pointer vers des adresses sans rapport, ce qui entraînera probablement un manque de cache si vous déréférencez.
Et si vous voulez exploiter plusieurs cœurs, cela peut devenir très intéressant, car en général, un seul processeur peut avoir une adresse donnée dans son cache L1 à la fois. Donc, si les deux cœurs accèdent constamment à la même adresse, il en résultera des manques constants de cache, car ils se battent pour l'adresse.
1 votes
En guise d'introduction, cet exposé donne un bon aperçu youtu.be/BP6NxVxDQIs
0 votes
L'URL raccourcie ci-dessus ne semble plus fonctionner, voici l'URL complète de la conférence : youtube.com/watch?v=BP6NxVxDQIs