• Aucun résultat trouvé

4.2.1 Introduction

Dans le but d’augmenter la précision des solutions, tout en diminuant le temps de calcul lors de la résolution d’un problème, on peut se tourner vers l’architecture GPU (Graphical Processing Unit) qui permet de faire du parallélisme massif. Les GPU sont des circuits qui ont été initialement conçus pour l’affichage graphique (notamment 2D puis 3D dans le cas des jeux vidéos). Ces circuits ont connu un essor fulgurant, avec une évolution vers des circuits dédiés au calcul hautement parallèle. Suivant le GPU utilisé, on est capable de traiter plusieurs centaines, voire plusieurs milliers de réels en simple ou double précision. On qualifie en général la programmation sur GPU de many-core, et qui correspond à du SIMD (Single Instruction Multiple Data), contrairement au SPMD (Single Program Multiple Data) vu dans la section 4.1. Comme le montre la Figure 4.8 , à la fin de l’année 2013, la puissance de calcul d’un CPU était de 500 GFLOP/s, alors que celle d’un GPU était de 5,5 TFLOP/s, soit un facteur 100.

L’architecture GPU est totalement différente à celle du CPU (Central Processing Unit), ce qui se traduit par certains changements dans la manière d’écrire un programme permettant de résoudre un problème. Il ne suffit pas de transposer un code développé sur CPU, puis de le compiler sur GPU afin d’obtenir les bonnes solutions du problème. Afin d’aider le programmeur à développer son code, la société NVIDIA a introduit l’environnement Compute Unified Device Architecture (CUDA) en 2007 (voir section 4.4). A l’heure actuelle, CUDA est l’environnement de développement des applications sur GPU le plus utilisé, même si des alternatives existent, tel que OpenCL (Open Computing Language) géré par le groupe Khronos. OpenCL a l’avantage d’être multi-plateforme et portable. Plusieurs travaux (Karimi et al., 2010) ont étudié les différences de performances entre les deux technologies, concluant que CUDA prenait l’avantage sur OpenCL.

Afin de mieux comprendre la manière de développer sur GPU, l’architecture GPU et ses princi-pales différences avec le CPU sont détaillées dans la suite. Ensuite, je présenterai rapidement la méthodologie pour développer des applications sur GPU, pour finalement appliquer ces différents points à notre problème. Bien entendue, nous invitons le lecture intéressé par ce sujet est invité à se tourner vers des ouvrages plus complets (Kirk et Hwu, 2010; Tsuchiyama

et al., 2010).

4.2.2 Architecture des GPU

Sur une vue d’ensemble du couple CPU/GPU, comme représenté Figure 4.9, on remarque que le CPU utilise le GPU comme un coprocesseur permettant d’effectuer des calculs adaptés à la parallélisation massive. Le GPU comme le CPU sont multi-coeurs et suivent une hiérarchie de mémoires. Nous reviendrons plus en détail sur cette hiérarchie de la mémoire qui est cruciale dans le développement sur GPU, dans la section 4.2.3

L’architecture des GPU est en constante évolution, pouvant apporter à chaque génération de profonds changements, alors que certaines autres caractéristiques évoluent très peu au cours du temps. En général, les unités constituant un GPU sont beaucoup plus simples que celles d’un CPU traditionnel. En 2012, les GPU les plus performants était constitués d’environ 500 cœurs, tandis que le meilleur CPU devait se contenter de 8 cœurs. Trois ans après, les meilleurs CPU doivent toujours se contenter d’en moyenne 8 cœurs, alors que les GPU ont vu leurs nombres de cœurs multiplié par dix. Dans les faits, les coeurs sont regroupés en grappes, appelées multi-processeurs (SM), de 32 cœurs (pour l’architecture Fermi) et jusqu’à 192 cœurs (pour l’architecture : Kepler). Il est très important de garder en mémoire ce nombre maximum de cœurs par SM, car nous verrons plus tard que ces coeurs doivent effectuer la même tâche pour obtenir une performance maximale.

Sur l’architecture Fermi, chacun de ces SM est capable d’effectuer 32 opérations de flottants ou d’entiers par coup d’horloge sur des nombres de 32 bits, ou alors 16 opérations sur des nombres de 64 bits. Depuis l’apparition de l’architecture Kepler, le SMX a prit la relève du SM, et au passage abandonne le système de double cadencement de Fermi, ce qui permet notamment de multiplier par deux la fréquence des unités de calculs.

Une autre différence majeure entre CPU et GPU, que l’on peut également mentionner est la latence de la mémoire. Au niveau du CPU, les tâches ti sont exécutées l’une après l’autre, avec un petit temps de latence entre chaque traitement, afin de fournir les données au processus. Comme ces temps de latence sont plus long sur GPU, il convient d’effectuer beaucoup de tâches, pour obtenir un haut débit. Ces tâches sont également appelées "threads" dans le langage CUDA. Ceci permet d’optimiser le temps d’exécution, en masquant le temps d’attente des données par le calcul des autres tâches. La Figure 4.10 illustre la latence faible d’un CPU et le haut débit d’un GPU.

4.2.3 Hiérarchie de la mémoire

La hiérarchie de la mémoire des GPU est très différente de celle des CPU. La mémoire des GPU est constituée des registres, de la mémoire locale, de la mémoire partagée, de la mémoire cache et de la mémoire globale.

Chaque thread peut accéder à son propre registre, dont l’accès est très rapide. Il est donc recommandé de faire appel à cette mémoire le plus souvent possible. Comme pour les registres, chaque thread peut accéder à une mémoire locale, qui est en général beaucoup plus lente que celles des registres. Suivant l’architecture, la mémoire locale peut-être une partie de la mémoire globale, ou de la mémoire cache L1. Lors de la compilation, la mémoire locale est automatiquement utilisée lorsque tous les registres sont déjà utilisés. Il est donc conseillé d’optimiser l’utilisation des registres.

La mémoire partagée est commune à un ensemble de threads formant ce qu’on appelle : un bloc. Cette mémoire permet de stocker et d’échanger temporairement des données afin d’augmenter encore les performances de calcul. La mémoire partagée bénéficie à beaucoup de problèmes, mais elle n’est pas toujours utilisable. Certains algorithmes tirent partie de cette mémoire, alors que d’autres tirent partie d’une hiérarchie de cache comme utilisée par les CPU. Il est possible que des conflits d’accès interviennent, entraînant une sérialisation des accès mémoires.

La mémoire globale est, en terme de capacité, la plus importante. Un facteur limitant de cette mémoire est la bande passante limitée, pouvant diminuer les performances de calculs. Comme pour la mémoire partagée, on peut avoir affaire à des conflits d’accès, si des threads d’une même grappe veulent faire appel à des accès mémoires non-voisines.

Figure 4.11 – Hiérarchie des différentes mémoires d’un GPU.

4.3 Conclusion

L’architecture GPU (Graphical Processing Unit) a été brièvement présentée, en montrant les différences avec l’organisation au sein d’un CPU (Central Processing Unit). L’aspect multi-parallèle de cet outil a été abordé, qui permet grâce à une bonne programmation de diminuer le temps de calcul de certains problèmes facilement parallélisables. Dans le chapitre qui va suivre, je vais présenter l’environnement CUDA, permettant de développer des programmes pouvant être déployés sur GPU.