Les cartes graphiques/La hiérarchie mémoire d'un GPU

Un livre de Wikilivres.
Aller à la navigation Aller à la recherche

Dans ce chapitre, nous allons voir comment est organisée la mémoire d'un GPU, ou plutôt devrait-on dire les mémoires d'un GPU. Et oui : un GPU contient beaucoup de mémoires différentes. La hiérarchie mémoire des GPUs est assez particulière, que ce soit au niveau des caches ou de la mémoire, parfois des registres.

Les mémoires d'un GPU[modifier | modifier le wikicode]

Un GPU contient évidemment une mémoire vidéo, de grande taille, capable de stocker textures, vertices, images et bien d'autres choses nécessaires pour un rendu 3D. On y trouve souvent des caches dédiés aux textures ou aux vertices, et les GPUs récents contiennent aussi des caches L1 et L2 de faible taille. Mais sur les cartes graphiques récentes, les caches sont complétés par des Local Store, des mémoires RAM qui servent de cache, mais fonctionnent comme des mémoires RAM normales.

Hiérarchie mémoire typique d'un GPU récent.

La mémoire vidéo[modifier | modifier le wikicode]

La mémoire vidéo est nécessaire pour stocker l'image à afficher à l'écran, mais aussi pour mémoriser temporairement des informations importantes. Dans le cas le plus simple, elle sert simplement de Framebuffer : elle stocke l'image à afficher à l'écran. Au fil du temps, elle s'est vu ajouter d'autres fonctions, comme stocker les textures et les vertices de l'image à calculer, ainsi que divers résultats temporaires.

Elle est très proche des mémoires RAM qu'on trouve sous forme de barrettes dans nos PC, à quelques différences près. En premier lieu, la mémoire vidéo peut supporter un grand nombre d'accès mémoire simultanés. Ensuite, elle est optimisée pour accéder à des données proches en mémoire.

Les anciennes cartes graphique pouvaient lire ou écrire directement dans la mémoire RAM, grâce à certaines fonctionnalités du bus AGP. Mais généralement, les données sont copiées depuis la mémoire RAM vers la mémoire vidéo, en passant par le bus. Cette copie est effectuée par un circuit spécialisé : le contrôleur DMA, qui permet d'échanger des données entre mémoire vidéo et mémoire RAM sans devoir utiliser le processeur. Il est souvent intégré dans le contrôleur de bus.

Les caches d'un CPU[modifier | modifier le wikicode]

Les cartes graphiques sont censées avoir peu de caches. Les premières cartes graphiques n'avaient qu'un cache de texture et éventuellement un cache de vertices, avec un cache d’instruction. Cette situation a perduré durant très longtemps, l'usage de caches plus complexe n'étant pas vraiment utile sur les cartes graphiques. Ce n'est que par la suite, quand les GPU commencèrent à être utilisés pour du calcul généraliste (scientifique, notamment), que la situation changea. Les GPU utilisèrent alors de plus en plus de caches généralistes. La hiérarchie mémoire des GPU ressemblent de plus en plus à celle des CPU, du moins pour les caches. On y trouve toute une hiérarchie de caches, avec des caches L1, L2, L3, etc.

Les Local Store des processeurs de shaders[modifier | modifier le wikicode]

En plus d'utiliser des caches, les processeurs de flux utilisent des local stores, des mémoires RAM intermédiaires entre la RAM principale et les caches/registres. Typiquement, chaque processeur de flux possède sa propre mémoire locale. Ces local stores peuvent être vus comme des caches, mais que le programmeur doit gérer manuellement. La faible capacité de ces mémoires, tout du moins comparé à la grande taille de la mémoire vidéo, les rend utile pour stocker temporairement des résultats de calcul "peu imposants". L'utilité principale est donc de réduire le trafic avec la mémoire centrale, les écritures de résultats temporaires étant redirigés vers les local stores.

Local stores d'un GPU.

Les processeurs de shaders récents sont des processeurs de flux[modifier | modifier le wikicode]

Beaucoup de processeurs graphiques actuels sont des processeurs de flux, aussi appelés stream processors. Ce sont des processeurs SIMD qui utilisent une hiérarchie de registres. Voici à quoi ressemble l'architecture d'un Stream Processor :

Stream processor

Les bancs de registres locaux et globaux[modifier | modifier le wikicode]

On voit que les processeurs de flux ont plusieurs bancs de registres. On trouve d'abord quelques bancs de registres locaux (Local Register File), directement connectés aux unités de calcul. Ces derniers sont reliés à un banc de registres plus gros, le banc de registre global, qui sert d'intermédiaire entre la mémoire RAM et les bancs de registres locaux. La différence entre les bancs de registres locaux/globaux et un cache vient du fait que les caches sont souvent gérés par le matériel, tandis que ces bancs de registres sont gérés via des instructions machines. Le processeur dispose d'instructions pour transférer des données entre les bancs de registres ou entre ceux-ci et la mémoire. Leur gestion peut donc être déléguée au logiciel, qui saura les utiliser au mieux.

Outre son rôle d'intermédiaire, le banc de registre global sert à transférer des données entre les bancs de registres locaux, où à stocker des données globales utilisées par des Clusters d'ALU différents. Les transferts de données entre la mémoire et le Global Register File ressemblent fortement à ceux qu'on trouve sur les processeurs vectoriels. Un processeur de flux possède quelques instructions capables de transférer des données entre ce Global Register File et la mémoire RAM. Et on trouve des instructions capables de travailler sur un grand nombre de données simultanées, des accès mémoires en Stride, en Scatter-Gather, etc.

Registres d'un Stream processor.

L'utilité de cette organisation[modifier | modifier le wikicode]

On peut se demander pourquoi utiliser plusieurs couches de registres ? Le fait est que les processeurs de flux disposent d'une grande quantité d'unités de calcul. Et cela peut facilement aller à plus d'une centaine ou d'un millier d'ALU ! Si on devait relier toutes cas unités de calcul à un gros banc de registres, celui-ci serait énorme, lent, et qui chaufferait beaucoup trop. Pour garder un banc de registres rapide et pratique, on est obligé de limiter le nombre d'unités de calcul connectées dessus, ainsi que le nombre de registres contenus dans le banc de registres. La solution est donc de casser notre gros banc de registres en plusieurs plus petits, reliés à un banc de registres plus gros, capable de communiquer avec la mémoire. Ainsi, nos unités de calcul vont aller lire ou écrire dans un banc de registres local très rapide.