Implémentation et analyse des performances d algorithmes de calcul scientifique sur GPU

Dimension: px
Commencer à balayer dès la page:

Download "Implémentation et analyse des performances d algorithmes de calcul scientifique sur GPU"

Transcription

1 Université de Liège Faculté des Sciences Appliquées Institut Montefiore Implémentation et analyse des performances d algorithmes de calcul scientifique sur GPU Marsic Nicolas Mémoire de fin d études réalisé en vue de l obtention du grade d Ingénieur Civil Électricien Année académique

2 Résumé Ce mémoire de fin d études à pour sujet le calcul scientifique sur processeur graphique, ou GPU. Ces nouvelles architectures sont, de plus en plus, exploitées à des fins autres que graphiques, étant donné la parallélisation massive qu elles offrent. L objectif de ce travail est d avoir une vue plus claire de ce qu il est judicieux de faire lors de l utilisation de stations équipées d unités de traitement graphique. Ce mémoire commence par une description de l architecture matérielle des GPUs. Ensuite, le sujet de la programmation sera abordé. Dans ce but, les langages CUDA et OpenCL seront présentés. En troisième partie, il sera question de comparer les performances, sur CPU et sur GPU, de différentes opérations d algèbre linéaire. Enfin, ce travail se clôture sur l implémentation GPU et l étude des performances de trois algorithmes de calcul scientifique : la vue adaptative, la méthode de Galerkin discontinue et la trajectoire de particules chargées dans un champ de force électromagnétique.

3 Remerciements C est, avec un réel plaisir, que je tiens à remercier toutes les personnes, sans qui ce mémoire n aurait jamais vu le jour. Tout d abord, je tiens à remercier monsieur le professeur Christophe Geuzaine, pour m avoir proposé ce mémoire, pour m avoir suivi tout au long de cette année, et pour son enthousiasme. Ensuite, je tiens à remercier monsieur David Colignon, pour m avoir aidé durant tout mon travail. Je tiens également à remercier madame Brigitte Mausen, pour sa relecture méticuleuse. Enfin, je remercie chaleureusement ma famille et mes amis, qui, au travers de leurs attentions quotidiennes, m ont soutenu durant toutes ces années. ii

4 Table des matières Introduction 1 1 Architecture d un processeur graphique Du rendu 3D au calcul scientifique Vocabulaire Architecture Programmation d un GPU Threads GPU Threads CPU Mémoire Mémoire globale Mémoire partagée Mémoire registre Vue d ensemble Allocation des ressources mémoires Transferts Paradigme SIMT Les nombres en virgule flottante Le multi GPU Résumé Le langage CUDA Répartition des threads Codes CPU, GPU et kernel Distinction entre les threads Transferts mémoires Allocation de la mémoire device iii

5 2.4.2 Copies entre host et device Exemple récapitulatif CUDA Streams Généralités CUDA Streams Synchronisation des tâches Le parallélisme de tâche Contrainte hardware Le multi GPU Synchronisation et opérations atomiques Compilation Mécanisme de programmation de plus bas niveau Le langage OpenCL Introduction Vocabulaire Code kernel Code host Contexte OpenCL File de commandes Compilation du kernel Allocation et transferts mémoires Appel du kernel Libération des ressources Un exemple complet Étude des performances Notions complémentaires Librairies BLAS Cartes NVIDIA Tesla Matériel utilisé Plateforme desktop Plateforme fermi Plateforme lmgpu Plateforme gameboy iv

6 4.3 Multiplication matricielle : cas des matrices carrées Implémentations naïves BLAS CUBLAS Scaling Transferts mémoires Multi GPU Double Précision Multiplication matricielle : matrice carrée & matrice rectangulaire Multiplication matrice vecteur Multiplication vecteur vecteur Résumé & Conclusion Problème de la vue adaptative Introduction Implémentation Base de travail Classe fullmatrix Intégration du code CUBLAS dans gmsh Résultats Présentation du cas de test Étude des performances Conclusion Méthode de Galerkin discontinue Description de la méthode de Galerkin discontinue Méthode des éléments finis & Galerkin continu La méthode de Galerkin discontinue Portage GPU du code dg : premier essai Implémentation Étude des performances Portage GPU du code dg : second essai Amélioration par rapport au premier portage Implémentation Étude des performances v

7 6.4 Conclusion Trajectoires de particules chargées dans un champ de force électromagnétique Présentation du problème Résolution Algorithme de Beeman Implémentation CUDA Implémentation CPU Résultats Occupation du GPU Performance Conclusion Conclusion 86 A Résolution éléments finis de l équation de Poisson 1D 88 Bibliographie 92 vi

8 Table des figures 1.1 Utilisation de la surface d une puce pour un CPU et un GPU (d après [12]) Bloc diagramme de l architecture de calcul G80 (d après [7]) Architecture d un streaming multiprocessor G80 (d après [5]) Appel d un kernel GPU Vue logique de la mémoire d un GPU (d après [5]) Architecture d un streaming multiprocessor G80 (d après [5]) Grilles de dimension 1, 2 et Exemple d exécution (d après [12]) Division en warp des blocs de dimension supérieure à un (warp de taille 2) Illustration du principe de pipeline (cas de deux copy engines) État des engines pour la première tentative de pipeline (d après [20]) Organisation des engines conduisant à un pipeline (d après [20]) Photographie d une carte graphique NVIDIA 8500 GT Photographie d une carte graphique NVIDIA GT Trois implémentations naïves de la multiplication matricielle (desktop) Comparaison entre un algorithme CUDA naïf et un algorithme CPU optimisé (desktop) Comparaison entre BLAS et CUBLAS (desktop) Performance de CUBLAS pour une évolution progressive des matrices (lmgpu) Effet de scaling (GeForce 8500GT & Tesla C1060) Temps de transferts mémoires (gameboy) Répartition des matrices pour 2 GPUs Répartition des matrices pour 4 GPUs Accélération apportée par le multi-gpu Distribution de la charge entre les GPUs vii

9 4.13 Accélération apportée par le multi-gpu (en GFLOPS) Passage de la simple à la double précision (lmgpu) Passage de la simple à la double précision (fermi) Passage de la simple à la double précision : comparaisons CPU / GPU Cas de la multiplication d une matrice carrée par une matrice rectangulaire Étude des performances dans le cas de la multiplication de matrices [N N] [N 6] (fermi) Étude des performances dans le cas de la multiplication de matrices [N N] [N 6] : temps de transferts (fermi) Étude des performances dans le cas de la multiplication de matrices [N N] [N 6] : variation de N par pas unitaire (fermi) Étude des performances dans le cas de la multiplication d une matrice carrée par un vecteur (fermi) Étude des performances dans le cas de la multiplication d une matrice carrée par un vecteur : temps de transfert (fermi) Étude des performances dans le cas de la multiplication d une matrice carrée par un vecteur : variation de la dimension par pas unitaire (fermi) Étude des performances dans le cas de la multiplication vecteur vecteur (fermi) Principaux résultats en termes de performances Illustration du principe de vue adaptative Cas test sans vue adaptative (d après [19]) Division d un triangle en sous-triangles Cas test avec vue adaptative (d après [19]) : division, de plus en plus fine, des domaines problématiques (la dernière figure représente la solution finale sans son maillage) Comparaison des performances Comparaisons des performances entre la version CPU et la version GPU, avec pipeline, de dg Champs électrique et d induction magnétique (premier cas test) Comparaison entre les versions CPU et GPU (premier cas test) Résultats du second cas test A.1 Domaine d étude A.2 Domaine discrétisé A.3 Fonction de forme associée au nœud viii

10 Liste des tableaux 1.1 Taille des différentes mémoires pour l architecture G80 (d après [22]) Exemple d exécution du code d addition vectorielle Comparaison entre les termes CUDA et OpenCL (d après [5]) Termes OpenCL désignant le matériel (d après [5]) Fonctions identifiant les working items (d après [5]) Fonctions pour libérer les ressources Logiciels disponibles sur desktop Logiciels disponibles sur fermi Logiciels disponibles sur lmgpu Logiciels disponibles sur gameboy Temps d initialisation Paramètres du plugin CULorentz (premier cas test) Temps d exécutions pour 49 particules (second cas test) Temps d exécutions pour 65 particules (second cas test) ix

11 Introduction Poussées par une industrie du jeu vidéo de plus en plus exigeante, les cartes graphiques modernes, communément appelées GPUs, pour Graphics Processing Units, sont devenues de véritable plateformes de calcul intensif. Partant de ce constat, des sociétés comme NVIDIA ou AMD, se sont mises à développer des architectures, permettant le développement et l exécution de codes généraux sur GPU. Cette nouvelle approche de la programmation porte le nom de General-Purpose computing on Graphics Processing Units, ou GPGPU. L attrait des processeurs graphiques, dans la résolution de problèmes de calcul scientifique, réside dans deux points importants. Tout d abord, comme nous le verrons dans ce travail, les GPUs nous offrent une architecture massivement parallèle. Ainsi, si nous trouvions un moyen d exploiter efficacement les GPUs, nous pourrions accélérer significativement une large gamme de codes de calculs. Deuxièmement, l intérêt des cartes graphiques réside dans leur faible coût, par rapport au prix d un microprocesseur classique. C est pourquoi, ces dernières années, un grand nombre de travaux ont émergé sur le sujet de la programmation sur GPU. Notons également que le super-calculateur chinois Tianhe-1A, classé premier au rang mondial en novembre , n embarque pas moins de 7168 cartes graphiques NVIDIA Tesla M2050. Dans le cadre de ce travail, nous nous proposons d explorer les différentes possibilités offertes par ces architectures. Ainsi, nous espérons obtenir une vue plus claire de ce qu il convient de faire, lorsque nous sommes face à des stations de calcul, embarquant un, ou plusieurs, processeurs graphiques. Ce document est divisé en 7 chapitres. Chapitre 1 : présentation de l architecture matérielle des processeurs graphiques Chapitre 2 : présentation du langage CUDA Chapitre 3 : présentation du langage OpenCL Chapitre 4 : étude des performances offertes par les GPUs, pour différentes opérations d algèbre linéaire Chapitre 5 : implémentation GPU et étude d un algorithme de vue adaptative Chapitre 6 : implémentation GPU et étude d un algorithme de calcul par la méthode de Galerkin discontinue Chapitre 7 : implémentation GPU et étude d un algorithme de calcul de trajectoires de particules chargées dans un champ de force électromagnétique 1. Voir : 1

12 Chapitre 1 Architecture d un processeur graphique Avant d aborder le sujet du calcul sur GPU, il importe de connaitre les bases de l architecture matérielle utilisée. En effet, celle-ci est substantiellement différente d une architecture x86 classique. Nous entrerons dans le détail des architectures NVIDIA GeForce. Cependant, notons que AMD (anciennement ATI) développe également des plateformes de calcul scientifique sur GPU. Citons l architecture HD6000, la dernière née des laboratoires AMD. Cette introduction est principalement basée sur les références suivantes : [7, 12, 5]. La section 1.7 est, quant à elle, inspirée de [22]. 1.1 Du rendu 3D au calcul scientifique La tâche la plus importante des GPUs modernes est le rendu 3D. Nous pouvons décomposer cette tâche en deux grandes étapes : 1. La projection de la représentation 3D d une scène sur un plan 2D 2. La pixellisation de l image 2D Tout le hardware d un GPU s articule autour de ces deux objectifs. A l aube du traitement graphique, les algorithmes de rendu étaient encodés une fois pour toutes sur la puce. En d autres termes, aucune modification des algorithmes ne pouvait être apportée. Très vite, les constructeurs ont rendu certaines parties du processeur programmables : le calcul sur GPU était né. L avantage avec l utilisation de processeurs graphiques, pour résoudre des problèmes de calcul intensif, est de pouvoir tirer parti du degré élevé de parallélisme de ces processeurs. En effet, le rendu graphique se parallélisant assez naturellement, les constructeurs ont décidé d opter pour une architecture mettant en scène un très grand nombre d unités de calcul mises en parallèle. Cette approche est très différente de ce qui est choisi pour un CPU. Pour celui-ci, une grande partie de la surface de la puce est dédiée à la mémoire et au contrôle. Ainsi, seule 2

13 une faible portion du CPU est dédiée au calcul proprement dit. La figure 1.1 reprend ce qui vient d être dit. Figure 1.1 Utilisation de la surface d une puce pour un CPU et un GPU (d après [12]) Avec les premiers GPUs programmables, les problèmes devaient être formulés sous la forme d opérations de rendu. Ceci rendait difficile le développement d algorithmes de calculs généraux. En 2007, NVIDIA lance CUDA (Compute Unified Device Architecture), une technologie permettant de programmer les GPUs via le langage C. 1.2 Vocabulaire Tout au long de ce travail, nous serons amenés à parler de GPU et de CPU. Physiquement, ces termes ne désignent que des processeurs. Cependant, ceux-ci nécessitent souvent des dispositifs supplémentaires, tels que de la mémoire. Nous avons donc besoin de termes pour désigner ces ensembles plus généraux. Nous appellerons device, l ensemble constitué : du GPU de mémoires RAM propres au GPU d une interface avec une carte mère 1 Nous appellerons host, l ensemble constitué : du CPU de mémoires RAM accessibles depuis le CPU de la carte mère 1.3 Architecture L architecture d un GPU moderne est basée sur une grille programmable. Les éléments de cette grille sont les Streaming Processors, ou SPs. Une illustration est disponible à la figure 1.2. Notons qu un SP est capable d effectuer une opération réelle d addition et de multiplication en un cycle Comme nous le verrons plus tard, il s agit d un lien PCI Express 2. Comprendre : R = A B + C et non R = A + C ou R = A B A, B, C R 3

14 Host Figure 1.2 Bloc diagramme de l architecture de calcul G80 (d après [7]) 4

15 Les streaming processors sont groupés par Streaming Multiprocessors, également appelés SM. En plus des SPs, ceux-ci offrent une cache d instructions, un circuit d instruction fetch, de la mémoire partagée, de la mémoire registre et des unités de calcul de fonctions transcendantes, appelées SFUs (Special Function Units). Une illustration est disponible à la figure 1.3. Instruction cache Figure 1.3 Architecture d un streaming multiprocessor G80 (d après [5]) L architecture G80 possède 16 streaming multiprocessors, contre 30 pour l architecture GT200. Notons que la dernière architecture née des laboratoires NVIDIA, l architecture Fermi, possède 32 SPs par SM (d après [11]). Pour ce qui est des générations antérieures, elles ne disposent que de 8 SPs par SM. Notons que les nombres annoncés ne sont valables qu en simple précision. Le sujet de la double précision est abordé à la section Programmation d un GPU Au niveau du GPU, nous disposons d un ensemble de langages, nous permettant de le programmer. Les langages les plus connus sont CUDA et OpenCL. Nous approfondirons ces langages aux chapitres suivants. Au niveau du CPU, nous disposons d un ensemble de fonctions permettant de contrôler le GPU. Il existe différentes librairies permettant ce contrôle. Celles-ci sont spécifiques au langage utilisé pour programmer le GPU. Grâce à ces fonctions, le CPU est capable de démarrer l exécution d un code sur le GPU. Nous avons donc un code, appelé depuis le CPU et lancé sur le GPU. Un tel code est appelé kernel. Au niveau du CPU, l appel d un kernel est toujours asynchrone : une fois le kernel invoqué, le CPU passe directement à l instruction suivante, que le kernel ait fini son exécution ou non. En plus de demander le lancement d un kernel, le CPU a l obligation de donner le nombre d exécutions désiré du kernel. Ces exécutions seront, autant que faire se peut, parallèles. 5

16 Les différents fils d exécution du kernel sont appelés threads. La figure 1.4 résume ce dernier paragraphe. CPU Figure 1.4 Appel d un kernel GPU Notons que, puisque le CPU a l obligation de donner le nombre d exécutions du kernel, ce nombre doit être connu avant l appel! En d autres termes, il est impossible de modifier a posteriori le nombre de thread. 1.5 Threads GPU Il nous faut encore comprendre comment les threads sont organisés et exécutés au niveau hardware. Lors de l appel du kernel, les threads sont divisés en blocs. Les nombres de blocs et de threads par bloc sont spécifiés à l appel du kernel. Ensuite, chaque bloc est accroché à un SM. Un maximum de 8 blocs peuvent être liés à un même SM. Les blocs ne trouvant aucun multiprocessor libre sont mis en file d attente. Un bloc est décroché d un SM dès que tous ses threads ont finis leurs exécutions. En plus de la limitation de 8 blocs par SM, celui-ci ne peut ordonnancer qu un nombre limité de warps : maximum 24 warps pour l architecture G80 maximum 32 warps pour l architecture GT200 maximum 48 warps pour l architecture GF100 3 (d après [15]) Les threads d un bloc sont divisés en groupes de 32, appelés warps. Notons qu un bloc peut contenir un maximum de 512 threads, soit 16 warps. Le nombre de threads résidant dans un SM peut être supérieur au nombre de SPs. Les threads sont alors ordonnancés temporellement entre les SPs. Dans un même warp, afin d obtenir une exécution purement parallèle, les threads ne peuvent pas diverger dans leurs instructions (par exemple, à cause d un branchement conditionnel). Si nous nous retrouvons dans cette configuration, les threads sont divisés en groupes suivant les différents chemins d exécution possibles. Ensuite, les groupes sont exécutés en série 4. Si jamais la divergence apparait au sein de warps différents, elle n a aucun effet. 3. Première génération de cartes graphiques basées sur l architecture Fermi 4. Notons que les threads sont exécutés en parallèle au sein d un même groupe 6

17 Ce phénomène s explique par la présence d un seul circuit d instruction fetch par SM (voir figure 1.3). En conséquence, tous les SPs voient la même instruction à exécuter. Remarquons que si seuls 8 SPs par SM sont disponibles 5, alors l exécution des 32 instructions courantes d un warp prend 4 cycles. Dans le cas de l architecture Fermi, avec ses 32 SPs par SM, un seul cycle est nécessaire. Avoir en permanence plusieurs warps à disposition permet au SM d être toujours actif : c est-à-dire, de ne pas être bloqué par un warp, par exemple en attente de données provenant de la mémoire. Pour terminer, les architectures G80 à GT200 ne peuvent exécuter qu un seul kernel à la fois. L architecture Fermi peut, quant à elle, gérer jusqu à 16 kernels concurrents. 1.6 Threads CPU Nous serons également amenés à parler de threads dans le contexte des codes sur CPU. Dans ce cadre, le concept de thread est légèrement différent : il s agit d un fil d exécution, ayant son propre flux de contrôle (d après [23]). Nous pouvons en retirer que la différence fondamentale, entre threads CPU et GPU, est dans le flux de contrôle. Les threads actifs du CPU ont leur propre flux de contrôle, ce qui n est pas le cas pour un thread GPU. Rappelons que les threads d un même SM partagent le même circuit d instruction fetch. 1.7 Mémoire Dans les architectures des processeurs graphiques modernes, la mémoire peut être divisée en trois niveaux : mémoire globale, mémoire partagée et mémoire registre Mémoire globale La mémoire globale est de type DRAM. Il s agit d une mémoire de très grande capacité, mais à accès lent. Cet étage de mémoire est accessible par tous les threads Mémoire partagée Comme nous l avons vu à la section 1.3, chaque SM dispose d une mémoire, dite partagée, de taille fixe. Cette mémoire est partagée par tous les threads d un même bloc. Étant donné qu un SM peut ordonnancer plusieurs blocs, cette mémoire devra être répartie entre les blocs d un même SM. 5. Dans le cas des architectures non Fermi 7

18 1.7.3 Mémoire registre Comme nous l avons vu à la section 1.3, chaque SM dispose d une mémoire, dite registre, de taille fixe. Cette mémoire est privée à chaque thread. Étant donné qu un SM peut ordonnancer plusieurs threads, cette mémoire sera répartie entre les threads d un même SM Vue d ensemble La figure 1.5 résume, d un point de vue logique, ce qui a été appris. Shared Memory Figure 1.5 Vue logique de la mémoire d un GPU (d après [5]) La figure 1.3 résume, d un point de vue matériel, ce qui a été appris. Dans un souci de clarté, ce schéma est repris à la figure 1.6. Instruction cache Figure 1.6 Architecture d un streaming multiprocessor G80 (d après [5]) 8

19 A titre d exemple, nous reprenons à la table 1.1 la taille des différentes mémoires de l architecture G80. Mémoire globale Mémoire partagée Mémoire registre 6 banques de 128 MB 16 kb par SM 32 kb par SM Table 1.1 Taille des différentes mémoires pour l architecture G80 (d après [22]) Allocation des ressources mémoires Lors de l attachement d un bloc à un SM, les ressources mémoires pour tous les threads du bloc sont directement allouées. Rappelons qu un SM peut ordonnancer plusieurs blocs : les ressources mémoires de leurs threads seront également allouées. Cette allocation directe des ressources permet un ordonnancement rapide des threads et des blocs, mais limite les nombres de threads par bloc et de blocs par SM. Par exemple, si les threads résidant dans le SM sont trop exigeants en termes de mémoire registre, le nombre de blocs sera dynamiquement réduit. Il est très important de noter que la réduction des ressources est effectuée en diminuant le nombre de blocs : l utilisation de grands blocs, occupant beaucoup de mémoire, peut conduire à une utilisation sous-optimale du processeur graphique. Illustrons les deux derniers paragraphes par un exemple. Imaginons que le système veuille associer 3 blocs de 256 threads, soit 24 warps au total, à un SM. Supposons que chaque thread alloue un tableau en mémoire registre de 11 flottants 6. Nous avons donc 44 octets de registres par thread, soit environ 33.8 kb de mémoire registre allouée pour le SM. Or, un SM ne possède que 32 kb de mémoire registre : le système ne pouvant pas allouer autant de mémoire, celui-ci n acceptera que 2 blocs et donc seulement 16 warps Transferts Des dispositifs de copie entre la mémoire RAM host et la RAM device sont implémentés. Notons que ces copies peuvent être synchrones ou asynchrones. L avantage des copies asynchrones est le suivant : le code kernel peut continuer son exécution pendant que des chargements, un montant et/ou 7 un descendant, ont lieu. On parle alors de parallélisme de tâches. Les transferts mémoires s effectuent au travers d un bus PCI Express 16X. Ce bus possède une bande passante théorique, simultanément en montant et en descendant, de 8 GB/s dans sa version 2 (d après [3]). Remarquons que la plateforme NVIDIA Ion, architecture de carte graphique intégrée à la carte mère, ne possède pas sa mémoire RAM propre : cette dernière partage la même mémoire que l host. Ceci implique qu aucun transfert par le bus PCI Express n est nécessaire (d après [20]). 6. Dans ce cas-ci, nous entendons par flottant, un nombre en virgule flottante simple précision : soit 4 octets (voir section 1.9) 7. Certaines architectures peuvent exploiter simultanément le bus en montant et en descendant 9

20 1.8 Paradigme SIMT Dans la section sur l architecture, nous avons affirmé que, pour avoir une exécution parallèle, les threads d un warp ne doivent pas diverger dans leurs instructions. Mais si tous les threads doivent exécuter les mêmes instructions, quel est l intérêt d une telle architecture? La réponse est très simple : bien que les threads ne peuvent pas diverger dans leurs instructions, ceux-ci peuvent diverger dans les arguments de ces instructions. Pour illustrer ce propos, prenons l exemple de la somme de deux vecteurs : chaque thread va effectuer la même opération d addition, mais avec des éléments différents. Addition vectorielle Soient trois vecteurs a, b et c de dimension 4 (supposons un warp de taille 4 pour l exemple). Nous pouvons écrire le pseudo-code suivant : void main ( void ){ float a [4]; float b [4]; float c [4]; CopyArrayOnGPU (a); CopyArrayOnGPU (b); LaunchKernel ( 4 Times, VectAdd, a, b, c); } GetArrayFromGPU (c); void VectAdd ( float *a, float *b, float *c){ int i = GetThreadId (); } c[i] = a[i] + b[i]; La table 1.2 illustre l exécution du kernel VectAdd. Thread 0 Thread 1 Thread 2 Thread 3 c[0] = a[0] + b[0] c[1] = a[1] + b[1] c[2] = a[2] + b[2] c[3] = a[3] + b[3] Table 1.2 Exemple d exécution du code d addition vectorielle Nous remarquons que les 4 threads exécutent une opération d addition : ces 4 additions se feront donc en parallèle. Cette approche porte le nom de SIMT, pour Single Instruction Multiple Thread. 10

21 1.9 Les nombres en virgule flottante Une grande évolution dans la technologie des GPUs est l adoption du standard IEEE 754. Rappelons que celui-ci est utilisé dans la représentation des nombres réels. Il existe différents degrés de précision, suivant le nombre de bits utilisés par réel : 32 bits pour les nombres en simple précision 64 bits pour les nombres en double précision Les premières architectures CUDA, telles que la G80, n implémentaient que la version simple précision. Les architectures plus récentes adoptent, quant à elles, les deux versions. Remarquons que, même si certains GPUs implémentent la double précision, les calculs en double précision sont largement plus lents que ceux en simple précision. Ce phénomène s explique par le fait que les SMs embarquent moins de SPs capables de double précision : l architecture GT200 ne compte que 1 SP par SM en double précision, contre 8 SP par SM en simple précision (d après [8]) l architecture GF100 ne compte que 16 SPs par SM en double précision, contre 32 SPs par SM en simple précision (d après [11]) Après lecture du dernier paragraphe, nous sommes en droit de nous poser la question suivante : les SPs en simple et double précision sont-ils physiquement séparés, ou s agit-il d une valeur équivalente? Helas, cette question reste sans réponses, étant donné que NVIDIA n aborde jamais la question de la réalisation physique. Cependant, en recoupant les références [8], [15] et [11], nous pouvons penser que pour l architecture GT200, le SP en double précision est effectivement physiquement séparé des SPs en simple précision. Par contre, l architecture GF100 semble unifiée. Encore une fois, rappelons qu il ne s agit que d une hypothèse, NVIDIA n abordant jamais le sujet de la réalisation physique Le multi GPU Afin d augmenter les performances en termes de rendu, les constructeurs de GPUs ont imaginé des dispositifs utilisant plusieurs cartes graphiques. Remarquons que différents liens propriétaires 8 ont été mis au point afin d obtenir une répartition dynamique du rendu entre les GPUs. Il est important de noter ces liens ne permettent pas d échanges directs de segments de mémoire entre les cartes graphiques. Donc, si dans le cadre d un code de calcul, des données doivent être échangées entre les devices, celles-ci doivent passer par le bus PCI Express. Au niveau de la programmation, nous pouvons demander aux différents GPUs d exécuter le même kernel. Les données sont alors réparties entre les GPUs. 8. Citons les liaisons NVIDIA SLI ( et AMD CrossFireX ( com/us-en/crossfirex_about.aspx) 11

22 1.11 Résumé Les concepts vus dans les sections précédentes étant importants, nous nous proposons des les résumer dans les lignes suivantes : Nous appelons device l ensemble constitué du GPU, de sa mémoire RAM propre, et d une interface avec la carte mère. Nous appelons host l ensemble constitué du CPU, de sa mémoire RAM, et de la carte mère. L unité de base d un processeur graphique est le streaming processor, aussi appelé SP. Un SP est capable d opérations réelles, du type R = A B + C, en un cycle. Un streaming multiprocessor, aussi appelé SM, est un groupement de SPs accompagné de mémoire et d un circuit d instruction fetch. Ce circuit d instruction fetch est commun pour tous les SPs du SM. Les GPUs Fermi disposent de 32 SPs par SM, contre 8 pour les générations antérieures. Les threads à exécuter sur le GPU sont divisés en blocs, avec maximum 512 threads par bloc. Un bloc est également divisé en groupes de 32 threads, appelés warps. Un SM peut ordonnancer simultanément un maximum de 8 blocs. En plus de cette limite de blocs, un SM ne peut ordonnancer qu un maximum de 24 warps pour l architecture G80, de 32 pour la GT200 et de 48 pour la GF100. Les SMs exécutent les threads d un warp en parallèle, tant que ces threads ne divergent pas. Si les threads d un warp divergent, alors les différents chemins d exécution sont exécutés en série, mais en parallèle au sein du même chemin. Si les warps divergent entre eux, cela n a aucune conséquence sur l exécution parallèle. Les blocs non liés à un SM sont en liste d attente. Les ressources mémoires sont allouées pour tous les threads d un bloc dès qu un bloc est lié à un SM. Cette allocation directe permet un ordonnancement rapide des threads, mais limite le nombre de threads par bloc et de blocs par SM. Les threads ne peuvent pas diverger en termes d instructions, mais peuvent diverger dans les arguments de ces instructions. Cette dernière approche porte le nom de Single Instruction Multiple Thread, aussi appelé SIMT. Les réels sont représentés suivant le standard IEEE 754. La double précision n est disponible que sur les architectures plus récentes. Le temps de calcul en double précision augmente largement par rapport à la simple précision. Les échanges de mémoires entre les GPUs doivent passer par le bus PCI Express. 12

23 Chapitre 2 Le langage CUDA CUDA est une technologie permettant la réalisation de calculs scientifiques sur GPU. Celle-ci est le fruit des laboratoires de la société NVIDIA. Le vocable CUDA est utilisé pour désigner à la fois le hardware et le langage de programmation. Ce chapitre est consacré à la présentation du langage CUDA, le hardware ayant déjà été présenté au chapitre précédent. Ce chapitre est basé sur les références suivantes : [5, 12, 20]. Notons que toutes les spécifications des fonctions CUDA sont reprises dans [14], et que les spécifications du compilateur sont reprises dans [10]. 2.1 Répartition des threads Nous avons déjà vu, dans le chapitre consacré à l architecture matérielle, comment les threads étaient organisés. Nous allons maintenant voir comment programmer le GPU pour paramétrer les nombres de blocs et de threads par bloc. Du point de vue logiciel, un appel du kernel crée une grille. Cette grille peut être de dimension 1, 2 ou 3, et est divisée en blocs. Les nombres de blocs par grille et de threads par bloc sont spécifiés à l appel du kernel. Une représentation graphique de ce qui vient d être présenté est disponible figure 2.1. Au niveau du langage CUDA, un kernel est appelé depuis le CPU par le code suivant : MyKernel <<< NumberOfBlocks, ThreadsPerBlock >>>( Arg0, Arg1,...); Les variables NumberOfBlocks et ThreadsPerBlock sont du type dim3, type spécifique à CUDA. Les variables de ce type possèdent trois champs. Si seul le premier champ est affecté, la grille sera à une dimension. Si les deux premiers champs sont affectés, la grille sera de dimension deux. Et finalement, si les trois champs sont affectés, la grille sera à trois dimensions. Exemples d utilisation des variables de type dim3 dim3 NumberOfBlocks ( 256); // 1 D grid of 256 blocks dim3 NumberOfBlocks (16, 16); // 2 D grid of 16 X 16 blocks 13

24 dim3 NumberOfBlocks (16, 16, 2); // 3 D grid of 16 X 16 X 2 blocks Grid 0 Block (0, 0) Block (0, 1) Block (0, 2) Block (1, 0) Block (1, 1) Block (1, 2) Grid 0 Block 0 Block 1 Block 2 Block 3 Block (2, 0) Block (2, 1) Block (2, 2) Grid 0 Block (0, 0, 0) Block (0, 1, 0) Block (0, 0, 1) Block (0, 1, 1) Block (1, 0, 0) Block (1, 1, 0) Block (1, 0, 1) Block (1, 1, 1) Figure 2.1 Grilles de dimension 1, 2 et 3 Remarquons qu au niveau du bloc, nous avons également une organisation à 1, 2 ou 3 dimensions. Exemple d un lancement de kernel Pour cet exemple, supposons des warps de taille 4. La commande : dim3 ThreadsPerBlock (2, 2); dim3 NumberOfBlocks (2, 2); MyKernel < < < NumberOfBlocks, ThreadsPerBlock > > >(); lancera 16 threads, organisés selon la figure 2.2. Il est important de noter que la taille des warps reste de 32, quelle que soit la dimension du bloc. Les blocs de dimension 2 et 3 diviseront leurs threads en groupes de 32 dans un espace linéaire. La figure 2.3 reprend ce qui vient d être dit pour une taille de warp, hypothétique, de 2. 14

25 Block(0, 0) Block(0, 1) Thread (0, 0) Thread (0, 1) Thread (0, 0) Thread (0, 1) Thread (1, 0) Thread (1, 1) Thread (1, 0) Thread (1, 1) Block(1, 0) Block(1, 1) Thread (0, 0) Thread (0, 1) Thread (0, 0) Thread (0, 1) Thread (1, 0) Thread (1, 1) Thread (1, 0) Thread (1, 1) Figure 2.2 Exemple d exécution (d après [12]) Threads (0, 0) (0, 1) (1, 0) (1, 1) (2, 0) (2, 1) Warp Space (0, 0) Figure 2.3 Division en warp des blocs de dimension supérieure à un (warp de taille 2) 2.2 Codes CPU, GPU et kernel Nous venons de voir comment lancer un kernel. L étape suivante est de voir comment coder un kernel. Dans le langage CUDA, il existe trois mots-clés spéciaux : global : définit un kernel device : définit une fonction exécutée par le device, et ne pouvant être appelée que depuis le device host : définit une fonction exécutée par l host, et ne pouvant être appelée que depuis l host Ces trois mots-clés sont placés devant le type de la fonction. Par exemple : global void MyKernel ( int *a){ a [0] = 42; } Il est important de noter qu un kernel est toujours de type void. Une question légitime est de se demander quelle est la différence entre une fonction global et une fonction device. La réponse est simple : une fonction global est appelée depuis le CPU et est exécutée sur le GPU. Une fonction device est, quant à elle, appelée 1 et exécutée sur le GPU. Pour ce qui est des fonctions device, elles peuvent être de type quelconque, à l inverse des kernels. Pour finir, notons qu une fonction n ayant aucun mot-clé est considérée comme une fonction host. 1. Appelée dans le cadre d exécution d une fonction global 15

26 2.3 Distinction entre les threads Nous avons vu comment écrire un kernel, et comment ses threads seront organisés. Maintenant, nous allons voir comment distinguer les threads, afin de les distribuer entre les données. Un code kernel a à sa disposition un ensemble de variables spéciales, appelées built-in variables. Celles-ci sont fixées, à l appel, pour chaque thread. Ces built-in variables sont : griddim : contient la taille de la grille blockidx : contient l identifiant du bloc, dans lequel est le thread courant blockdim : contient la taille du bloc, dans lequel est le thread courant threadidx : contient l identifiant, local au bloc, du thread courant Notons que toutes ces variables sont des structures contenant les champs x, y et z. Ceuxci permettent de sélectionner la dimension recherchée. Prenons l exemple de l addition de vecteurs de taille N : Kernel d addition vectorielle global void VectAdd ( float *a, float *b, float *c, int N){ int i = blockidx. x * blockdim. x + threadidx. x; } if(i < N) // a, b and c of size N c[i] = a[i] + b[i]; Notons la présence d une instruction de type if. Cette instruction peut, éventuellement, amener à une divergence des threads du même warp, comme vu au chapitre précédent. Cependant, cette instruction est indispensable si plus de N threads ont été lancé. De plus, un seul chemin d exécution est non trivial. En effet, le chemin ne vérifiant pas la condition du if se termine directement. 2.4 Transferts mémoires Nous sommes maintenant aptes à écrire et à lancer des kernels CUDA. Mais, nous devons encore programmer les échanges de données entre host et device Allocation de la mémoire device Avant de copier des segments de mémoire, nous devons allouer l espace device nécessaire. La fonction prévue à cet effet est cudamalloc. Cette fonction s utilise assez naturellement. Le code suivant, alloue un segment de mémoire de 42 flottants sur le device. Allocation de mémoires device float * vdevice ; 16

27 cudamalloc (( void **) & vdevice, sizeof ( float ) * 42); Après cette opération, le pointeur vdevice pointe vers le segment de mémoire alloué sur le GPU. La mémoire device allouée peut être libérée par la fonction cudafree. Celle-ci s utilise exactement comme la fonction free Copies entre host et device Copies synchrone Maintenant que la mémoire device est allouée, nous pouvons y introduire des données. La fonction chargée des transferts est cudamemcpy. Cette fonction s utilise comme suit : cudamemcpy ( void * DestinationPointer, const void * SourcePointer, size_ t SizeInByteToCopy, enum cudamemcpykind KindOfCopy ); où KindOfCopy peut être : cudamemcpyhosttodevice, si le pointeur source est un pointeur vers le host, et si le pointeur destination pointe vers le device cudamemcpydevicetohost, si le pointeur source est un pointeur vers le device, et si le pointeur destination pointe vers l host Notons que KindOfCopy peut prendre d autres valeurs. Illustrons cette fonction par un exemple : chargeons un segment de mémoire de 17 flottants de l host vers le device. Ensuite, rechargeons ce segment vers l host. Transferts mémoires float * vdevice, * vhost ; // Allocate host memory // vhost = ( float *) malloc ( sizeof ( float ) * 17); // Allocate device memory // cudamalloc (( void **) & vdevice, sizeof ( float ) * 17); // Host --- Device copy // cudamemcpy ( vdevice, vhost, sizeof ( float ) * 17, cudamemcpyhosttodevice ); // Device --- Host copy // cudamemcpy ( vhost, vdevice, sizeof ( float ) * 17, cudamemcpydevicetohost ); Notons que la fonction cudamemcpy retourne uniquement quand le transfert est terminé : il s agit d un transfert synchrone. 17

28 Comme annoncé au chapitre 1, des mécanismes de copie asynchrone existent. Sans entrer dans les détails, cette copie s effectue au moyen de la fonction cudamemcpyasync. Dans ce cas, le segment de mémoire host doit être page-locked, ce qui implique que le segment ne changera pas d adresse physique. Nous verrons plus tard comment allouer de tels segments. Remarquons que l utilisation de segments page-locked permet d accélérer les transferts mémoires en utilisant le mécanisme de DMA 2. Cependant, le segment restant toujours en mémoire physique, l espace mémoire accessible diminue. Mémoire page-locked et copie asynchrone Afin d obtenir un segment de mémoire page-locked, nous disposons de deux techniques. La première méthode est l utilisation de la fonction cudamallochost. Celle-ci permet d allouer un segment de mémoire host page-locked. Son utilisation est la suivante : cudamallochost ( void ** ptr, size_ t size ); où : ptr pointe vers le segment de mémoire host alloué size est la taille, en octet, à allouer Pour ce qui est de la libération de la mémoire, celle-ci s effectue par la fonction cudafreehost. Celle-ci s utilise exactement comme free. Notons que cette première technique est affectée du problème suivant : le segment ne pourra jamais être débloqué de la mémoire physique. Ainsi, si nous devons allouer un grand nombre de segments, nous arriverons rapidement à court de mémoire. Partant de ce constat, nous aimerions pouvoir bloquer un segment, le temps d une copie, puis le débloquer. Nous en arrivons ainsi à notre deuxième méthode. Celle-ci exploite la fonction cudahostregister 3. Cette dernière s utilise comme suit : cudahostregister ( void * ptr, size_ t size, unsigned int flags ); où : ptr pointe vers le segment de mémoire à bloquer size donne la taille du segment à bloquer flags permet de passer un ensemble de drapeaux, dont nous ne rentrerons pas dans le détail La fonction permettant de débloquer un segment est simplement cudahostunregister. Celle-ci ne prend qu un seul paramètre, le pointeur du segment bloqué. Grâce à ces deux fonctions, nous pouvons donc bloquer et débloquer un segment en mémoire physique. Cependant, les segments mémoires utilisables par cudahostregister doivent vérifier deux propriétés : L adresse de départ du segment doit être un multiple de 4 KB La taille du segment doit être un multiple de 4 KB 2. DMA : Direct Memory Access mécanisme déchargeant le processeur des transferts mémoires 3. Notons que cudahostregister n existe qu à partir de la version 4 de CUDA (voir [17]) 18

29 On parle d alignement, pour l adresse et pour la taille, de 4 KB. La contrainte sur l adresse de départ peut être vérifiée, pour les systèmes UNIX, en allouant la mémoire par la fonction posix_memalign. Celle-ci prend les arguments suivants : posix_ memalign ( void ** memptr, size_ t alignment, size_ t size ); tels que : après l allocation, memptr pointe vers le segment alignement nous donne l alignement sur l adresse de départ size nous donne la taille du segment Pour ce qui est de la contrainte sur la taille du segment, la solution est très simple. Si le segment à bloquer n est pas multiple de 4 KB, il suffit de bloquer un segment plus grand, multiple de 4 KB. Le code suivant illustre la deuxième méthode. Allouons un segment pointé par c et de taille N, en octet, quelconque. Exemple d utilisation de cudahostregister // Alloc aligned memory (4 K = 4096)// posix_memalign (( void **)&c, 4096, N); // Lock page // if(n % 4096) // We lock a bigger segment cudahostregister ( c, (( N / 4096) + 1) * 4096, 0); else // We have a good segment cudahostregister ( c, N, 0); // Unlock page // cudahostunregister ( c); // Free memory // free (c); Une question légitime est de savoir pourquoi les segments doivent être alignés sur 4 KB. Il est assez compliqué de répondre à cette question, mais nous pouvons remarquer le fait suivant : les systèmes d exploitation modernes utilisent, par défaut, des tailles de page de 4 KB. Mais, il est important de rappeler, que cette taille de page peut être modifiée. 2.5 Exemple récapitulatif Nous sommes maintenant capables d écrire notre premier code CUDA complet. Prenons comme exemple l addition vectorielle. Addition vectorielle : code complet # include < stdlib.h> 19

30 # include <cuda.h> // CUDA Library global void VectAddKernel ( float *a, float *b, float *c, int N){ int i = blockidx. x * blockdim. x + threadidx. x; // Thread global ID if(i < N) c[i] = a[i] + b[i]; // Each thread do a part of the job } void VectAdd ( float * ahost, float * bhost, float * chost, int N){ float * adevice, * bdevice, * cdevice ; // Allocate device memory // cudamalloc (( void **) & adevice, sizeof ( float ) * N); cudamalloc (( void **) & bdevice, sizeof ( float ) * N); cudamalloc (( void **) & cdevice, sizeof ( float ) * N); // Copy ahost and bhost on device // cudamemcpy ( adevice, ahost, sizeof ( float ) * N, cudamemcpyhosttodevice ); cudamemcpy ( bdevice, bhost, sizeof ( float ) * N, cudamemcpyhosttodevice ); // Launch kernel // dim3 TpB (256); dim3 BpG ((N + TpB.x - 1) / TpB.x); VectAddKernel <<<BpG, TpB >>>( adevice, bdevice, cdevice, N); // Get result form host // cudamemcpy ( chost, cdevice, sizeof ( float ) * N, cudamemcpydevicetohost ); } // Free device memory // cudafree ( adevice ); cudafree ( bdevice ); cudafree ( cdevice ); int main ( void ){ int N = 42; // Vector size float *a, *b, *c; // Allocate host memory // a = ( float *) malloc ( sizeof ( float ) * N); b = ( float *) malloc ( sizeof ( float ) * N); c = ( float *) malloc ( sizeof ( float ) * N); // Compute Addition ( of noise vectors ) // VectAdd (a, b, c, N); // Now, we have c = a + b // 20

31 // Free host memory // free (a); free (b); free (c); } return 0; En anticipant sur la section 2.9, signalons que la compilation de ce code (baptisons la source add.cu 4 ) est réalisée par la commande : nvcc add.cu Pour finir cette section, remarquons que le header des librairies CUDA est cuda.h. 2.6 CUDA Streams Généralités Comme nous l avons vu précédemment, certaines cartes NVIDIA permettent de réaliser du parallélisme de tâche. Plus précisément, ces cartes sont capables de gérer simultanément des transferts mémoires et des opérations de calcul. Dans le jargon CUDA, on parle de concurrent copy and execution. Les architectures pouvant exploiter ce parallélisme disposent de copy engines. Une architecture avec : Un copy engine peut gérer en parallèle 5 un transfert mémoire, soit montant, soit descendant Deux copy engines peut gérer en parallèle deux transferts mémoires, un montant et un descendant Ce mécanisme de copy engine permet, dans certains cas, une forte accélération du code, en exploitant le mécanisme de pipeline. Nous entendons par pipeline, le fait de pouvoir charger des données, tout en continuant l exécution d un kernel. Illustrons ce principe à la figure 2.4. Pour en terminer avec les généralités, notons que le gain du pipeline, a un prix en termes de complexité de programmation, comme nous le verrons ci-après. 4. Les sources CUDA ont l extension.cu 5. Comprendre en plus du kernel 21

32 Copie montante : Gestion du temps sans pipeline Exécution du Kernel : Copie descendante : Temps Copie montante : Gestion du temps avec pipeline Exécution du Kernel : Copie descendante : Temps Figure 2.4 Illustration du principe de pipeline (cas de deux copy engines) CUDA Streams Techniquement parlant, ce mécanisme de pipeline s implémente via des CUDA Streams. Ce mécanisme nous permet d obtenir une garantie sur l ordre des opérations. Ainsi, nous pourrons être certains du fait que : les copies devices 6 seront terminées avant le lancement du kernel le kernel aura terminé son exécution avant les copies hosts 7 Notons que, sans surprises, les transferts mémoires devront être asynchrones, pour pouvoir utiliser le mécanisme de pipeline. En effet, il est impossible d obtenir la configuration temporelle de la figure 2.4, si nous devons attendre la fin de chaque transfert mémoire. Au niveau du langage CUDA, la création d un stream est le résultat de la commande suivante. Création d un CUDA Stream // Stream declaration cudastream_ t Stream ; // Stream creation cudastreamcreate (& Stream ); Pour ce qui est de la destruction d un stream, nous utiliserons la fonction : cudastreamdestroy ( Stream ); où Stream est le stream à détruire. 6. Comprendre de l host vers le device 7. Comprendre du device vers l host 22

33 L attachement d une opération de copie (asynchrone) à un stream est le résultat de la commande suivante : cudamemcpyasync ( void * DestinationPointer, const void * SourcePointer, size_t SizeInByteToCopy, enum cudamemcpykind KindOfCopy, cudastream_ t Stream ); Notons que cette fonction prend les mêmes arguments que cudamemcpy, ainsi qu un stream CUDA. Maintenant que nous savons comment attacher une copie à un stream, il ne nous reste plus qu à montrer comment attacher un kernel à un stream. Cette opération se fait au lancement du kernel : MyKernel <<<BpG, TpB, DS, Stream > > >(...); Nous retrouvons l appel classique d un kernel, avec deux arguments supplémentaires : DS il s agit de la quantité de mémoire partagée (en octet) allouée dynamiquement par bloc 8 Stream il s agit simplement du stream sur lequel le kernel sera attaché Par défaut, ces arguments sont mis à zéro, tout en respectant le type du paramètre Synchronisation des tâches Comme nous l avons vu, l organisation de plusieurs tâches en pipeline demande des appels asynchrones. Cependant, il nous faut un mécanisme permettant au CPU d attendre la fin du pipeline. Dans l approche sans pipeline, cette synchronisation était assurée par la copie 9, synchrone, descendante 10. Notons que, implicitement, les copies et le kernel sont dans le même stream. Ainsi, la copie descendante attendra la fin du kernel. Et, comme la copie est synchrone, le CPU attendra la fin de celle-ci. En conclusion, sans pipeline nous avons une synchronisation implicite sur la copie descendante. Pour la structure en pipeline, nous n avons aucun mécanisme implicite de synchronisation. Nous devrons donc exploiter un mécanisme explicite. A cette fin, nous utiliserons les CUDA Events. Ceux-ci peuvent être attachés à des streams, et disposent d une opération de synchronisation. Cette opération bloquera le CPU, tant que l event n a pas été exécuté par le stream. Notons que ce mécanisme de synchronisation peut être global. Dans ce cas, il faut attacher l event au stream 0. Ce stream désigne le contexte CUDA. Sans entrer dans les détails, un contexte peut être vu comme un processus, au sens classique sur CPU. Ainsi, un contexte englobe tous les streams. 8. Cette quantité de mémoire s ajoutera alors à la mémoire allouée statiquement 9. Rappelons qu un appel kernel est toujours asynchrone Nous ne pouvons donc pas utiliser le kernel pour la synchronisation 10. Nous entendons par copie descendante, une copie du device vers l host De façon symétrique, une copie montante va de l host vers le device 23

34 Plus précisément, un event est dit terminé, lorsque toutes les commandes qui le précèdent dans un stream ont été accomplies. Si jamais l event est attaché au stream O, l event est terminé dès que toutes les commandes, de tous les streams, ont été accomplies. Au niveau du langage CUDA, la création d un event est prise en charge par la fonction cudaeventcreate. Celle-ci prend un pointeur vers une variable de type cudaevent_t. L exemple suivant illustre la création d un event. Création d un event cudaevent_ t e; // Event declaration cudaeventcreate (& e); // Event creation La destruction de l event est assurée par cudaeventdestroy. Cette fonction prend en argument l event à détruire. L attachement d un event à un stream s effectue par l opération cudaeventrecord. Notons qu en plus de lier l event à stream, cette fonction fixe la position de l event. Celle-ci est la même que la position de la fonction. Cette dernière prend les arguments suivants : cudaeventrecord ( cudaevent_ t event, cudastream_ t stream ); où : event est l event à attacher stream est le stream sur lequel l event sera attaché Notons que si stream est mis à 0, l event sera attaché au contexte CUDA. Maintenant, il ne nous reste plus qu à synchroniser le CPU sur l event. Ainsi, dès que cudaeventrecord aura été exécuté par le stream, ou par le contexte, le processus host pourra continuer d avancer dans son code. La fonction permettant cette synchronisation est cudaeventsynchronize. Celle-ci prend en argument l event à attendre Le parallélisme de tâche Maintenant que nous sommes capables d attacher et de synchroniser des copie asynchrones et des kernels à des streams, nous pouvons exploiter le mécanisme vu au début de cette section. Pour rappel, les streams nous permettent d avoir une garantie sur l ordre d exécution des opérations. Les events, quant à eux, nous permettent de nous synchroniser. Prenons comme exemple l exécution de deux kernels, chacun demandant une copie montante et une descendante. Nous aimerions organiser les copies et les kernels, afin d obtenir une structure en pipeline. Le code suivant est parfaitement correct. Nous ignorerons les étapes d allocation de mémoire, afin de nous concentrer sur le mécanisme de pipeline. Première tentative de pipeline // Create Streams // cudastream_ t streama, streamb ; cudastreamcreate (& streama ); cudastreamcreate (& streamb ); 24

35 // Create Event // cudaevent_ t sync ; cudaeventcreate (& sync ); // Upload -- Kernel -- Download -- Stream A // cudamemcopy ( pdevicea, phosta, sizeinbytea, cudamemcpyhosttodevice, streama ); kernela <<<BpG, TpB, 0, streama >>>( pdevicea ); cudamemcopy ( phosta, pdevicea, sizeinbytea, cudamemcpydevicetohost, streama ); // Upload -- Kernel -- Download -- Stream B // cudamemcopy ( pdeviceb, phostb, sizeinbyteb, cudamemcpyhosttodevice, streamb ); kernelb <<<BpG, TpB, 0, streamb >>>( pdeviceb ); cudamemcopy ( phostb, pdeviceb, sizeinbyteb, cudamemcpydevicetohost, streamb ); // Synchronize on CUDA Context // cudaeventrecord ( sync, 0); cudaeventsynchronize ( sync ); // Here, we have phosta and phostb processed // // Destroy Event // cudaeventdestroy ( sync ); // Destroy Streams // cudastreamdestroy ( streama ); cudastreamdestroy ( streamb ); Contrainte hardware Bien que parfaitement licite, le code précédent n exploitera pas la configuration en pipeline. La raison provient de la gestion hardware des streams. Pour le hardware, les streams n existent pas, seuls les engines 11 existent! Ainsi, le hardware place les tâches sur les engines, indépendamment de leurs streams d origine, et dans l ordre des appels. Mais, afin de garantir l ordre d exécution, le hardware peut bloquer une tâche mise sur un engine. De plus, les tâches suivantes seront mises en attente. Ainsi, le pipeline est rompu. 11. Nous entendons par engine, soit un copy engine, soit un engine d exécution kernel 25

36 Dans le cas où nous appelons, d abord toutes les tâches du premier stream, puis toutes les tâches du second stream, nous nous retrouvons avec nos engines dans la configuration de la figure 2.5. Pour des questions de simplicité, nous supposerons un seul copy engine. Copy Engine Copie Device (Stream A) Copie Host (Stream A) Copie Device (Stream B) Copie Host (Stream B) Attente Bloquage Kernel Engine Kernel (Stream A) Kernel (Stream B) Figure 2.5 État des engines pour la première tentative de pipeline (d après [20]) Comme nous l avons signalé, afin de respecter l ordre des opérations, un engine peut suspendre une tâche. Ainsi, partant de la figure 2.5, nous pouvons constater les faits suivants : 1. La copie vers l host, pour le stream A, doit attendre la fin du kernel A. 2. La copie vers le device, pour le stream B, vient après la copie vers l host, pour le stream A. 3. Ainsi, la copie vers le device, pour le stream B, est forcée d attendre la fin du kernel A. 4. On ne peut donc pas, en même temps, copier et exécuter un kernel. Donc, finalement, le pipeline est rompu. Une organisation des engines conduisant à un pipeline est disponible à la figure 2.6. Copy Engine Copie Device (Stream A) Copie Device (Stream B) Copie Host (Stream A) Copie Host (Stream B) Pipeline Kernel Engine Kernel (Stream A) Kernel (Stream B) Figure 2.6 Organisation des engines conduisant à un pipeline (d après [20]) Dans ce nouveau cas, la copie vers le device, pour le stream B, peut avoir lieu en même temps que l exécution du kernel A. Ainsi, nous nous retrouvons dans une situation de pipeline. Notons qu il est toujours possible que les copies descendantes, dans l attentes de leurs résultats, bloquent le pipeline. Ceci peut être en partie évité, si le nombre de kernel à exécuter est grand. Ainsi, seules les dernières copies bloqueront le pipeline. Au niveau de l implémentation, il suffit simplement de lancer d abord toutes les copies montantes, puis tous les kernels, puis toutes les copies descendantes. Le code suivant reprend ce qui vient d être expliqué. Implémentation conduisant à un pipeline // Create Streams // cudastream_ t streama, streamb ; cudastreamcreate (& streama ); cudastreamcreate (& streamb ); 26

37 // Create Event // cudaevent_ t sync ; cudaeventcreate (& sync ); // Uploads // cudamemcopy ( pdevicea, phosta, sizeinbytea, cudamemcpyhosttodevice, streama ); cudamemcopy ( pdeviceb, phostb, sizeinbyteb, cudamemcpyhosttodevice, streamb ); // Kernels // kernela <<<BpG, TpB, 0, streama >>>( pdevicea ); kernelb <<<BpG, TpB, 0, streamb >>>( pdeviceb ); // Downloads // cudamemcopy ( phosta, pdevicea, sizeinbytea, cudamemcpydevicetohost, streama ); cudamemcopy ( phostb, pdeviceb, sizeinbyteb, cudamemcpydevicetohost, streamb ); // Synchronize on CUDA Context // cudaeventrecord ( sync, 0); cudaeventsynchronize ( sync ); // Here, we have phosta and phostb processed // // Destroy Event // cudaeventdestroy ( sync ); // Destroy Streams // cudastreamdestroy ( streama ); cudastreamdestroy ( streamb ); 2.7 Le multi GPU La gestion de plusieurs GPUs pose le problème suivant : les devices doivent être commandés par le host, mais le host ne peut commander qu un seul device. Pour remédier à ce problème, la solution consiste à multithreader l host : chaque thread de l host s occupera alors de la gestion d un GPU. Dans le cadre ce travail, l API utilisée est OpenMP Toutes les spécifications de OpenMP sont disponibles dans [18] 27

38 Du point de vue de la programmation, pour qu un thread host s attache à un device, celui-ci doit exécuter la fonction cudasetdevice(deviceid). La variable deviceid est un entier désignant le device. Notons que les devices sont numérotés en partant de zéro. Remarquons qu il existe des fonctions permettant de renvoyer tous les devices compatibles CUDA, leur identifiant et leurs propriétés. Pour plus d informations, nous renvoyons le lecteur à [14]. 2.8 Synchronisation et opérations atomiques Remarquons tout d abord que les architectures de GPUs disposent d opérations atomiques. Afin d obtenir des codes de calculs généraux et parallèles, il est indispensable de disposer de mécanismes de synchronisation. Il existe un mécanisme de barrière 13 pour les threads d un même bloc : il s agit de la fonction syncthreads(). Si la synchronisation doit être effectuée à un niveau supérieur au bloc (entre deux blocs par exemple), celle-ci devra être codée explicitement. Dans ce but, nous pouvons exploiter les opérations atomiques du GPU et la mémoire globale. Étant donné le passage par la mémoire globale, un tel mécanisme sera extrêmement lent. 2.9 Compilation Au niveau de la compilation, NVIDIA a publié son propre compilateur CUDA, appelé nvcc. Ce compilateur commence par décomposer le code en sa partie device et sa partie host. La partie device sera compilée par nvcc lui-même, tandis que la partie host sera compilée grâce à : The GNU compiler pour les plateformes Linux The Microsoft Visual Studio compiler pour les plateformes Windows Notons qu il est possible de changer le compilateur host de nvcc. Afin de gérer la compilation de projets très vastes, des méthodes de compilation de plus haut niveau existent. Dans le cadre de ce travail, nous utiliserons l outil de développement CMake 14. Celui-ci dispose d un module gérant la compilation des codes hosts et devices. Ce module, du nom de FindCUDA 15, est disponible nativement dans la version 2.8 de CMake. Remarquons que les fonctions compilées par nvcc seront affectées par du name mangling 16. Ce processus peut conduire à des incompréhensions entre nvcc et le compilateur host. Afin de résoudre ce problème, les fonctions vues par les deux compilateurs devront être marquées comme extern "C". 13. Tous les threads du même bloc doivent exécuter la barrière, avant de pouvoir continuer dans leurs codes 14. Toutes les informations sur CMake sont disponibles sur la page : Les spécifications de FindCUDA sont disponibles sur le site de CMake 16. Dans certains cas, en fonction du langage, le compilateur peut ajouter des éléments aux noms des fonctions, des variables, etc Cette opération s appelle le name mangling 28

39 2.10 Mécanisme de programmation de plus bas niveau Jusqu à présent, nous avons discuté du langage CUDA C 17. Cependant, il ne s agit pas du seul mécanisme de programmation proposé par NVIDIA. Pour ce qui est du contrôle du GPU par le CPU, celui-ci peut être beaucoup plus explicite en passant par le Driver API. A ce niveau, le GPU doit être géré beaucoup plus explicitement. Cette gestion est automatisée dans la version haut niveau présentée dans les sections précédentes. Par exemple, via le Driver API, le lancement d un kernel n est pas simplement : VectAddKernel <<<BpG, TpB >>>( adevice, bdevice, cdevice, N); Ce lancement serait de la forme : Version simplifiée d un lancement de kernel par le Driver API // Set Kernel Arguments // cuparamsetv ( VectAddKernel, & adevice ); cuparamsetv ( VectAddKernel, & bdevice ); cuparamsetv ( VectAddKernel, & cdevice ); cuparamsetv ( VectAddKernel, &N); // Set 1D Blocks // cufuncsetblockshape ( VectAddKernel, TpB, 1, 1); // Launch Kernel in a 1 D Grid // culaunchgrid ( VectAddKernel, BpG, 1); Notons que cette façon de procéder est reprise par le langage OpenCL. Au niveau de la programmation du GPU, il exsite également un langage d assembleur pour les machines NVIDIA. Ce langage porte le nom de PTX 18. Notons que le compilateur nvcc est également capable d assembler un code PTX. Il est aussi possible de demander au compilateur de ne générer que le code assembleur, via la commande : nvcc - ptx mysource. cu Pour terminer, notons que toutes les spécifications de PTX sont disponibles dans [13]. 17. Ou, plus simplement, le langage CUDA 18. PTX : Parallel Thread Execution 29

40 Chapitre 3 Le langage OpenCL Le langage OpenCL est un langage de programmation pour GPU se voulant multiplateforme, standard et ouvert. A l origine, ce langage a été développé par Apple pour être ensuite repris par le Khronos Group, connu pour le standard OpenGL. Bien que la majeure partie du présent travail soit basée sur CUDA, il nous paraissait indispensable d aborder le sujet d OpenCL, ne serait-ce que pour ses aspects multiplateformes. En effet, il serait dommage de se limiter aux seules plateformes NVIDIA. Ce chapitre est basé sur les références [5, 16, 4]. 3.1 Introduction Philosophiquement, l approche OpenCL est assez proche de l approche CUDA. Cependant, notons deux grandes différences. La première différence est que, comme nous allons le voir par la suite, les codes OpenCL sont beaucoup plus verbeux que les codes CUDA C 1. Ceci s explique par le coté multiplateforme d OpenCL. La seconde différence est la suivante : le code device est compilé à l exécution du code host. Le code host est, quant à lui, compilé par un compilateur C/C++ quelconque. Notons que le header des fonctions OpenCL est contenu dans CL/cl.h. L argument avancé pour la compilation au runtime est qu ainsi le code dispose toujours des dernières avancées en termes de compilation. Le revers de la médaille se situe dans un overhead au lancement du code. Cependant, si le problème traité est suffisamment grand, l overhead est négligeable face au temps de calcul. 1. En réalité, les codes OpenCL sont aussi verbeux que les codes basés sur le Driver API de NVIDIA 30

41 3.2 Vocabulaire Dans l approche OpenCL certains concepts CUDA sont repris, mais avec des appellations différentes. Dans un souci de concision, nous introduirons à la table 3.1 les concepts importants communs entre OpenCL et CUDA. CUDA Thread Bloc Grille Mémoire partagée Mémoire registre OpenCL Work item Work group NDRange Local memory Private memory Table 3.1 Comparaison entre les termes CUDA et OpenCL (d après [5]) Du point de vue matériel, OpenCL étant multiplateforme, certains termes hardware ont également été renommés. Ces changements de désignation sont repris à la table 3.2. CUDA OpenCL Streaming processor (SP) Processing element (PE) Streaming multiprocessor (SM) Compute unit (CU) Table 3.2 Termes OpenCL désignant le matériel (d après [5]) Pour terminer cette section sur le vocabulaire, dans les spécifications d OpenCL un nouveau concept apparait : il s agit de la plateforme. On entend par plateforme l ensemble constitué d un host et de un, ou plusieurs, devices. Ce mécanisme permet la gestion de plusieurs GPUs. 3.3 Code kernel Les codes kernels OpenCL sont fort similaires à leur homologue CUDA. Les différences les plus importantes sont les suivantes : le code kernel est précédé du mot-clé kernel, au lieu de global les arguments du code kernel ne peuvent pas être des structures les arguments du code kernel, résidant en mémoire (device) globale, doivent être précédés du mot-clé global il n y a pas de build-in variables, mais des fonctions permettant d obtenir les différents identifiants Les fonctions permettant de distinguer les working items sont reprises à la tables

42 Fonction OpenCL Description Équivalent CUDA get_global_id Identifiant global blockidx * blockdim + threadidx du working item get_local_id Identifiant local threadidx du working item get_global_size Taille du NDRange griddim * blockdim get_local_size Taille du working group blockdim Table 3.3 Fonctions identifiant les working items (d après [5]) Ces fonctions prennent comme argument un entier, celui-ci étant la dimension considérée. Notons que la numérotation commence à zéro. Par exemple, get_local_id(1) correspond à threadidx.y, c est-à-dire à la dimension 2 de l identifiant local du working item. 3.4 Code host Nous présenterons dans cette section les différentes étapes pour écrire un code host Contexte OpenCL Nativement, OpenCL est capable de gérer plusieurs devices 2. La notion de contexte permet de traiter ce problème. Un contexte désigne, entres autres, un ensemble de devices. Dans le langage OpenCL, la fonction clcreatecontext permet de retourner un contexte 3. cl_ context où : clcreatecontext ( const cl_context_properties * properties, cl_ uint num_ devices, const cl_ device_ id * devices, void ( CL_CALLBACK * pfn_notify ) ( const char * errinfo, const void * private_ info, size_ t cb, void * user_data ), void * user_data, cl_int * errcode_ret ) *properties, permet de spécifier la plateforme à utiliser (si ce pointeur est NULL, la plateforme sélectionnée est définie par l implémentation d OpenCL) num_device, donne le nombre de devices dans *devices *devices, pointe vers la liste de devices à utiliser dans le contexte *errcode_ret, pointe vers une variable, où le code d erreur sera retourné *pfn_notify, pointe vers une fonction à appeler en cas d erreur Les autres paramètres sont les arguments de la fonction pointée par *pfn_notify 2. Rappelons nous qu avec CUDA, nous devions multithreader le code host, afin que chaque thread s occupe d un device 3. Un contexte est de type cl_context 32

43 Afin d obtenir la liste des devices, nous utiliserons les fonctions clgetplatformids puis clgetdeviceids. Ces fonctions s utilisant assez naturellement, nous ne les spécifierons pas dans ce travail 4. Notons simplement que ces deux fonctions seront toujours appelées deux fois : une fois pour obtenir le nombre de plateformes (ou de devices), et une fois pour pour obtenir la liste des plateformes (ou des devices) File de commandes La seconde étape pour écrire un code OpenCL est de déclarer une file de commande ou Command Queue. Cette file 5 permettra à l host de charger les instructions kernel. La fonction retournant une command queue est clcreatecommandqueue. Une fois encore, cette fonction s utilisant assez naturellement, elle ne sera pas spécifiée. Pour terminer, notons qu en cas d utilisation de plusieurs devices, il est nécessaire de créer une file de commande par device Compilation du kernel Pour commencer, notons que le code kernel est vu depuis l host comme un simple tableau de caractères. Création d un objet programme La première étape pour compiler le kernel est de créer un objet programme. Pour ce faire, nous pouvons utiliser la fonction clcreateprogramwithsource. cl_program clcreateprogramwithsource ( cl_context context, cl_ uint count, const char ** strings, const size_ t * lengths, cl_int * errcode_ret ); où : context, est le contexte count, donne le nombre de tableaux de caractères contenus dans strings **strings, pointe vers un tableau, de tableaux de caractères, contenant les caractères du code kernel *lengths, pointe vers un tableau contenant la taille des tableaux de strings *errcode_ret, pointe vers le code retourné en cas d erreur Il est très important de noter que, même si length est connu à la compilation, celui-ci doit être alloué par malloc. 4. Notons que les spécifications de toutes les fonctions OpenCL sont disponibles dans [4] 5. Notons que cette file n est pas forcément de type FIFO (Fist In First Out) : en effet, l exécution des instructions kernel peut être en mode out-of-order 6. L identifiant du device est l un des arguments de clcreatecommandqueue 33

44 Notons qu avec OpenCL, il n y a pas explicitement de fonctions devices, comme avec les fonctions CUDA device. Si nous voulons déclarer une telle fonction, il suffit simplement de mettre son code dans le tableau de caractères contenant le code kernel. Compilation du kernel La fonction compilant le kernel est clbuildprogram. cl_ int où : clbuildprogram ( cl_ program program, cl_ uint num_ devices, const cl_ device_ id * device_ list, const char * options, void ( CL_CALLBACK * pfn_notify ) ( cl_ program program, void * user_data ), void * user_data ) program, est l objet programme num_device, est le nombre de device contenu dans la liste device_list *device_list, pointe la liste des devices sur lesquels le kernel sera exécuté *options, pointe vers une liste d options à passer au compilateur (voir [4]) Le reste des arguments concerne la fonction appelée en cas d erreur Création d un objet kernel Pour terminer la compilation, il ne reste plus qu à définir un objet kernel. Cet objet pourra être utilisé pour appeler le kernel. La fonction chargée de retourner le kernel est clcreatekernel. Cette fonction s utilise assez naturellement, et ne sera pas spécifiée dans ce travail Allocation et transferts mémoires L allocation de la mémoire device s effectue via la fonction clcreatebuffer. Cette fonction renvoie un objet mémoire lié au segment device alloué. cl_ mem clcreatebuffer ( cl_ context context, cl_ mem_ flags flags, size_ t size, void * host_ptr, cl_int * errcode_ret ) où : context, est le contexte flags, permet de modifier l allocation, et l utilisation, d un segment de mémoire *host_ptr, permet de copier un segment, directement à l allocation, de l host vers le device (nécessite l utilisation d un flag) *errcode, pointe vers le code retourné en cas d erreur 34

45 Parmi les valeurs de flags, citons CL_MEM_COPY_HOST_PTR permettant de copier le segment pointé par *host_ptr directement à l allocation. Notons qu une fois le segment copié, les segments host et device sont indépendants. Pour ce qui est de la lecture d un segment de mémoire device, nous devons passer par la fonction clenqueuereadbuffer. cl_ int clenqueuereadbuffer ( cl_ command_ queue command_ queue, cl_ mem buffer, cl_ bool blocking_ read, size_ t offset, size_t cb, void *ptr, cl_ uint num_ events_ in_ wait_ list, const cl_ event * event_ wait_ list, cl_event * event ) où : command_queue, est la file de commandes buffer, est l objet mémoire lié au segment device à lire blocking_read, spécifie si la lecture est synchrone (CL_TRUE), ou non (CL_FALSE) offset, donne un offset du segment à lire cb, donne le nombre d octets à lire *ptr, pointe vers le segment de mémoire host, où les données device seront copiées num_events_in_wait_list, donne le nombre d éléments dans event_wait_list *event_wait_list, pointe vers une liste d évènements à vérifier avant d effectuer la lecture. *event, pointe vers un évènement produit par clenqueuereadbuffer pour identifier l opération de lecture Notons qu en plus du flag d écriture de clcreatebuffer, il existe un mécanisme d écriture en mémoire device similaire à clenqueuereadbuffer. Il s agit simplement de la fonction clenqueuewritebuffer. Cette fonction s utilise d une manière analogue à son homologue de lecture Appel du kernel Avant d appeler le kernel, il importe de lui passer ses arguments : cette étape est réalisée par la fonction clsetkernelarg. Notons que cette fonction s utilise assez naturellement, et ne sera donc pas spécifiée dans ce travail. La fonction clenqueuendrangekernel permet l appel proprement dit du kernel. clenqueuendrangekernel ( cl_command_queue command_queue, cl_ kernel kernel, cl_ uint work_ dim, const size_ t * global_ work_ offset, const size_ t * global_ work_ size, const size_ t * local_ work_ size, cl_ uint num_ events_ in_ wait_ list, const cl_ event * event_ wait_ list, cl_event * event ) 35

46 où : command_queue, est la file de commandes, où le kernel sera lancé kernel, est le kernel à lancer work_dim, donne la dimension du NDRange et des work groups *global_work_offset, pointe vers un segment donnant un offset pour l identifiant global des work items (l élément 0 donne l offset pour la dimension 1, l offset 1 pour la dimension 2, etc) *global_work_size, donne la division du NDRange en work groups (équivalent de NumberOfBlocks sous CUDA) *local_work_size, donne la division des work groups en work items (équivalent de ThreadsPerBlock sous CUDA) Les autres arguments gèrent les évènements (voir clenqueuereadbuffer) Libération des ressources Tout au long de notre code, nous avons alloué un ensemble d objets. Afin d avoir une présentation complète, la table 3.4 reprend les fonctions utiles pour libérer les ressources. Fonction clreleasememobject clreleasekernel clreleaseprogram clreleasecommandqueue clreleasecontext Objet libéré Mémoire Kernel Programme File de commandes Contexte Table 3.4 Fonctions pour libérer les ressources 3.5 Un exemple complet Un exemple complet de multiplication matricielle est disponible à l adresse : Sous Linux, ce code se compile via la commande : gcc - lopencl - lm clmmult. c où : -lopencl, permet de lier notre code avec les librairies OpenCL -lm, permet à notre code d utiliser la fonction ceil (arrondit un réel à l entier supérieur) 36

47 Chapitre 4 Étude des performances Maintenant que nous sommes familiers avec les architectures de GPUs et avec leurs langages de programmation, nous pouvons commencer à comparer différents algorithmes de calculs. Dans ce chapitre, nous étudierons différentes implémentations de la multiplication matricielle, de la multiplication matrice vecteur, et de la multiplication vecteur vecteur. 4.1 Notions complémentaires Librairies BLAS Il existe un ensemble de librairies optimisées pour l algèbre linéaire. Ces librairies portent le nom générique de BLAS, pour Basic Linear Algebra Subprograms. ATLAS, pour Automatically Tuned Linear Algebra Software, est une des implémentations de BLAS. A la compilation, celle-ci teste différents algorithmes, afin de ne retenir que les plus performants pour la plateforme donnée. Dans le monde du GPU, NVIDIA a développé sa propre version de BLAS, appelée CUBLAS, optimisée pour ses plateformes de calcul. Notons qu il n existe pas, à l heure actuelle, d équivalent OpenCL à CUBLAS Cartes NVIDIA Tesla Les cartes Tesla sont une déclinaison de la gamme des cartes graphiques de NVIDIA, mais uniquement dédiées aux calculs. Par exemple, ces cartes ne disposent pas de sorties vidéos. 4.2 Matériel utilisé Les tests de ce travail ont été réalisés sur quatre machines différentes : desktop fermi 37

48 lmgpu gameboy Plateforme desktop Hardware La plateforme desktop est un ordinateur classique, dédié aux jeux vidéo et équipé d un processeur Intel Core 2 Duo E6750 et d une carte graphique NVIDIA GeForce 8500 GT. Le processeur est un dual-core, cadencé à 2.66 GHz et considéré comme étant d entrée de gamme en La carte graphique possède, quant à elle, 16 streaming processors cadencés à 450 MHz et était également considérée comme d entrée de gamme en Au niveau de la mémoire, l host dispose de 2 GB, tandis que le device n en possède que 512 MB. Figure 4.1 Photographie d une carte graphique NVIDIA 8500 GT Pour terminer, notons que toutes les spécifications sont disponibles sur les pages internet suivantes : E6750 : GT : Software Les différents logiciels disponibles sur la plateforme desktop sont repris à la tables 4.1. Le CUDA Toolkit est la suite logicielle proposée par NVIDIA, comprenant le compilateur nvcc, les librairies CUDA et CUBLAS, un debugger (cuda-gdb), etc. 38

49 Logiciel Noyau linux Drivers NVIDIA gcc CUDA Toolkit ATLAS Version (32 bits) Table 4.1 Logiciels disponibles sur desktop Plateforme fermi Hardware La plateforme fermi est l évolution de la plateforme desktop. En effet, au cours de ce travail nous avons acquis une carte NVIDIA GeForce GT430. Celle-ci a remplacé la 8500 GT. La GT430 est une carte entrée de gamme, basée sur l architecture Fermi. Elle dispose de 96 SPs cadencés à 1.40 GHz. Au niveau de la mémoire, nous disposons ici de 1 GB. Notons que la nouvelle carte dispose d une interface PCI Express 2.0. Cependant, notre carte mère ne prend en charge que la version 1.0. Notre carte est donc bridée. Figure 4.2 Photographie d une carte graphique NVIDIA GT430 Pour ce qui est de la partie host, aucune modification n a été apportée, en passant de la plateforme desktop à la plateforme fermi. Toutes les spécifications de la carte NVIDIA GeForce GT430 sont disponibles à l adresse : 39

50 Software Les différents logiciels disponibles sur la plateforme fermi sont repris à la tables 4.2. Signalons le passage à la version 4.0 RC2 du CUDA Toolkit. Logiciel Version Noyau linux (32 bits) Drivers NVIDIA gcc CUDA Toolkit 4.0 RC2 ATLAS 3.8 Table 4.2 Logiciels disponibles sur fermi Plateforme lmgpu Hardware La plateforme lmgpu est une station dédiée aux calculs intensifs sur GPU. Celle-ci est située au CISM 1 de l Université Catholique de Louvain. Du point de vue matériel, cette machine est équipée d un processeur Intel Xeon X5550 et de deux cartes NVIDIA Tesla C1060. Au niveau du CPU, celui-ci dispose de 4 cœurs cadencés à 2.67 GHz. Notons que ceux-ci peuvent exécuter 2 threads en parallèle : ce qui nous donne un total de 8 threads exécutables en parallèle. Pour ce qui est des cartes Tesla, chacune dispose de 240 SPs cadencés à 1.3 GHz : soit un total de 480 SPs pour la station. Au niveau mémoire, nous avons : 24 GB pour l host et 4 GB par device. Pour terminer, nous retrouverons toutes les spécifications sur les pages internet suivantes : X5550 : C1060 : Software Pour ce qui est des logiciels disponibles sur lmgpu, ceux-ci sont repris à la table 4.3. Logiciel Version Noyau linux (64 bits) Drivers NVIDIA gcc CUDA Toolkit 3.0 Table 4.3 Logiciels disponibles sur lmgpu 1. Institut de Calcul Intensif et de Stockage de Masse : 40

51 4.2.4 Plateforme gameboy Hardware La station gameboy est la pointure au-dessus de lmgpu : elle dispose de 2 CPUs Intel Xeon X5550 et de 4 GPUs NVIDIA Tesla C1060. Cette plateforme est détenue par l unité de mécanique appliquée de l Université Catholique de Louvain. Software La table 4.4 reprend les logiciels disponibles sur gameboy. Logiciel Version Noyau linux (64 bits) Drivers NVIDIA gcc CUDA Toolkit 3.0 Table 4.4 Logiciels disponibles sur gameboy 4.3 Multiplication matricielle : cas des matrices carrées Tout au long de cette section, nous étudierons différents algorithmes de multiplication matricielle : AB = C. Notons que toutes les matrices sont carrées et générées aléatoirement. Notre étude consiste à mesurer les temps mis par les différentes implémentations de la multiplication. Nous réaliserons ces mesures via la fonction clock_gettime. Notons qu afin de moyenner nos mesures, sauf mention du contraire, une même expérience sera répétée 100 fois. Pour ce qui est des mesures proprement dites, nous ne tiendrons pas compte du temps consommé par la génération des matrices à multiplier. En d autres termes : Pour les codes CPU, on ne mesure que le temps de calcul de la multiplication Pour les codes CUDA, on mesure le temps de calcul et les temps de transferts, montant et descendant, entre host et device Pour les codes OpenCL, on mesure le temps de calcul, les temps de transferts et le temps de compilation Pour finir, les codes sources des différents algorithmes, ainsi que les résultats obtenus, sont disponibles à l adresse : 41

52 4.3.1 Implémentations naïves Commençons par comparer trois implémentations naïves : une version purement CPU, une CUDA et une OpenCL. La figure 4.3 reprend les différents résultats. Notons que nous entendons par naïf, le fait que les codes ne tiennent compte d aucune optimisation, en particulier au niveau des accès mémoires. Pour ce qui est du temps mis par l algorithme CPU, il n a plus été mesuré pour les tailles de matrices supérieures à GPU Simple (CUDA) GPU Simple (OpenCL) CPU Simple Codes Naifs desktop 10 Temps [s] Taille de la Matrice Figure 4.3 Trois implémentations naïves de la multiplication matricielle (desktop) Nous remarquons immédiatement l accélération entre les versions GPUs et la version CPU. Ainsi, sans trop de travail, des algorithmes lents peuvent être sensiblement accélérés. Notons également un très léger ralenti de la version OpenCL par rapport à la version CUDA. Ceci s explique par la compilation à l exécution : celle-ci consomme quelques fractions de seconde. Cependant, sur de très grandes matrices, ce ralenti est négligeable. Signalons également que ce ralenti est constant sur de grandes matrices. Ceci provient du fait que le temps de compilation ne dépend pas de la taille des matrices. 42

53 4.3.2 BLAS Nous avons vu qu un algorithme naïf pouvait être sensiblement accéléré par GPU. Maintenant, comparons notre version CUDA naïve à une version CPU optimisée. Pour rappel, cette version optimisée de BLAS est ATLAS. La figure 4.4 reprend les résultats obtenus. 6 5 BLAS GPU Simple BLAS desktop 4 Temps [s] Taille de la Matrice Figure 4.4 Comparaison entre un algorithme CUDA naïf et un algorithme CPU optimisé (desktop) Dans ce cas-ci, nous observons immédiatement que la version CPU optimisée est de loin meilleure que la version GPU naïve. En conclusion, la parallélisation massive par GPU peut se révéler inutile, si aucun travail d optimisation n est réalisé. 43

54 4.3.3 CUBLAS Maintenant que nous savons qu il importe d optimiser, même en parallélisation massive, observons l effet de ces optimisations. La figure 4.5 compare les implémentations BLAS et CUBLAS BLAS CUBLAS CUBLAS desktop 1 Temps [s] Taille de la Matrice Figure 4.5 Comparaison entre BLAS et CUBLAS (desktop) Sans surprises, avec la version GPU optimisée, nous retrouvons le gain très important de la parallélisation massive. Remarquons également l accélération sur GPU, en passant d une matrice à une Afin d expliquer ce phénomène, commençons par tester CUBLAS pour un pas de dimension unitaire. En d autres termes, passons d une matrice à une , puis à une , et ainsi de suite. 44

55 0.035 CUBLAS lmgpu Zoom 0.03 Temps [s] Taille de la Matrice Figure 4.6 Performance de CUBLAS pour une évolution progressive des matrices (lmgpu) Nous pouvons remarquer une accélération importante à toutes les matrices Il est fort probable que l implémentation de CUBLAS 3.0, pour la multiplication matricielle, utilise des segments de mémoire de taille multiple de Ainsi, toutes les matrices multiples de ces segments offrent des chemins d accès mémoires optimaux. Ce sont ces chemins d accès qui permettent une accélération substantielle. Ce raisonnement est inspiré de [22]. L argument de l auteur est que la performance du code de multiplication dépend des accès mémoires. Celui-ci propose un code, utilisant des blocs mémoires de pour A et de pour B. Notons que ce code a servi d inspiration à CUBLAS 2.0. Étant donné que nous retrouvons des segments multiples de 16 16, nous pouvons penser que peu de changements ont été effectués, pour la multiplication matricielle, en passant de CUBLAS 2.0 à CUBLAS 3.0. Il est important de noter que le raisonnement précédent n est qu une piste de réflexion. Une explication plus précise demanderait l accès aux sources de CUBLAS 3.0, ce qui n est pas possible à l heure actuelle. Pour terminer, notons que le test, dont est tirée la figure 4.6, n a été réalisé qu avec 10 échantillons par point, au lieu de nos 100 échantillons habituels. En effet, ce test demande un très grand nombre de points. Ainsi, le nombre d échantillons par point a été diminué pour garder un temps de test raisonnable. 45

56 4.3.4 Scaling Jusqu à présent, nous avons exploité exclusivement la plateforme desktop. Regardons si, en passant de 16 SPs à 240 SPs, l accélération est visible. La figure 4.7 reprend les différentes mesures. Temps [s] deskop (GeForce 8500GT) gameboy (Tesla C1060) lmgpu (Tesla C1060) Scaling Simple (CUDA) Scaling Simple (OpenCL) Temps [s] Scaling CUBLAS Temps [s] Taille de la Matrice Figure 4.7 Effet de scaling (GeForce 8500GT & Tesla C1060) Nous remarquons immédiatement l effet très marquant du passage de 16 à 240 SPs. Cependant, au niveau de l implémentation OpenCL, le temps de compilation étant indépendant du device, les petites matrices ne subissent pas l effet du scaling Nous entendons par scaling, l augmentation du nombre de SP 46

57 4.3.5 Transferts mémoires Jusqu à présent, nous ne nous sommes intéressés qu au temps de calcul proprement dit. Maintenant, regardons de plus près le temps mis pour transférer des données Host Device Device Host Calcul CUBLAS gameboy Transferts 0.1 Temps [s] Taille de la Matrice Figure 4.8 Temps de transferts mémoires (gameboy) Nous pouvons immédiatement conclure de la figure 4.8 que les temps de transferts mémoires sont négligeables face au temps de calcul, dans le cas de la multiplication de grandes matrices carrées Multi GPU Jusqu ici, nous n avons utilisé qu une seule des cartes des stations lmgpu et gameboy. Exploitons à présent tout le potentiel de ces stations. Répartition des matrices Pour ce qui est de la répartition des matrices, dans le cas des 2 GPUs de la station lmgpu, nous diviserons la matrice A en deux suivant ses lignes. Ensuite, nous distribuerons ces deux sous-matrices entre les devices. Pour ce qui est de la matrice B, celle-ci sera copiée entièrement sur les devices. Au niveau du résultat, chaque GPU aura sa sous-matrice de C en mémoire. C est au transfert des devices vers l host que la matrice C sera reconstruite dans sa totalité. Une illustration graphique de la répartition des matrices est disponible à la figure

58 A 1 A 2 B = C 1 C 2 Figure 4.9 Répartition des matrices pour 2 GPUs Pour ce qui est de la division des matrices, pour la plateforme gameboy, nous procéderons de façon analogue à lmgpu. Cependant, gameboy ayant 4 GPUs, nous diviserons également la matrice B en deux suivant ses colonnes. Ainsi, la matrice C sera divisée en 4 sous-matrices. Ensuite, chaque device pourra s occuper d une de ces sous-matrices. Nous illustrons ce paragraphe à la figure A 1 A 2 B 1 B 2 = C 11 C 12 C 21 C 22 Figure 4.10 Répartition des matrices pour 4 GPUs Notons que les nombres de lignes de A et de colonnes de B peuvent ne pas être multiples de 2. Dans ce cas, les derniers GPUs devront traiter des matrices plus larges d une ligne et/ou d une colonne. Cependant, ce surplus n est pas gênant. Accélération en temps d exécution Nous avons comparé le code de multiplication matricielle de CUBLAS, en distribuant les matrices sur 1, 2 et 4 GPU. Les deux premiers tests ont été effectués sur lmgpu, tandis que le dernier provient de gameboy. La table 4.11 reprend les résultats obtenus. Notons que nous avons également vérifié si le passage de, respectivement, 1 à 2 GPUs et 1 à 4 GPUs, accélérait le calcul d un facteur, respectivement 2 et 4. Sur la figure 4.11, nous retrouvons, plus au moins, les facteurs d accélération attendus. Pour finir, la figure 4.12 permet de vérifier que chaque GPU est soumis à la même charge que ses congénères. Nous remarquons que la distribution de la charge est, comme attendue, homogène. 48

59 GPU 2 GPUs 4 GPUs Temps sur 2 GPUs 2 Temps sur 4 GPUs 4 Multi GPU Scaling Temps [s] Taille de la Matrice Figure 4.11 Accélération apportée par le multi-gpu Temps [s] GPU0 GPU1 Multi GPU lmgpu Distribution Temps [s] Taille de la Matrice Multi GPU gameboy Distribution GPU0 GPU1 GPU2 GPU Taille de la Matrice Figure 4.12 Distribution de la charge entre les GPUs 49

60 Accélération en GFLOPS Pour terminer notre étude sur le multi-gpu, nous avons évalué l accélération en termes d opérations en virgule flottante. Pour avoir accès au nombre d opérations via nos données temporelles, nous avons exploité la relation suivante : GFLOPS = 2 N 3 t 10 9 où : N est la taille de la matrice (carrée) t est le temps de calcul Cette formule se base sur le fait que la multiplication matricielle demande de l ordre de 2N 3 opérations. Les résultats de ce dernier test sont repris à la table GPU 2 GPUs 4 GPUs FLOPS 1000 GFLOPS Taille de la Matrice Figure 4.13 Accélération apportée par le multi-gpu (en GFLOPS) Notons tout d abord les impressionnants pics à environ 1300 GFLOPS 3 pour gameboy. Ce résultat est à mettre en parallèle avec le nombre maximum d opérations d un GPU. Dans le cas de la carte NVIDIA Tesla C1060, nous avons 622 GFLOPS par carte en simple précision 4. Soit 2488 GFLOPS pour les 4 cartes de gameboy, ce qui nous donne des pics à environ 50% de la puissance maximale. 3. Cette valeur est à comparer à la centaine de GFLOPS théorique, atteinte par l architecture Westmere, dernière-née des laboratoires d Intel 4. Ce résultat ne tiens pas compte du gain donné par les SFUs 50

61 Pour ce qui est des autres pics de gameboy, ceux-ci se situent à environ 400 GFLOPS, soit seulement 16% de la puissance maximale. Enfin, pour ce qui est des autres plateformes, nous retrouvons les rapports de 16% et 50%. Partant des derniers constats, nous pressentons l intérêt d un algorithme exploitant la structure de la sous-section Un tel algorithme pourrait, par exemple, allouer des matrices un peu plus grandes, afin de satisfaire cette structure. Nous obtiendrons ainsi, paradoxalement, une accélération substantielle tout en calculant plus d éléments! Pour rappel, ceci est dû à l importance des transferts mémoires dans la multiplication matricielle. Pour terminer, notons que la figure 4.13 ne présente pas la structure de la figure 4.6. Ce phénomène provient simplement du sous-échantillonnage de la taille des matrices. En effet, à la figure 4.6 nous échantillonnons les matrices par pas de 1 1, tandis qu à la figure 4.13 nous échantillonnons les matrices par pas de Double Précision Jusqu alors, nous n avons exécuté que des codes en simple précision. Comme annoncé au chapitre 1, le passage à la double précision s accompagne d une perte de vitesse. La figure 4.14 permet d observer ce phénomène float double CUBLAS (double) lmgpu Temps [s] Taille de la Matrice Figure 4.14 Passage de la simple à la double précision (lmgpu) 5. Pour rappel, une division de A en blocs de 64 16, et de B en

62 Rappelons que ce phénomène s explique par un nombre plus faible de SPs capables de double précision. Pour la carte NVIDIA Tesla C1060, on compte un total de 240 SPs en simple précision, contre 30 en double précision. Nous venons d observer le comportement en double précision de la plateforme lmgpu. Pour rappel, celle-ci n est pas basée sur l architecture Fermi. A présent, observons le passage à la double précision pour la plateforme fermi. Les résultats sont repris à la figure Comme nous pouvons le constater, nous avons également repris les valeurs obtenues pour la double précision de la plateforme lmgpu float(fermi) double(fermi) double(lmgpu) CUBLAS (double) fermi 0.08 Temps [s] Taille de la Matrice Figure 4.15 Passage de la simple à la double précision (fermi) Pour commencer, rappelons que la plateforme fermi implémente la version 4.0 du toolkit CUDA, tandis que la plateforme lmgpu n implémente que la version 3.0. Revenons au graphique de la figure Rappelons que la station fermi ne possède que 96 SPs. Cependant, nous remarquons des performances semblables, par rapport à la station lmgpu, qui dispose de 240 SPs. Ce phénomène s explique par le fait que l architecture Fermi dispose, proportionnellement, de plus de SPs capables de double précision. Ainsi, in fine, malgré un plus petit nombre de SPs, les performances en double précision sont du même ordre de grandeur. Pour terminer cette section sur la double précision, comparons les performances des deux plateformes précédentes, par rapport à une approche sur CPU. Les résultats sont disponibles à la figure

63 3.5 3 CPU GPU (GT430) GPU (C1060) BLAS VS CUBLAS (double) 2.5 Temps [s] Taille de la Matrice Figure 4.16 Passage de la simple à la double précision : comparaisons CPU / GPU Nous remarquons immédiatement que, malgré des performances réduites en double précision, le gain d un calcul sur GPU reste important. 4.4 Multiplication matricielle : matrice carrée & matrice rectangulaire Jusqu à présent, nous avons étudié le cas très particulier des matrices carrées. Cependant, en pratique, il n est pas rare d avoir à multiplier une matrice carrée avec une matrice non carrée. A[N N] B[N M] = C[N M] Figure 4.17 Cas de la multiplication d une matrice carrée par une matrice rectangulaire 53

64 Tester les performances d une telle multiplication est compliqué, étant donné que nous avons deux paramètres variables. En pratique, le nombre de colonnes, M, de la matrice non carrée est faible. Par exemple, dans un problème électromagnétique résolu par éléments finis, celui-ci est égal à 6. En effet, dans un tel problème, le nombre de variables est de 3 pour le champ magnétique et de 3 pour le champ électrique, soit un total de 6 variables. Dans le cadre de ce travail, nous appelons matrice rectangulaire, une matrice [N M] telle que N M ou N M. Dans le cadre des tests suivants, nous utiliserons des matrices non carrées de taille N 6, et nous ferons varier le paramètre N. Signalons que nous testerons nos codes directement en double précision. Comparons les performances de la plateforme fermi, dans le cas de la situation exposée aux paragraphes précédents Multiplication Matricielle [N N] [N 6] (double) fermi BLAS CUBLAS Temps [s] Taille de la Matrice (N) Figure 4.18 Étude des performances dans le cas de la multiplication de matrices [N N] [N 6] (fermi) Nous constatons directement des résultats plus mitigés, par rapport à la multiplication de matrices carrées. En effet, nous constatons directement qu il est plus avantageux d utiliser une version CPU de BLAS, pour de faibles valeurs de N. Signalons également que, même dans le cas de grandes valeurs de N, le gain sur GPU n est pas spectaculaire. Cette diminution des performances est liée aux temps de transferts mémoires entre host et device. En effet, dans le cas de matrices rectangulaires, le temps passé dans les transferts mémoires n est plus négligeable, face au temps de calcul, comme nous pouvons le voir à la figure Notons que celle-ci est à comparer avec la figure

65 Multiplication Matricielle [N N] [N 6] (double): Transfert fermi Calcul Transferts: Host Device Host Temps [s] Taille de la Matrice (N) Figure 4.19 Étude des performances dans le cas de la multiplication de matrices [N N] [N 6] : temps de transferts (fermi) A la vue de ces résultats, nous pouvons conclure que, dans le cas des matrices rectangulaire, les temps de transferts mémoires ne sont pas négligeables. Ainsi, afin d obtenir un code de calcul performant, nous devrons, autant que faire ce peut, optimiser les transferts mémoires entre host et device. Cette contrainte nous forcera à étudier l algorithme à implémenter au niveau des transferts mémoires, afin de minimiser ceux-ci. Il nous sera donc plus difficile de porter un code sur GPU, si celui-ci est exigeant en termes de multiplication par des matrices rectangulaires. Observons à présent le comportement de la multiplication si nous faisons varier le paramètre N par pas unitaire, exactement comme nous l avons fait dans le cas de la figure 4.6. Les résultats sont disponibles à la figure

66 Multiplication Matricielle [N N] [N 6] (double) fermi Temps [s] Taille de la Matrice (N) Figure 4.20 Étude des performances dans le cas de la multiplication de matrices [N N] [N 6] : variation de N par pas unitaire (fermi) Nous pouvons constater qu il n y a pas de structure particulière, comme nous avons pu le constater à la figure 4.6, dans le cas des matrices carrées. Pour finir l étude des matrices rectangulaires, signalons que plus la petite dimension de la matrice monte, plus nous nous rapprochons d une matrice carrée. Ainsi, le gain en performance sera accru. 4.5 Multiplication matrice vecteur Abordons maintenant le problème de la multiplication matrice vecteur. Dans ce travail, nous aborderons le cas particulier d une matrice carrée. En effet, il s agit du cas de figure, extrêmement courant, rencontré dans la résolution des systèmes d équations algébriques. Avant d entrer dans le vif du sujet, remarquons que notre problème est un cas particulier de la multiplication d une matrice carrée par une matrice rectangulaire. En effet, un vecteur peut être vu comme une matrice rectangulaire, où la petite dimension est unitaire. Cette constatation nous laisse présager de mauvaises performances, lors du calcul sur GPU. Étudions le cas de la multiplication d une matrice [N N] par un vecteur de dimension N. Les résultats sont repris à la figure Signalons que les calculs ont été réalisés sur la plateforme fermi en double précision. 56

67 Multiplication Matrice Vecteur (double) fermi BLAS CUBLAS Temps [s] Taille de la Matrice (N) Figure 4.21 Étude des performances dans le cas de la multiplication d une matrice carrée par un vecteur (fermi) Nous constatons immédiatement une chute dramatique des performances lors du calcul sur GPU. Comme expliqué à la section précédente, cette diminution des performances est due aux temps des transferts mémoires. Nous pouvons observer ce phénomène à la figure Comme nous pouvons le constater, dans le cas de la multiplication d une matrice carrée par un vecteur, les temps de transferts sont largement supérieurs aux temps de calculs. Il est donc encore plus critique de minimiser les transferts mémoires, dans le développement d un code de calcul basé sur des multiplications matrice vecteur. Pour terminer, au niveau du comportement de la multiplication, dans le cas d une variation de la dimension par pas unitaire, nous n observons aucune structure particulière. Ceci n est pas particulièrement étonnant, puisque, déjà dans le cas des matrices rectangulaires, nous n avons trouvé aucune structure. Les résultats sont repris à la figure

68 Multiplication Matrice Vecteur (double) fermi Calcul Transferts: Host Device Host 0.01 Temps [s] Taille de la Matrice (N) Figure 4.22 Étude des performances dans le cas de la multiplication d une matrice carrée par un vecteur : temps de transfert (fermi) 7 x 10 3 Multiplication Matrice Vecteur (double) fermi Temps [s] Taille de la Matrice (N) Figure 4.23 Étude des performances dans le cas de la multiplication d une matrice carrée par un vecteur : variation de la dimension par pas unitaire (fermi) 58

69 4.6 Multiplication vecteur vecteur Terminons ce chapitre par l étude de la multiplication vecteur vecteur. Dans le cadre de ce travail, nous étudierons le cas très courant du produit scalaire. Au vu des résultats des sections précédentes, nous nous attendons à des performances médiocres sur GPU. Dans cette section, le cas test est simple : via la plateforme fermi, nous observerons le temps de calcul, en double précision, du produit scalaire de deux vecteurs de taille N. Le paramètre variable sera N. Les résultats sont reprise à la figure x Multiplication Vecteur Vecteur (double) fermi BLAS CUBLAS Temps [s] Taille du Vecteur x 10 5 Figure 4.24 Étude des performances dans le cas de la multiplication vecteur vecteur (fermi) Comme prévu, les performances s écroulent totalement dans le cas du produit scalaire sur GPU. En effet, nous observons un temps de calcul 10 fois plus court sur CPU. La raison provient encore une fois des transferts mémoires. Dans le cas du produit matrice vecteur, nous avions déjà un temps de calcul plus faible que le temps de transfert. Le passage à un produit scalaire ne peut, en aucun cas, améliorer le rapport temps de calculs sur temps de transferts. En réalité, ce rapport est dégradé lors du passage du produit matrice vecteur au produit vecteur vecteur. En effet, le produit scalaire demande de l ordre de N opérations, contre N 2 opérations pour le produit matrice vecteur. En conclusion, dans le cadre d un code de calcul exploitant des produits scalaires, il est impératif de minimiser les transferts mémoires. 59

70 4.7 Résumé & Conclusion Au travers de ce chapitre, nous avons observé le comportement, sur GPU, de trois opérations d algèbre linaire : le produit matrice matrice le produit matrice vecteur le produit vecteur vecteur Nous avons pu constater une impressionnante accélération, lors du passage sur GPU, dans le cas de la multiplication de matrices carrées. Rappelons que pour obtenir cette accélération, nous avons utilisé une implémentation efficace des opérations d algèbre linéaire sur GPU. Cette implémentation porte le nom de CUBLAS. Cette accélération ne provient pas tant de la parallélisation massive offerte par le GPU, mais plutôt d un bon rapport temps de calculs sur temps de transferts. Dans les autres cas 6, le passage sur GPU s accompagnait de résultats mitigés. En effet, dans ces cas de figure, les temps de transferts entre host et device ne peuvent plus être négligés. Nous pouvons constater que, plus nous nous éloignons, en termes de dimensions, du produit de matrices carrées, plus le rapport temps de calculs sur temps de transferts se détériore. En conclusion, afin d exploiter le potentiel de parallélisation massive sur GPU, nous devrons étudier finement les transferts mémoires de l algorithme à implémenter. En effet, l utilisation du GPU n a de sens que si le rapport temps de calculs sur temps de transferts est grand. Dans tous les autres cas, nous préférerons l utilisation de CPUs 7. Signalons que les produits de grandes matrices carrées nous garantissent un rapport temps de calculs sur temps de transferts grand. Ainsi, autant que faire ce peut, cette opération devra être privilégiée. Pour terminer, la figure 4.25 reprend les résultats principaux en termes de performances. 6. Pour rappel, il s agit des produits matrice carrée matrice rectangulaire, matrice carrée vecteur et vecteur vecteur 7. Notons que dans le cas d une programmation hybride CPU/GPU, il est légitime de porter un code aussi rapide sur CPU que sur GPU, afin de libérer le CPU de cette tâche 60

71 Multiplication Matricielle [N N] [N N] (double) fermi BLAS CUBLAS Taille de la Matrice Taille de la Matrice (N) Temps [s] Temps [s] Multiplication Matrice Vecteur (double) fermi BLAS CUBLAS Taille de la Matrice (N) Taille du Vecteur x Multiplication Matricielle [N N] [N 6] (double) fermi BLAS CUBLAS Temps [s] 1.8 x Temps [s] Multiplication Vecteur Vecteur (double) fermi BLAS CUBLAS Figure 4.25 Principaux résultats en termes de performances 61

72 Chapitre 5 Problème de la vue adaptative Jusqu à présent, nous avons étudié, d un point de vue relativement théorique, les possibilités offertes par le calcul sur GPU. Les chapitres suivants se proposent d étudier différents problèmes pratiques, et d étudier les performances lors du passage sur GPU. Plus particulièrement, dans ce chapitre, nous étudierons le problème de la vue adaptative. Nous commencerons par introduire le problème. Ensuite, nous proposerons le portage sur GPU d un code CPU. Pour finir, nous étudierons les performances de ce portage sur la plateforme fermi. 5.1 Introduction Le problème traité dans ce chapitre est celui de la vue adaptative. Afin d exposer le problème, imaginons une fonction quelconque définie sur un domaine. Nous désirons afficher une couleur différente, suivant les valeurs prises par cette fonction. Le problème est le suivant. Les librairies graphiques ne peuvent afficher que des variations linéaires de couleurs, sur un domaine donné. Par exemple, si nous voulons représenter une fonction variant en x 2, nous ne pourrons faire varier les couleurs qu en x. Ainsi, l image de cette fonction n en sera pas une représentation fidèle. Une solution consiste à diviser le domaine, afin d interpoler linéairement, et par morceaux, la fonction à afficher. De cette façon, l image sur l écran reflètera plus fidèlement la fonction. Illustrons ces derniers paragraphes par un exemple. Supposons que nous voulons afficher une variation de couleur en x 2 sur le domaine x [ 1, 1]. La figure 5.1 reprend : La variation à afficher (courbe noire) La variation affichée, si nous interpolons directement sur le domaine [ 1, 1] (courbe bleue) La variation affichée, si nous interpolons sur une division en 4 sous-domaines (courbe rouge) Enfin, dans le cas de plusieurs domaines, nous pouvons diviser chaque domaine différemment et suivant les besoins. Cette solution porte le nom de vue adaptative. Celle-ci est utilisée, entre autres, dans la visualisation de solutions éléments finis, où les éléments utilisés sont d ordre supérieur à un. Pour plus d information, nous renvoyons le lecteur à [19]. 62

73 1.5 Variation interpolée linéairement 1 Variation interpolée sur une division du domaine 0.5 Variation à afficher Implémentation Base de travail Figure 5.1 Illustration du principe de vue adaptative Dans ce travail, nous partirons du code de vue adaptative présent dans le logiciel open source gmsh 1. Notre objectif sera de porter ce code sur GPU, en le modifiant le moins possible. A cette fin, nous modifierons la classe fullmatrix, chargée de représenter les matrices et les vecteurs utilisés par le code de vue adaptative Classe fullmatrix Cette classe est disponible dans la partie./numeric/ du code de gmsh. Cette classe utilise intensivement les opérations BLAS. Nous pouvons donc substituer les appels BLAS sur CPU, par des appels à CUBLAS. Signalons qu en plus de ces substitutions, nous devrons gérer les transferts mémoires entre host et device. Étant donné qu a priori, nous ne savons pas comment cette classe sera utilisée, nous utiliserons le schéma de transfert suivant. Pour chaque méthode appelant une fonction CUBLAS, nous commencerons par charger les arguments sur le device. Ensuite, nous lancerons le kernel de calcul. Et pour finir, nous rapatrierons les données sur l host. 1. Logiciel de maillage, de pré-processing et de post-processing (voir [2]) : 63

74 A titre d exemple, prenons l implémentation de la méthode gemm de fullmatrix. Soient a, b et c des objets fullmatrix, et soient alpha et beta des entiers. Alors, c.gemm(a, b, alpha, beta) revient à c := alpha * a * b + beta * c. Le code de cette méthode est repris ci-après. Code de la méthode gemm de fullmatrix template <> void fullmatrix < double >:: gemm ( const fullmatrix < double > &a, const fullmatrix < double > & b, double alpha, double beta ){ // Few initializations // int rowa = a. size1 (), cola = a. size2 (); int rowb = b. size1 (), colb = b. size2 (); int M = _r, N = _c, K = cola ; int LDA = rowa, LDB = rowb, LDC = _r; int Asize = rowa * cola, Bsize = rowb * colb, Csize = _r * _c; double *AD, *BD, *CD; // Device pointers cublasinit (); // Device Memory // cublasalloc ( Asize, sizeof ( double ), ( void **)& AD ); cublasalloc ( Bsize, sizeof ( double ), ( void **)& BD ); cublasalloc ( Csize, sizeof ( double ), ( void **)& CD ); // Host to Device Copy, et cublassetmatrix (rowa, cola, sizeof ( double ), a. _data, LDA, AD, LDA ); cublassetmatrix (rowb, colb, sizeof ( double ), b. _data, LDB, BD, LDB ); cublassetmatrix (_r, _c, sizeof ( double ), _ data, LDC, CD, LDC ); // CUBLAS Call // cublasdgemm ( N, N, M, N, K, alpha, AD, LDA, BD, LDB, beta, CD, LDC ); // Device to Host Copy // cublasgetmatrix (_r, _c, sizeof ( double ), CD, LDC, _data, LDC ); // Free Device Memory // cublasfree (AD ); cublasfree (BD ); cublasfree (CD ); } // Release CUBLAS resources // cublasshutdown (); Notons que ce code exploite les fonctions de haut niveau offertes par CUBLAS. En effet, certaines primitives CUDA, telles que les copies mémoires, ont été abstraites dans CUBLAS. 64

75 Ainsi, par exemple, la copie montante d une matrice s effectue au moyen de la fonction cublassetmatrix. Toutes les fonctions CUBLAS sont reprises dans [9]. Le code complet du portage est disponible à l adresse suivante : montefiore.ulg.ac.be/~marsic. 5.3 Intégration du code CUBLAS dans gmsh Pour commencer, signalons que nous avons utilisé uniquement les fonctions disponibles dans la libraire CUBLAS. Or, cette librairie peut être utilisée indépendamment des librairies de base CUDA. De plus, les kernels utilisés par CUBLAS sont déjà compilés. Ainsi, le compilateur nvcc n est d aucune utilité. Partant de ce constat, nous devons uniquement demander au compilateur host, de lier notre code avec la librairie CUBLAS. Plus particulièrement, gmsh utilise la plateforme CMake comme outil de compilation. Ainsi, afin de compiler notre classe fullmatrix modifiée, nous devons ajouter les lignes suivantes au CMakeLists.txt du projet gmsh : INCLUDE_ DIRECTORIES (/ PATH_ TO_ CUDATOOLKIT / include ) LINK_LIBRARIES ("-L/ PATH_TO_CUDATOOLKIT / lib / - lcublas ") 5.4 Résultats Présentation du cas de test Afin de comparer les implémentations CPU et GPU, utilisons le cas test suivant. Soit l étude par éléments finis de l onde de pression au niveau d un réacteur d avion. Les éléments utilisés lors de la résolution sont des polynômes de Lagrange d ordre 8, dont le support est un triangle (ce qui nous donne en tout 45 degrés de liberté par triangle). La solution, sans vue adaptative, est reprise à la figure 5.2. Figure 5.2 Cas test sans vue adaptative (d après [19]) Appliquons maintenant la vue adaptative, en divisant les triangles problématiques en 4, puis en 16, et enfin en 64. Le schéma de division est repris à la figure 5.3. Les résultats de la vue adaptative sont repris à la figure

76 Figure 5.3 Division d un triangle en sous-triangles Figure 5.4 Cas test avec vue adaptative (d après [19]) : division, de plus en plus fine, des domaines problématiques (la dernière figure représente la solution finale sans son maillage) Étude des performances Observons maintenant le temps mis par les implémentations CPU et GPU. Les résultats sont repris à la figure 5.5. Signalons que l axe horizontal donne la division des triangles. Celle-ci est exprimée en 4x, où x est la valeur représentée sur l axe. Nous constatons immédiatement l effondrement des performances, lors du passage sur GPU. Afin d expliquer ce phénomène, observons la taille des matrices mises en jeu. Dans le cas d une division des triangles en 64 sous triangles, nous notons 1635 multiplications de matrices [45 3] par des matrices [3 3]. En conclusion, cette implémentation passe plus de temps à transférer les matrices entre l host et le device qu à multiplier ces matrices! 66

. Plan du cours. . Architecture: Fermi (2010-12), Kepler (12-?)

. Plan du cours. . Architecture: Fermi (2010-12), Kepler (12-?) Plan du cours Vision mate riel: architecture cartes graphiques NVIDIA INF 560 Calcul Paralle le et Distribue Cours 3 Vision logiciel: l abstraction logique de l architecture propose e par le langage CUDA

Plus en détail

Introduction à CUDA. gael.guennebaud@inria.fr

Introduction à CUDA. gael.guennebaud@inria.fr 36 Introduction à CUDA gael.guennebaud@inria.fr 38 Comment programmer les GPU? Notion de kernel exemple (n produits scalaires): T ci =ai b ( ai, b : vecteurs 3D, ci for(int i=0;i

Plus en détail

Initiation au HPC - Généralités

Initiation au HPC - Généralités Initiation au HPC - Généralités Éric Ramat et Julien Dehos Université du Littoral Côte d Opale M2 Informatique 2 septembre 2015 Éric Ramat et Julien Dehos Initiation au HPC - Généralités 1/49 Plan du cours

Plus en détail

Introduction à la programmation des GPUs

Introduction à la programmation des GPUs Introduction à la programmation des GPUs Anne-Sophie Mouronval Mesocentre de calcul de l Ecole Centrale Paris Laboratoire MSSMat Avril 2013 Anne-Sophie Mouronval Introduction à la programmation des GPUs

Plus en détail

Calcul multi GPU et optimisation combinatoire

Calcul multi GPU et optimisation combinatoire Année universitaire 2010 2011 Master recherche EEA Spécialité : SAID Systèmes Automatiques, Informatiques et Décisionnels Parcours : Systèmes Automatiques Calcul multi GPU et optimisation combinatoire

Plus en détail

Architecture des ordinateurs

Architecture des ordinateurs Décoder la relation entre l architecture et les applications Violaine Louvet, Institut Camille Jordan CNRS & Université Lyon 1 Ecole «Découverte du Calcul» 2013 1 / 61 Simulation numérique... Physique

Plus en détail

Limitations of the Playstation 3 for High Performance Cluster Computing

Limitations of the Playstation 3 for High Performance Cluster Computing Introduction Plan Limitations of the Playstation 3 for High Performance Cluster Computing July 2007 Introduction Plan Introduction Intérêts de la PS3 : rapide et puissante bon marché L utiliser pour faire

Plus en détail

Une bibliothèque de templates pour CUDA

Une bibliothèque de templates pour CUDA Une bibliothèque de templates pour CUDA Sylvain Collange, Marc Daumas et David Defour Montpellier, 16 octobre 2008 Types de parallèlisme de données Données indépendantes n threads pour n jeux de données

Plus en détail

Architecture des calculateurs

Architecture des calculateurs Formation en Calcul Scientifique - LEM2I Architecture des calculateurs Violaine Louvet 1 1 Institut Camille jordan - CNRS 12-13/09/2011 Introduction Décoder la relation entre l architecture et les applications

Plus en détail

Segmentation d'images à l'aide d'agents sociaux : applications GPU

Segmentation d'images à l'aide d'agents sociaux : applications GPU Segmentation d'images à l'aide d'agents sociaux : applications GPU Richard MOUSSA Laboratoire Bordelais de Recherche en Informatique (LaBRI) - UMR 5800 Université de Bordeaux - France Laboratoire de recherche

Plus en détail

Métriques de performance pour les algorithmes et programmes parallèles

Métriques de performance pour les algorithmes et programmes parallèles Métriques de performance pour les algorithmes et programmes parallèles 11 18 nov. 2002 Cette section est basée tout d abord sur la référence suivante (manuel suggéré mais non obligatoire) : R. Miller and

Plus en détail

INTRODUCTION AUX SYSTEMES D EXPLOITATION. TD2 Exclusion mutuelle / Sémaphores

INTRODUCTION AUX SYSTEMES D EXPLOITATION. TD2 Exclusion mutuelle / Sémaphores INTRODUCTION AUX SYSTEMES D EXPLOITATION TD2 Exclusion mutuelle / Sémaphores Exclusion mutuelle / Sémaphores - 0.1 - S O M M A I R E 1. GENERALITES SUR LES SEMAPHORES... 1 1.1. PRESENTATION... 1 1.2. UN

Plus en détail

Introduction à la Programmation Parallèle: MPI

Introduction à la Programmation Parallèle: MPI Introduction à la Programmation Parallèle: MPI Frédéric Gava et Gaétan Hains L.A.C.L Laboratoire d Algorithmique, Complexité et Logique Cours du M2 SSI option PSSR Plan 1 Modèle de programmation 2 3 4

Plus en détail

Introduction au calcul parallèle avec OpenCL

Introduction au calcul parallèle avec OpenCL Introduction au calcul parallèle avec OpenCL Julien Dehos Séminaire du 05/01/2012 Sommaire Introduction Le calculateur du CGR/LISIC/LMPA Généralités sur OpenCL Modèles Programmation Optimisation Conclusion

Plus en détail

03/04/2007. Tâche 1 Tâche 2 Tâche 3. Système Unix. Time sharing

03/04/2007. Tâche 1 Tâche 2 Tâche 3. Système Unix. Time sharing 3/4/27 Programmation Avancée Multimédia Multithreading Benoît Piranda Équipe SISAR Université de Marne La Vallée Besoin Programmes à traitements simultanés Réseau Réseau Afficher une animation en temps

Plus en détail

EPREUVE OPTIONNELLE d INFORMATIQUE CORRIGE

EPREUVE OPTIONNELLE d INFORMATIQUE CORRIGE EPREUVE OPTIONNELLE d INFORMATIQUE CORRIGE QCM Remarque : - A une question correspond au moins 1 réponse juste - Cocher la ou les bonnes réponses Barème : - Une bonne réponse = +1 - Pas de réponse = 0

Plus en détail

Contrôle Non Destructif : Implantation d'algorithmes sur GPU et multi-coeurs. Gilles Rougeron CEA/LIST Département Imagerie Simulation et Contrôle

Contrôle Non Destructif : Implantation d'algorithmes sur GPU et multi-coeurs. Gilles Rougeron CEA/LIST Département Imagerie Simulation et Contrôle Contrôle Non Destructif : Implantation d'algorithmes sur GPU et multi-coeurs Gilles Rougeron CEA/LIST Département Imagerie Simulation et Contrôle 1 CEA R & D for Nuclear Energy 5 000 people Nuclear systems

Plus en détail

UE Programmation Impérative Licence 2ème Année 2014 2015

UE Programmation Impérative Licence 2ème Année 2014 2015 UE Programmation Impérative Licence 2 ème Année 2014 2015 Informations pratiques Équipe Pédagogique Florence Cloppet Neilze Dorta Nicolas Loménie prenom.nom@mi.parisdescartes.fr 2 Programmation Impérative

Plus en détail

Introduction à la programmation orientée objet, illustrée par le langage C++ Patrick Cégielski cegielski@u-pec.fr

Introduction à la programmation orientée objet, illustrée par le langage C++ Patrick Cégielski cegielski@u-pec.fr Introduction à la programmation orientée objet, illustrée par le langage C++ Patrick Cégielski cegielski@u-pec.fr Mars 2002 Pour Irène et Marie Legal Notice Copyright c 2002 Patrick Cégielski Université

Plus en détail

1. Structure d un programme C. 2. Commentaire: /*..texte */ On utilise aussi le commentaire du C++ qui est valable pour C: 3.

1. Structure d un programme C. 2. Commentaire: /*..texte */ On utilise aussi le commentaire du C++ qui est valable pour C: 3. 1. Structure d un programme C Un programme est un ensemble de fonctions. La fonction "main" constitue le point d entrée pour l exécution. Un exemple simple : #include int main() { printf ( this

Plus en détail

Arithmétique binaire. Chapitre. 5.1 Notions. 5.1.1 Bit. 5.1.2 Mot

Arithmétique binaire. Chapitre. 5.1 Notions. 5.1.1 Bit. 5.1.2 Mot Chapitre 5 Arithmétique binaire L es codes sont manipulés au quotidien sans qu on s en rende compte, et leur compréhension est quasi instinctive. Le seul fait de lire fait appel au codage alphabétique,

Plus en détail

INITIATION AU LANGAGE C SUR PIC DE MICROSHIP

INITIATION AU LANGAGE C SUR PIC DE MICROSHIP COURS PROGRAMMATION INITIATION AU LANGAGE C SUR MICROCONTROLEUR PIC page 1 / 7 INITIATION AU LANGAGE C SUR PIC DE MICROSHIP I. Historique du langage C 1972 : naissance du C dans les laboratoires BELL par

Plus en détail

M2-Images. Rendu Temps Réel - OpenGL 4 et compute shaders. J.C. Iehl. December 18, 2013

M2-Images. Rendu Temps Réel - OpenGL 4 et compute shaders. J.C. Iehl. December 18, 2013 Rendu Temps Réel - OpenGL 4 et compute shaders December 18, 2013 résumé des épisodes précédents... création des objets opengl, organisation des données, configuration du pipeline, draw,... opengl 4.3 :

Plus en détail

Grandes lignes ASTRÉE. Logiciels critiques. Outils de certification classiques. Inspection manuelle. Definition. Test

Grandes lignes ASTRÉE. Logiciels critiques. Outils de certification classiques. Inspection manuelle. Definition. Test Grandes lignes Analyseur Statique de logiciels Temps RÉel Embarqués École Polytechnique École Normale Supérieure Mercredi 18 juillet 2005 1 Présentation d 2 Cadre théorique de l interprétation abstraite

Plus en détail

Brefs rappels sur la pile et le tas (Stack. / Heap) et les pointeurs

Brefs rappels sur la pile et le tas (Stack. / Heap) et les pointeurs Brefs rappels sur la pile et le tas (Stack / Heap) et les pointeurs (exemples en C) v1.11 - Olivier Carles 1 Pile et Tas Mémoire allouée de manière statique Mémoire Allouée Dynamiquement variables locales

Plus en détail

Temps Réel. Jérôme Pouiller <j.pouiller@sysmic.org> Septembre 2011

Temps Réel. Jérôme Pouiller <j.pouiller@sysmic.org> Septembre 2011 Temps Réel Jérôme Pouiller Septembre 2011 Sommaire Problèmatique Le monotâche Le multitâches L ordonnanement Le partage de ressources Problèmatiques des OS temps réels J. Pouiller

Plus en détail

Cours d initiation à la programmation en C++ Johann Cuenin

Cours d initiation à la programmation en C++ Johann Cuenin Cours d initiation à la programmation en C++ Johann Cuenin 11 octobre 2014 2 Table des matières 1 Introduction 5 2 Bases de la programmation en C++ 7 3 Les types composés 9 3.1 Les tableaux.............................

Plus en détail

1 Architecture du cœur ARM Cortex M3. Le cœur ARM Cortex M3 sera présenté en classe à partir des éléments suivants :

1 Architecture du cœur ARM Cortex M3. Le cœur ARM Cortex M3 sera présenté en classe à partir des éléments suivants : GIF-3002 SMI et Architecture du microprocesseur Ce cours discute de l impact du design du microprocesseur sur le système entier. Il présente d abord l architecture du cœur ARM Cortex M3. Ensuite, le cours

Plus en détail

Partie 7 : Gestion de la mémoire

Partie 7 : Gestion de la mémoire INF3600+INF2610 Automne 2006 Partie 7 : Gestion de la mémoire Exercice 1 : Considérez un système disposant de 16 MO de mémoire physique réservée aux processus utilisateur. La mémoire est composée de cases

Plus en détail

Premiers Pas en Programmation Objet : les Classes et les Objets

Premiers Pas en Programmation Objet : les Classes et les Objets Chapitre 2 Premiers Pas en Programmation Objet : les Classes et les Objets Dans la première partie de ce cours, nous avons appris à manipuler des objets de type simple : entiers, doubles, caractères, booléens.

Plus en détail

Info0804. Cours 6. Optimisation combinatoire : Applications et compléments

Info0804. Cours 6. Optimisation combinatoire : Applications et compléments Recherche Opérationnelle Optimisation combinatoire : Applications et compléments Pierre Delisle Université de Reims Champagne-Ardenne Département de Mathématiques et Informatique 17 février 2014 Plan de

Plus en détail

Architecture des ordinateurs

Architecture des ordinateurs Architecture des ordinateurs Cours 4 5 novembre 2012 Archi 1/22 Micro-architecture Archi 2/22 Intro Comment assembler les différents circuits vus dans les cours précédents pour fabriquer un processeur?

Plus en détail

IN 102 - Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C

IN 102 - Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C IN 102 - Cours 1 Qu on le veuille ou non, les systèmes informatisés sont désormais omniprésents. Même si ne vous destinez pas à l informatique, vous avez de très grandes chances d y être confrontés en

Plus en détail

C++ Programmer. en langage. 8 e édition. Avec une intro aux design patterns et une annexe sur la norme C++11. Claude Delannoy

C++ Programmer. en langage. 8 e édition. Avec une intro aux design patterns et une annexe sur la norme C++11. Claude Delannoy Claude Delannoy Programmer en langage C++ 8 e édition Avec une intro aux design patterns et une annexe sur la norme C++11 Groupe Eyrolles, 1993-2011. Groupe Eyrolles, 2014, pour la nouvelle présentation,

Plus en détail

Le langage C. Séance n 4

Le langage C. Séance n 4 Université Paris-Sud 11 Institut de Formation des Ingénieurs Remise à niveau INFORMATIQUE Année 2007-2008 Travaux pratiques d informatique Le langage C Séance n 4 But : Vous devez maîtriser à la fin de

Plus en détail

VIII- Circuits séquentiels. Mémoires

VIII- Circuits séquentiels. Mémoires 1 VIII- Circuits séquentiels. Mémoires Maintenant le temps va intervenir. Nous avions déjà indiqué que la traversée d une porte ne se faisait pas instantanément et qu il fallait en tenir compte, notamment

Plus en détail

Nom de l application

Nom de l application Ministère de l Enseignement Supérieur et de la Recherche Scientifique Direction Générale des Etudes Technologiques Institut Supérieur des Etudes Technologiques de Gafsa Département Technologies de l Informatique

Plus en détail

Leçon 1 : Les principaux composants d un ordinateur

Leçon 1 : Les principaux composants d un ordinateur Chapitre 2 Architecture d un ordinateur Leçon 1 : Les principaux composants d un ordinateur Les objectifs : o Identifier les principaux composants d un micro-ordinateur. o Connaître les caractéristiques

Plus en détail

REALISATION d'un. ORDONNANCEUR à ECHEANCES

REALISATION d'un. ORDONNANCEUR à ECHEANCES REALISATION d'un ORDONNANCEUR à ECHEANCES I- PRÉSENTATION... 3 II. DESCRIPTION DU NOYAU ORIGINEL... 4 II.1- ARCHITECTURE... 4 II.2 - SERVICES... 4 III. IMPLÉMENTATION DE L'ORDONNANCEUR À ÉCHÉANCES... 6

Plus en détail

Programmation parallèle et distribuée

Programmation parallèle et distribuée ppd/mpassing p. 1/43 Programmation parallèle et distribuée Communications par messages Philippe MARQUET Philippe.Marquet@lifl.fr Laboratoire d informatique fondamentale de Lille Université des sciences

Plus en détail

Exécution des instructions machine

Exécution des instructions machine Exécution des instructions machine Eduardo Sanchez EPFL Exemple: le processeur MIPS add a, b, c a = b + c type d'opération (mnémonique) destination du résultat lw a, addr opérandes sources a = mem[addr]

Plus en détail

Gestion de mémoire secondaire F. Boyer, Laboratoire Sardes Fabienne.Boyer@imag.fr

Gestion de mémoire secondaire F. Boyer, Laboratoire Sardes Fabienne.Boyer@imag.fr Gestion de mémoire secondaire F. Boyer, Laboratoire Sardes Fabienne.Boyer@imag.fr 1- Structure d un disque 2- Ordonnancement des requêtes 3- Gestion du disque - formatage - bloc d amorçage - récupération

Plus en détail

Programmation C. Apprendre à développer des programmes simples dans le langage C

Programmation C. Apprendre à développer des programmes simples dans le langage C Programmation C Apprendre à développer des programmes simples dans le langage C Notes de cours sont disponibles sur http://astro.u-strasbg.fr/scyon/stusm (attention les majuscules sont importantes) Modalités

Plus en détail

Conventions d écriture et outils de mise au point

Conventions d écriture et outils de mise au point Logiciel de base Première année par alternance Responsable : Christophe Rippert Christophe.Rippert@Grenoble-INP.fr Introduction Conventions d écriture et outils de mise au point On va utiliser dans cette

Plus en détail

Cours Programmation Système

Cours Programmation Système Cours Programmation Système Filière SMI Semestre S6 El Mostafa DAOUDI Département de Mathématiques et d Informatique, Faculté des Sciences Université Mohammed Premier Oujda m.daoudi@fso.ump.ma Février

Plus en détail

Les structures de données. Rajae El Ouazzani

Les structures de données. Rajae El Ouazzani Les structures de données Rajae El Ouazzani Les arbres 2 1- Définition de l arborescence Une arborescence est une collection de nœuds reliés entre eux par des arcs. La collection peut être vide, cad l

Plus en détail

DE L ALGORITHME AU PROGRAMME INTRO AU LANGAGE C 51

DE L ALGORITHME AU PROGRAMME INTRO AU LANGAGE C 51 DE L ALGORITHME AU PROGRAMME INTRO AU LANGAGE C 51 PLAN DU COURS Introduction au langage C Notions de compilation Variables, types, constantes, tableaux, opérateurs Entrées sorties de base Structures de

Plus en détail

Exécutif temps réel Pierre-Yves Duval (cppm)

Exécutif temps réel Pierre-Yves Duval (cppm) Exécutif temps réel Pierre-Yves Duval (cppm) Ecole d informatique temps réel - La Londes les Maures 7-11 Octobre 2002 Plan Exécutif Tâches Evénements et synchronisation Partage de ressources Communications

Plus en détail

High Performance by Exploiting Information Locality through Reverse Computing. Mouad Bahi

High Performance by Exploiting Information Locality through Reverse Computing. Mouad Bahi Thèse High Performance by Exploiting Information Locality through Reverse Computing Présentée et soutenue publiquement le 21 décembre 2011 par Mouad Bahi pour l obtention du Doctorat de l université Paris-Sud

Plus en détail

INTRODUCTION A JAVA. Fichier en langage machine Exécutable

INTRODUCTION A JAVA. Fichier en langage machine Exécutable INTRODUCTION A JAVA JAVA est un langage orienté-objet pur. Il ressemble beaucoup à C++ au niveau de la syntaxe. En revanche, ces deux langages sont très différents dans leur structure (organisation du

Plus en détail

Bien architecturer une application REST

Bien architecturer une application REST Olivier Gutknecht Bien architecturer une application REST Avec la contribution de Jean Zundel Ce livre traite exactement du sujet suivant : comment faire pour que les services web et les programmes qui

Plus en détail

RÉALISATION D UN SITE DE RENCONTRE

RÉALISATION D UN SITE DE RENCONTRE RÉALISATION D UN SITE DE RENCONTRE Par Mathieu COUPE, Charlène DOUDOU et Stéphanie RANDRIANARIMANA Sous la coordination des professeurs d ISN du lycée Aristide Briand : Jérôme CANTALOUBE, Laurent BERNARD

Plus en détail

Solution A La Gestion Des Objets Java Pour Des Systèmes Embarqués

Solution A La Gestion Des Objets Java Pour Des Systèmes Embarqués International Journal of Engineering Research and Development e-issn: 2278-067X, p-issn: 2278-800X, www.ijerd.com Volume 7, Issue 5 (June 2013), PP.99-103 Solution A La Gestion Des Objets Java Pour Des

Plus en détail

Rapport de stage Master 2

Rapport de stage Master 2 Rapport de stage Master 2 Informatique Haute Performance et Simulation, 2 ème année Ecole Centrale Paris Accélération des méthodes statistiques sur GPU Auteur : CHAI Anchen. Responsables: Joel Falcou et

Plus en détail

Résolution de systèmes linéaires par des méthodes directes

Résolution de systèmes linéaires par des méthodes directes Résolution de systèmes linéaires par des méthodes directes J. Erhel Janvier 2014 1 Inverse d une matrice carrée et systèmes linéaires Ce paragraphe a pour objet les matrices carrées et les systèmes linéaires.

Plus en détail

Travaux pratiques. Compression en codage de Huffman. 1.3. Organisation d un projet de programmation

Travaux pratiques. Compression en codage de Huffman. 1.3. Organisation d un projet de programmation Université de Savoie Module ETRS711 Travaux pratiques Compression en codage de Huffman 1. Organisation du projet 1.1. Objectifs Le but de ce projet est d'écrire un programme permettant de compresser des

Plus en détail

Bases de programmation. Cours 5. Structurer les données

Bases de programmation. Cours 5. Structurer les données Bases de programmation. Cours 5. Structurer les données Pierre Boudes 1 er décembre 2014 This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License. Types char et

Plus en détail

Potentiels de la technologie FPGA dans la conception des systèmes. Avantages des FPGAs pour la conception de systèmes optimisés

Potentiels de la technologie FPGA dans la conception des systèmes. Avantages des FPGAs pour la conception de systèmes optimisés Potentiels de la technologie FPGA dans la conception des systèmes Avantages des FPGAs pour la conception de systèmes optimisés Gérard FLORENCE Lotfi Guedria Agenda 1. Le CETIC en quelques mots 2. Générateur

Plus en détail

Table des matières PRESENTATION DU LANGAGE DS2 ET DE SES APPLICATIONS. Introduction

Table des matières PRESENTATION DU LANGAGE DS2 ET DE SES APPLICATIONS. Introduction PRESENTATION DU LANGAGE DS2 ET DE SES APPLICATIONS Depuis SAS 9.2 TS2M3, SAS propose un nouveau langage de programmation permettant de créer et gérer des tables SAS : le DS2 («Data Step 2»). Ces nouveautés

Plus en détail

INTERSYSTEMS CACHÉ COMME ALTERNATIVE AUX BASES DE DONNÉES RÉSIDENTES EN MÉMOIRE

INTERSYSTEMS CACHÉ COMME ALTERNATIVE AUX BASES DE DONNÉES RÉSIDENTES EN MÉMOIRE I N T E RS Y S T E M S INTERSYSTEMS CACHÉ COMME ALTERNATIVE AUX BASES DE DONNÉES RÉSIDENTES EN MÉMOIRE David Kaaret InterSystems Corporation INTERSySTEMS CAChé CoMME ALTERNATIvE AUx BASES de données RéSIdENTES

Plus en détail

Informatique Générale

Informatique Générale Informatique Générale Guillaume Hutzler Laboratoire IBISC (Informatique Biologie Intégrative et Systèmes Complexes) guillaume.hutzler@ibisc.univ-evry.fr Cours Dokeos 625 http://www.ens.univ-evry.fr/modx/dokeos.html

Plus en détail

IV- Comment fonctionne un ordinateur?

IV- Comment fonctionne un ordinateur? 1 IV- Comment fonctionne un ordinateur? L ordinateur est une alliance du hardware (le matériel) et du software (les logiciels). Jusqu à présent, nous avons surtout vu l aspect «matériel», avec les interactions

Plus en détail

Plan du cours 2014-2015. Cours théoriques. 29 septembre 2014

Plan du cours 2014-2015. Cours théoriques. 29 septembre 2014 numériques et Institut d Astrophysique et de Géophysique (Bât. B5c) Bureau 0/13 email:.@ulg.ac.be Tél.: 04-3669771 29 septembre 2014 Plan du cours 2014-2015 Cours théoriques 16-09-2014 numériques pour

Plus en détail

Matériel & Logiciels (Hardware & Software)

Matériel & Logiciels (Hardware & Software) CHAPITRE 2 HARDWARE & SOFTWARE P. 1 Chapitre 2 Matériel & Logiciels (Hardware & Software) 2.1 Matériel (Hardware) 2.1.1 Présentation de l'ordinateur Un ordinateur est un ensemble de circuits électronique

Plus en détail

as Architecture des Systèmes d Information

as Architecture des Systèmes d Information Plan Plan Programmation - Introduction - Nicolas Malandain March 14, 2005 Introduction à Java 1 Introduction Présentation Caractéristiques Le langage Java 2 Types et Variables Types simples Types complexes

Plus en détail

Programmer en JAVA. par Tama (tama@via.ecp.fr( tama@via.ecp.fr)

Programmer en JAVA. par Tama (tama@via.ecp.fr( tama@via.ecp.fr) Programmer en JAVA par Tama (tama@via.ecp.fr( tama@via.ecp.fr) Plan 1. Présentation de Java 2. Les bases du langage 3. Concepts avancés 4. Documentation 5. Index des mots-clés 6. Les erreurs fréquentes

Plus en détail

Vers du matériel libre

Vers du matériel libre Février 2011 La liberté du logiciel n est qu une partie du problème. Winmodems Modem traditionnel Bon fonctionnement Plus cher Electronique propriétaire Blob sur DSP intégré au modem Bien reçu par les

Plus en détail

Fiche technique RDS 2012

Fiche technique RDS 2012 Le 20/11/2013 OBJECTIF VIRTUALISATION mathieuc@exakis.com EXAKIS NANTES Identification du document Titre Projet Date de création Date de modification Fiche technique RDS Objectif 02/04/2013 20/11/2013

Plus en détail

IRL : Simulation distribuée pour les systèmes embarqués

IRL : Simulation distribuée pour les systèmes embarqués IRL : Simulation distribuée pour les systèmes embarqués Yassine El Khadiri, 2 ème année Ensimag, Grenoble INP Matthieu Moy, Verimag Denis Becker, Verimag 19 mai 2015 1 Table des matières 1 MPI et la sérialisation

Plus en détail

Chapitre 1 : La gestion dynamique de la mémoire

Chapitre 1 : La gestion dynamique de la mémoire Chapitre 1 : La gestion dynamique de la mémoire En langage C un programme comporte trois types de données : Statiques; Automatiques ; Dynamiques. Les données statiques occupent un emplacement parfaitement

Plus en détail

Cours d Algorithmique et de Langage C 2005 - v 3.0

Cours d Algorithmique et de Langage C 2005 - v 3.0 Cours d Algorithmique et de Langage C 2005 - v 3.0 Bob CORDEAU cordeau@onera.fr Mesures Physiques IUT d Orsay 15 mai 2006 Avant-propos Avant-propos Ce cours en libre accès repose sur trois partis pris

Plus en détail

Rapport 2014 et demande pour 2015. Portage de Méso-NH sur Machines Massivement Parallèles du GENCI Projet 2015 : GENCI GEN1605 & CALMIP-P0121

Rapport 2014 et demande pour 2015. Portage de Méso-NH sur Machines Massivement Parallèles du GENCI Projet 2015 : GENCI GEN1605 & CALMIP-P0121 Rapport 2014 et demande pour 2015 Portage de Méso-NH sur Machines Massivement Parallèles du GENCI Projet 2015 : GENCI GEN1605 & CALMIP-P0121 Rappel sur Méso-NH : Modélisation à moyenne échelle de l atmosphère

Plus en détail

Une dérivation du paradigme de réécriture de multiensembles pour l'architecture de processeur graphique GPU

Une dérivation du paradigme de réécriture de multiensembles pour l'architecture de processeur graphique GPU Une dérivation du paradigme de réécriture de multiensembles pour l'architecture de processeur graphique GPU Gabriel Antoine Louis Paillard Ce travail a eu le soutien de la CAPES, agence brésilienne pour

Plus en détail

Introduction à la programmation concurrente

Introduction à la programmation concurrente Introduction à la programmation concurrente Moniteurs Yann Thoma Reconfigurable and Embedded Digital Systems Institute Haute Ecole d Ingénierie et de Gestion du Canton de Vaud This work is licensed under

Plus en détail

<Insert Picture Here> Solaris pour la base de donnés Oracle

<Insert Picture Here> Solaris pour la base de donnés Oracle Solaris pour la base de donnés Oracle Alain Chéreau Oracle Solution Center Agenda Compilateurs Mémoire pour la SGA Parallélisme RAC Flash Cache Compilateurs

Plus en détail

J2SE Threads, 1ère partie Principe Cycle de vie Création Synchronisation

J2SE Threads, 1ère partie Principe Cycle de vie Création Synchronisation J2SE Threads, 1ère partie Principe Cycle de vie Création Synchronisation Cycle Ingénierie 2e année SIGL Dernière mise à jour : 19/10/2006 Christophe Porteneuve Threads Principes Cycle de vie Création java.lang.thread

Plus en détail

Manipulation 4 : Application de «Change».

Manipulation 4 : Application de «Change». Manipulation 4 : Application de «Change». Première partie : Cette manipulation a pour but d utiliser un service Web afin d obtenir les taux de change appliqués entre les différentes monnaies référencées

Plus en détail

Conception des systèmes répartis

Conception des systèmes répartis Conception des systèmes répartis Principes et concepts Gérard Padiou Département Informatique et Mathématiques appliquées ENSEEIHT Octobre 2012 Gérard Padiou Conception des systèmes répartis 1 / 37 plan

Plus en détail

Éléments d informatique Cours 3 La programmation structurée en langage C L instruction de contrôle if

Éléments d informatique Cours 3 La programmation structurée en langage C L instruction de contrôle if Éléments d informatique Cours 3 La programmation structurée en langage C L instruction de contrôle if Pierre Boudes 28 septembre 2011 This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike

Plus en détail

INTRODUCTION À LA PROGRAMMATION CONCURRENTE

INTRODUCTION À LA PROGRAMMATION CONCURRENTE INTRODUCTION À LA PROGRAMMATION CONCURRENTE POLYCOPIÉ DU COURS PCO1 Prof. Claude Evéquoz Prof. Yann Thoma HEIG-VD 2009 Table des matières Table des matières 2 1 Introduction à la programmation concurrente

Plus en détail

Introduction à Java. Matthieu Herrb CNRS-LAAS. Mars 2014. http://homepages.laas.fr/matthieu/cours/java/java.pdf

Introduction à Java. Matthieu Herrb CNRS-LAAS. Mars 2014. http://homepages.laas.fr/matthieu/cours/java/java.pdf Introduction à Java Matthieu Herrb CNRS-LAAS http://homepages.laas.fr/matthieu/cours/java/java.pdf Mars 2014 Plan 1 Concepts 2 Éléments du langage 3 Classes et objets 4 Packages 2/28 Histoire et motivations

Plus en détail

Notice d Utilisation du logiciel Finite Element Method Magnetics version 3.4 auteur: David Meeker

Notice d Utilisation du logiciel Finite Element Method Magnetics version 3.4 auteur: David Meeker Notice d Utilisation du logiciel Finite Element Method Magnetics version 3.4 auteur: David Meeker DeCarvalho Adelino adelino.decarvalho@iutc.u-cergy.fr septembre 2005 Table des matières 1 Introduction

Plus en détail

Argument-fetching dataflow machine de G.R. Gao et J.B. Dennis (McGill, 1988) = machine dataflow sans flux de données

Argument-fetching dataflow machine de G.R. Gao et J.B. Dennis (McGill, 1988) = machine dataflow sans flux de données EARTH et Threaded-C: Éléments clés du manuel de références de Threaded-C Bref historique de EARTH et Threaded-C Ancêtres de l architecture EARTH: Slide 1 Machine à flux de données statique de J.B. Dennis

Plus en détail

Algorithmique et Programmation, IMA

Algorithmique et Programmation, IMA Algorithmique et Programmation, IMA Cours 2 : C Premier Niveau / Algorithmique Université Lille 1 - Polytech Lille Notations, identificateurs Variables et Types de base Expressions Constantes Instructions

Plus en détail

Défi Cloud Computing

Défi Cloud Computing EQUIPE RICM 2010 Défi Cloud Computing Dossier de remarques Ricom c est l @base 04/12/2009 Sommaire Introduction... 3 Les applications et la plateforme Cloud Computing... 4 Cloud Computing - RICM-2010 Page

Plus en détail

Hiérarchie matériel dans le monde informatique. Architecture d ordinateur : introduction. Hiérarchie matériel dans le monde informatique

Hiérarchie matériel dans le monde informatique. Architecture d ordinateur : introduction. Hiérarchie matériel dans le monde informatique Architecture d ordinateur : introduction Dimitri Galayko Introduction à l informatique, cours 1 partie 2 Septembre 2014 Association d interrupteurs: fonctions arithmétiques élémentaires Elément «NON» Elément

Plus en détail

Cours de Systèmes d Exploitation

Cours de Systèmes d Exploitation Licence d informatique Synchronisation et Communication inter-processus Hafid Bourzoufi Université de Valenciennes - ISTV Introduction Les processus concurrents s exécutant dans le système d exploitation

Plus en détail

UE C avancé cours 1: introduction et révisions

UE C avancé cours 1: introduction et révisions Introduction Types Structures de contrôle Exemple UE C avancé cours 1: introduction et révisions Jean-Lou Desbarbieux et Stéphane Doncieux UMPC 2004/2005 Introduction Types Structures de contrôle Exemple

Plus en détail

Introduction au langage C

Introduction au langage C Introduction au langage C Cours 1: Opérations de base et premier programme Alexis Lechervy Alexis Lechervy (UNICAEN) Introduction au langage C 1 / 23 Les premiers pas Sommaire 1 Les premiers pas 2 Les

Plus en détail

Sujet proposé par Yves M. LEROY. Cet examen se compose d un exercice et de deux problèmes. Ces trois parties sont indépendantes.

Sujet proposé par Yves M. LEROY. Cet examen se compose d un exercice et de deux problèmes. Ces trois parties sont indépendantes. Promotion X 004 COURS D ANALYSE DES STRUCTURES MÉCANIQUES PAR LA MÉTHODE DES ELEMENTS FINIS (MEC 568) contrôle non classant (7 mars 007, heures) Documents autorisés : polycopié ; documents et notes de

Plus en détail

Sanity Check. bgcolor mgcolor fgcolor

Sanity Check. bgcolor mgcolor fgcolor Sanity Check bgcolor mgcolor fgcolor 0 1 2 3 4 5 6 7 8 9 10 Compilation pour cibles hétérogènes: automatisation des analyses, transformations et décisions nécessaires, François Irigoin et Ronan Keryell

Plus en détail

GPA770 Microélectronique appliquée Exercices série A

GPA770 Microélectronique appliquée Exercices série A GPA770 Microélectronique appliquée Exercices série A 1. Effectuez les calculs suivants sur des nombres binaires en complément à avec une représentation de 8 bits. Est-ce qu il y a débordement en complément

Plus en détail

ACTIVITÉ DE PROGRAMMATION

ACTIVITÉ DE PROGRAMMATION ACTIVITÉ DE PROGRAMMATION The purpose of the Implementation Process is to realize a specified system element. ISO/IEC 12207 Sébastien Adam Une introduction 2 Introduction Ø Contenu Utilité de l ordinateur,

Plus en détail

Centre CPGE TSI - Safi 2010/2011. Algorithmique et programmation :

Centre CPGE TSI - Safi 2010/2011. Algorithmique et programmation : Algorithmique et programmation : STRUCTURES DE DONNÉES A. Structure et enregistrement 1) Définition et rôle des structures de données en programmation 1.1) Définition : En informatique, une structure de

Plus en détail

I00 Éléments d architecture

I00 Éléments d architecture I00 I Exemples d ordinateur Pour les informaticiens, différentes machines de la vie courante sont des ordinateurs : par exemple les ordinateurs portables, les ordinateurs fixes, mais aussi les supercalculateurs,

Plus en détail

Le Collège de France crée une chaire pérenne d Informatique, Algorithmes, machines et langages, et nomme le Pr Gérard BERRY titulaire

Le Collège de France crée une chaire pérenne d Informatique, Algorithmes, machines et langages, et nomme le Pr Gérard BERRY titulaire Communiquédepresse Mars2013 LeCollègedeFrancecréeunechairepérenned Informatique, Algorithmes,machinesetlangages, etnommeleprgérardberrytitulaire Leçoninauguralele28mars2013 2009avait marquéunpas importantdans

Plus en détail

6 - Le système de gestion de fichiers F. Boyer, UJF-Laboratoire Lig, Fabienne.Boyer@imag.fr

6 - Le système de gestion de fichiers F. Boyer, UJF-Laboratoire Lig, Fabienne.Boyer@imag.fr 6 - Le système de gestion de fichiers F. Boyer, UJF-Laboratoire Lig, Fabienne.Boyer@imag.fr Interface d un SGF Implémentation d un SGF Gestion de la correspondance entre la structure logique et la structure

Plus en détail

Systèmes et traitement parallèles

Systèmes et traitement parallèles Systèmes et traitement parallèles Mohsine Eleuldj Département Génie Informatique, EMI eleuldj@emi.ac.ma 1 Système et traitement parallèle Objectif Etude des architectures parallèles Programmation des applications

Plus en détail

Génération de code binaire pour application multimedia : une approche au vol

Génération de code binaire pour application multimedia : une approche au vol Génération de binaire pour application multimedia : une approche au vol http://hpbcg.org/ Henri-Pierre Charles Université de Versailles Saint-Quentin en Yvelines 3 Octobre 2009 Présentation Présentation

Plus en détail

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère L'héritage et le polymorphisme en Java Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère En java, toutes les classes sont dérivée de la

Plus en détail