4 Gestion de la mémoire
4.1 Mémoire Globale
CUDA est capable de lire et d’écrire sur la mémoire embarquée dans la carte graphique.
Ces opérations portent, respectivement, les noms de gathering et de scattering comme nous l’avons vu dans la section 1.3.4 de l’article précédent
La mémoire globale est accessible depuis n’importe quel endroit dans CUDA, avec les mêmes performances.
Les données de cette mémoire ne sont pas mises en cache. Les temps de latence sont donc extrêmement longs : de 400 à 600 cycles. Il est à noter que durant chaque demande d’accès à cette mémoire, le multiprocesseur reste inactif.
La mémoire utilisée pour la mémoire globale est de la DRAM. Cette mémoire bon marché (1.34$ pour une puce de 1Go DDR3 en juillet 2009 pour les intégrateurs). Celle-ci est utilisée comme mémoire principale de nos ordinateurs et a l’avantage d’être compacte. Cependant le défaut de cette mémoire est sa latence pouvant atteindre 30 ns (environ 30 cycles), sans compter le temps de transfert entre la mémoire et le multiprocesseur…
4.2 Mémoire locale
Tout comme la mémoire globale, la mémoire locale n’est pas cachée et souffre de temps de latence importants. Cette mémoire est propre à chaque thread. Elle est utilisée automatiquement pour les structures de grandes tailles ainsi que pour les tableaux de grande taille ou de taille inconnue.
4.3 Mémoire constante
La mémoire constante est une zone mémoire de 8Ko accessible en lecture uniquement. Cette mémoire est cachée, ce qui lui assure une latence d’1 instruction. Pour les threads d’un demi warp, l’accès à cette mémoire est aussi rapide qu’à celle d’un registre, tant que les threads accèdent au même emplacement mémoire. Le coût de lecture augmente linéairement avec le nombre d’adresses différentes demandées par les threads.
4.4 Mémoire texture
Cet espace mémoire est optimisé pour les espaces à deux dimensions. Cette mémoire est cachée, et optimisée pour des accès en lecture. La latence est donc constante et très faible.
4.5 Mémoire partagée
Cette mémoire est présente sur le chipset, ce qui lui permet d’être assez rapide, plus que la mémoire locale.
Elle est commune à un bloc de threads, chaque block de threads possédant sa propre zone de mémoire partagée.
En fait, pour tous les threads d’un warp, accéder à cette mémoire est aussi rapide que d’accéder à un registre, tant qu’il n’y a pas de conflit entre les threads.
Pour permettre une bande-passante assez élevée, la mémoire partagée est divisée en modules de mémoire, les banques, qui peuvent être accédée simultanément. Ainsi, n lectures ou écritures qui tombent dans des banques différentes peuvent être exécutées simultanément dans un warp, ce qui permet d’augmenter sensiblement la bande passante, qui devient n fois plus élevée que celle d’un module.
Cependant, si deux demandes tombent dans la même banque, il y a un conflit de banques, et l’accès doit être sérialisé. Le matériel divise ces requêtes problématiques en autant de requêtes que nécessaire pour qu’aucun problème n’ait lieu, ce qui diminue la bande passante d’un facteur équivalent au nombre de requêtes total à effectuer.
Pour des performances maximales, il est donc très important de comprendre comment les adresses mémoires sont reliées aux banques, pour pouvoir prévoir les requêtes, et, ainsi, minimiser les conflits.
4.6 Registres
Généralement, l’accès à un registre se fait en un seul cycle.
Le compilateur et l’organisateur de threads organisent les instructions pour des performances optimales.
Le meilleur moyen d’obtenir de bonnes performances est d’utiliser un multiple de 64 comme nombre de threads par bloc.
Chaque multiprocesseur dispose de 8192 registres.
4.7 Mémoire système
Depuis les GT200 (GeForce GTX 260 à 295), il est désormais possible d’utiliser la mémoire principale du système, alias RAM, grâce à CUDA 2.2.
Les appels à cette mémoire ne peuvent être fréquents : ils sont encore plus lents que les appels à la mémoire locale (700 à 800 cycles de latence !). Mais la RAM est disponible, de nos jours, en quantités plus grandes que celle disponible sur les GPU.
Mémoire |
Localisation |
Cachée |
Accès |
Portée |
Registre |
chipset |
Non |
Lecture/Ecriture |
1 seul thread |
Locale |
DRAM |
Non |
Lecture/Ecriture |
1 seul thread |
Partagée |
chipset |
Non indiqué |
Lecture/Ecriture |
Tous les threads du bloc |
Globale |
DRAM |
Non |
Lecture/Ecriture |
Tous les threads + hôte |
Constante |
DRAM |
Oui |
Lecture |
Tous les threads + hôte |
Texture |
DRAM |
Oui |
Lecture |
Tous les threads + hôte |
Récapitulatif des caractéristiques des mémoires
4.8 La mémoire, en chiffres, pour une Geforce 8800 GTX
Dans le cas d’une Geforce 8800 GTX, les multiprocesseurs disposent de 8192 registres à partager entre tous les threads de tous les blocs actifs sur ce multiprocesseur.
Le nombre de blocs actifs par multiprocesseur pour sa part ne peut pas dépasser les 8, tandis que le nombre de warps étant pour sa part limité à 24 (24 warps x 32 threads par warp = 768 threads).
Un multi processeur est constitué de deux processeurs, ce qui donne un nombre total de threads :
8 multiprocesseurs x 2 x 768 threads par processeurs = 12.288 threads
Nous disposons donc de 12.288 threads en cours de traitement à chaque instant.
Connaître ces limites peut sembler rébarbatif, mais est très utile pour bien dimensionner son application en fonction des ressources disponibles.
Optimiser un programme CUDA consiste donc essentiellement à équilibrer au mieux le nombre de blocs et leur taille : plus le nombre de threads par blocs sera important moins la latence des opérations mémoire se fera ressentir, mais cela diminue le nombre de registres par threads.
Il est à noter qu’un bloc de 512 threads serait particulièrement peu efficace, car un seul bloc pourrait être actif sur un multiprocesseur, gâchant ainsi potentiellement 256 threads ( 768 threads max par warp – 512 threads actifs).
NVIDIA conseille donc d’utiliser des blocs de 128 à 256 threads qui offrent le meilleur compromis entre masquage de la latence et le nombre de registres suffisants pour la plus part des kernels.
Impressive !
OMG mon mémoire fait pâle figure à présent.
Mais non, tu as rédigé de supers articles !!
Il me tarde de lire ton mémoire au complet.
Bonjour,
je souhaite savoir quels sont les outils et comment faire les configurations s’il y a lieu de faire, à fin programmer en java en utilisant CUDA?
j’ai un windows xp pack2
carte graphique NVIDIA GeForce Go 7300
1 Go de RAM
merci à l’avance.
Cordialement.
Bonjour Amrani,
Pour le développement avec CUDA, il faut tout d’abord installer CUDA (drivers) et son SDK, pour avoir les docs.
Ensuite, en java, il existe 2 projets permettant de binder CUDA à java :
– jacuzzi : http://sourceforge.net/apps/wordpress/jacuzzi/
– JCublas : http://www.jcuda.org/jcuda/jcublas/JCublas.html
pour le 1 Go de Ram, c’est pas un peu juste pour dev en Java? Eclipse est assez lourd…
Bonjour, il y a une faute en première page, je cite :
« le code devant s’exécuter sur le GPU est en fait exécuté sur le GPU »
-> est en fait exécuté sur le CPU.
Voila merci pour l’article sinon 🙂
pouvez vous nous indiquer les étapes à suivre pour utiliser cuda avec Visual studio 2010 ou bien 2008 et merci d’avance