Utilisation de GPU pour la mise au point de récepteurs solaires
Transcription
Utilisation de GPU pour la mise au point de récepteurs solaires
Utilisation de GPU pour la mise au point de récepteurs solaires Sylvain Collange Stage de Master deuxième année encadré par Marc Daumas et David Defour Équipe DALI, Laboratoire ELIAUS, Université de Perpignan Via Domitia Février-juin 2007 École normale supérieure de Lyon 46 allée d’Italie 69364 Lyon Cedex 07 - France Introduction Conçus à l’origine pour accélérer les calculs géométriques et graphiques liés au rendu d’images de synthèse, les processeurs graphiques (graphics processing units, GPU) permettent aujourd’hui une souplesse d’utilisation suffisante pour envisager leur utilisation pour d’autres tâches de calcul intensif. Nous allons nous intéresser ici aux problématiques soulevées par le portage d’une application de modélisation physique sur GPU. 1 Présentation des processeurs graphiques F IG . 1 – L’ASUS EN7800GTX TOP, une des cartes graphiques que nous utilisons. Un processeur graphique, ou GPU, est un coprocesseur spécialisé dans le traitement graphique utilisé dans des ordinateurs personnels. Les processeurs graphiques répondent principalement à une demande en calcul pour produire des images de synthèse en temps réel pour les jeux vidéo. S’étant généralisés à partir de la fin des années 1990, les premiers GPU commercialisés à grande échelle étaient basés sur un pipeline fixe correspondant à un certain modèle de traitement géométrique. Dans ce modèle, les fonctionnalités désirées pouvaient uniquement être activées ou désactivées à l’aide de drapeaux de contrôle et paramétrés par des constantes. La demande pour des effets de rendu graphique plus variés à conduit les constructeurs de GPU à rendre certaines unités de traitement programmables plutôt que simplement paramétrables, à partir de la version 8 de la bibliothèque Microsoft DirectX en 2001. Cela a permis d’envisager leur utilisation pour d’autres tâches que des applications purement graphiques. L’importance du marché du jeu vidéo permet des volumes de production — et par là même des économies d’échelles — qui conduisent à un coût unitaire des GPU relativement faible, de l’ordre de celui d’un processeur généraliste. Ce prix est sans comparaison avec celui d’un coprocesseur spécialisé pour des applications de calcul scientifique tel que le ClearSpeed Advance1 , nécessairement produit en faible volume. 1 http://www.clearspeed.com 2 La demande des utilisateurs conduit également à des cycles de développement de produits très courts, avec une nouvelle génération de GPU tous les six mois à un an. De même, la production en volume permet aux GPU de bénéficier des dernières technologies de gravure disponibles, en même temps que les processeurs. En contrepartie de ces avantages, les applications scientifiques à porter sur GPU devront exhiber un parallélisme de données suffisant, et surtout pouvoir se projeter de manière efficace sur la structure du GPU, prévue pour les applications graphiques. Nous disposons de deux cartes graphiques pour le développement : elles sont équipées respectivement d’un GeForce 7800 GTX de nVidia et d’un Radeon X1800 XL d’ATI, ainsi que de 256 Mo de mémoire chacune. 1.1 Architecture des GPU 1.1.1 Spécificités des GPU Le GPU est généralement implanté sur une carte graphique qui contient également de la mémoire dédiée. Cette carte graphique communique avec le reste de la machine par un lien dédié, actuellement de type PCI-Express (peripheral component interconnect – Express). La figure 2 présente l’architecture générale d’une station de travail haut de gamme typique de 2007, avec les débits crêtes des différents bus et canaux de communication. Une première chose que l’on remarque sur ce diagramme est que la bande passante Carte mère CPU Intel Core 2 Duo Mémoire centrale Carte graphique 8,5 Go/s Écran GPU nVidia GeForce 8800 GTX Northbridge 4 Go/s Intel P965 86,4 Go/s Mémoire graphique GDDR4 12,8 Go/s 2 Go/s 2×DDR2-800 Southbridge Intel ICH 8 Entrées/sorties F IG . 2 – Diagramme d’un exemple de carte-mère équipée d’un GPU entre le GPU et sa mémoire dédiée (86,4 Go/s dans cet exemple) est largement supérieure à celle des autres liens de communications. Elle est en particulier supérieure d’un facteur 20 à celle du lien PCI-Express à 4 Go/s reliant la carte graphique au reste du système, et d’un facteur 10 à celle du lien PSB (processor system bus) à 8,5 Go/s vers le processeur central. Même programmables, les unités d’exécution des GPU restent très différentes de celles d’un processeur généraliste. En effet, un GPU effectue des tâches à très fort parallélisme de données : typiquement des transformations géométriques identiques sur des sommets traités indépendamment ou des calculs d’éclairement sur des pixels éga- 3 lement indépendants. Ces spécificités conduisent les concepteurs de GPU à chercher à optimiser le débit des unités de calcul et d’accès mémoire, plutôt que leur latence. D’une part, alors qu’un processeur généraliste utilisera les transistors disponibles principalement pour le contrôle et les caches, un GPU privilégiera les unités de calcul virgule flottante, le contrôle étant simplifié par la présence de parallélisme de données. On peut ainsi disposer de nombreuses unités de calcul en parallèle. Un GPU est donc parallèle en largeur. D’autre part, en continuité avec l’architecture historique d’un long pipeline graphique unique, les unités de calcul distinctes (multiplieur, additionneur, accès mémoire. . .) sont souvent organisées en pipeline plutôt que parallèlement (fig. 3). Un GPU est donc également parallèle en profondeur. Instructions Décodage Mul Décodage d’instructions Adresses (numéros de registres) Banc de registres numéros de registres Données Add Add Mul Load/ Store Banc de registres Texture (Load) Unités d’exécution (a) Organisation horizontale (superscalaire, VLIW. . .). (b) Organisation verticale (GPU, DSP. . .). F IG . 3 – Comparaison entre les organisations respectives des unités de calcul des processeurs généralistes et des GPU. De façon à alimenter suffisamment rapidement les unités de calcul, le contrôle de la mémoire est également conçu de manière à maximiser le débit au détriment de la latence. Tout comme les unités de calcul, les unités mémoire disposent de larges bus de données (384 à 512 bits sur les modèles hauts de gamme actuels). Elles maintiennent également en vol un grand nombre d’instructions d’accès mémoire, qui seront éventuellement réordonnées pour optimiser le débit mémoire. Les technologies mémoire utilisées (GDDR3, GDDR4) sont également conçues pour fournir des débits élevés avec une architecture profondément pipelinée. En revanche, un GPU ayant à traiter quasi-exclusivement des données séquentielles, l’utilisation de caches reste très limitée, avec des caches de taille réduite à faible associativé. Ces paramètres expliquent que la puissance brute théorique disponible dans un GPU soit très supérieure à celle d’un processeur généraliste. 1.1.2 Le pipeline graphique Le chemin de données dans un GPU est conçu pour des données graphiques. Ce chemin, appelé pipeline graphique, est représenté figure 4. Les données en entrée sont des listes de sommets décrivant des primitives, soit des polygones, segments ou points. Ces données sont en général transférées depuis la mé4 Vertex shader Rasterizer Pixel shader ROP Sommets Sommets Fragments Fragments Pixels Coordonnées (x, y, z, w) Vecteur normal Coordonnées de texture Couleur Coordonnées (x, y, z) Coordonnées de texture Couleur Coordonnée (z) Coordonnées de texture Couleur Coordonnée (z) Couleur Couleur Tampon de sommets Mémoire GPU Textures Tampon d’image F IG . 4 – Pipeline graphique simplifié moire centrale vers la mémoire graphique préalablement au rendu. À chaque sommet est associé plusieurs attributs, tels que ses coordonnés dans l’espace tri-dimensionnel de la scène, sa couleur, ses caractéristiques ou ses coordonnées de texture. Chacun de ces attributs est généralement représenté sous la forme d’un vecteur à quatre composantes contenant des nombres en virgule flottante simple précision (32 bits). La première étape du pipeline graphique consiste à traiter chaque sommet pour opérer des transformations sur ses attributs. Notamment, le sommet doit être projeté depuis l’espace tri-dimensionnel de la scène à représenter vers la surface bi-dimensionnelle de l’écran. Des calculs d’éclairements simples peuvent égalements être effectués pour modifier l’attribut couleur du sommet. Toutes ces transformations sont réalisées par une unité programmable, le vertex shader2 . Une fois le traitement géométrique sur les sommets effectués, le GPU discrétise les primitives en sélectionnant les pixels qui se trouvent à l’intérieur. Cela consiste en l’étape de rasterization. À ces pixels, aussi appelés fragments, sont associés les mêmes attributs qu’aux sommets transformés (couleur, coordonnées de texture). Ces attributs sont interpolés bilinéairement à partir des attributs des sommets de la primitive. Les fragments arrivent alors dans la seconde unité programmable, le pixel shader ou fragment shader. Cette unité permet d’effectuer des calculs sur chaque fragment, tels que de l’application de texture. Enfin, les unités ROP (raster operations) sont principalement chargées d’arbitrer les situations où plusieurs fragments se trouvent aux mêmes coordonnées et doivent former un unique pixel. Le test d’occlusion, ou Z-Culling, consiste à ne conserver que le fragment le plus proche de l’observateur. Cela est réalisé en comparant sa coordonnée Z avec la coordonnée minimale rencontrée jusque là, stockée dans le Z-buffer. Le blending consiste à fusionner la couleur du fragment à écrire avec la couleur du pixel dans le tampon d’image, selon une équation prédéfinie. La génération suivante, arrivée fin 2006 et 2007 et correspondant à la version 10 de DirectX, marque une rupture avec ce modèle en unifiant les vertex et pixel shaders et en ajoutant un nouveau type de shader, le geometry shader. Celui-ci se place entre le vertex shader et le pixel shader. Ce programme permet d’ajouter ou supprimer des sommets : prenant en entrée une primitive (triangle, ligne. . .), il fournit en sortie une autre primitive constitué d’un ensemble de sommets. 2 Vertex shader désigne traditionnellement le programme qui sera exécuté sur les sommets. Par abus de langage, on utilise également ce terme pour les unités matérielles qui exécuteront ce programme. 5 1.1.3 Les shaders Les unités principalement utilisées pour le GPGPU sont les deux unités programmables que sont les vertex shaders et pixel shaders. Ces deux processeurs sont relativement similaires. Ils comprennent chacun plusieurs cœurs d’exécution, dont le nombre varie suivant le modèle et le type de shader. Comme les GPU traitent typiquement plus de fragments que de sommets, l’équilibre entre les deux types de shader est équilibré en conséquence. Le nVidia GeForce 7800 GTX possède par exemple 8 cœurs de vertex shader pour 24 cœurs de pixel shaders. La figure 5 présente les shaders d’un des GPU que nous utilisons. Depuis Rasterization Registres constants Registres d’entre SP (?) MAD Texturage Depuis assemblage des primitives Registres constants Mini-ALU Norm Registres d’entre MAD Texturage Registres temporaires SP SP MAD Mini-ALU Registres de sortie Registres temporaires Vers Rasterization Registres de sortie x8 Vers ROPs (a) Vertex shaders. x24 (b) Pixel shaders. F IG . 5 – Architecture probable des unités de vertex et pixel shader du GPU nVidia GeForce 7800 GTX Chaque cœur d’exécution dispose de trois types d’unités de calcul. – Une ou plusieurs unités MAD réalisant des multiplications et additions sur des vecteurs de quatre flottants simple précision. – Une unité de texturage, permettant des accès mémoire avec calcul d’adresse à partir de coordonnées 2D ou 3D, conversion de type de donnée et filtrage (interpolation à partir de valeurs spatialement adjacentes). – Une unité scalaire d’évaluation de fonctions élémentaires, tels que la réciproque, racine carrée réciproque, exponentielle et logarithme en base 2 et fonctions trigonométriques (notée SP sur la figure 5). Contrairement aux processeurs généralistes sur lesquels ces fonctions sont typiquement calculées en logiciel ou microcodées, on a ici une unité entièrement matérielle, totalement pipelinée, pouvant délivrer un résultat par cycle d’horloge. Les unités dites mini-alu des pixel shaders existent principalement pour conserver 6 la compatibilité avec les premières versions des langages de shaders. Elles permettent d’effectuer des décalages par des constantes, utiles pour les recadrages lors des calculs en virgule fixe. Avec l’arrivée de la virgule flottante depuis le standard Shader Model 2.0, elles ont cependant perdu de leur utilité. On trouve également chez nVidia des unités effectuant des normalisations de vecteurs, mais uniquement en demi précision (16 bits). 1.1.4 Le parallélisme Pour nourrir ces unités de calcul, du parallélisme est exploité à quatre niveaux. – Les MAD et unités de texturage travaillent sur des vecteurs à quatre éléments : ce sont des unités SIMD (single-instruction multiple-data). – Les différentes unités d’exécution de chaque cœur peuvent fonctionner en parallèle, démarrant plusieurs instructions par cycle d’horloge. Les pixel shaders de certains GPU disposent également de deux MAD cascadés. Les MAD opérant sur des vecteurs de 4 éléments peuvent parfois êtres scindées pour effectuer deux instructions sur des vecteurs plus courts (co-issue ou dual-issue). Le GPU exploite donc également du parallélisme d’instruction (ILP, instruction-level parallelism). Cette forme de parallélisme est cependant minoritaire sur les GPU, contrairement aux processeurs généralistes pour lesquels elle représente la principale source de parallélisme. – Pour masquer la latence des unités de calcul, chaque cœur travaille simultanément sur un groupe de sommets (ou fragments). À chaque cycle d’horloge, le contexte d’exécution est changé et un autre élément est traité. Ainsi, lorsque le contrôle retournera à l’élément initial, le calcul sera terminé et la prochaine instruction pourra être exécutée immédiatement, quelle que soit leur relation de dépendance. Les latences mémoires lors d’accès à des textures peuvent être masquées de manière analogue. Cela représente du parallélisme en profondeur. – Chaque cœur d’exécution peut travailler indépendamment sur des sommets (fragments) différents, soit en exécutant les mêmes opérations au même moment, soit avec un contexte d’exécution séparé pour chaque cœur. On a donc une architecture multi-cœur effectuant du SIMD ou MIMD (multiple-instructions multipledata), suivant le type de shader et la génération. 1.2 Programmation des GPU 1.2.1 Programmation graphique Avant de contenir des unités programmables, les GPU étaient basés sur un pipeline fixe mais paramétrable, chaque unité le long du pipeline pouvant être activée ou non et éventuellement paramétrée. Le GPU, vu alors comme une machine à états, est configuré au moyen d’une bibliothèque graphique telle que OpenGL ou Microsoft DirectX faisant abstraction du matériel. Cette bibliothèque fait appel à des pilotes spécifiques au matériel pour envoyer les commandes au GPU. L’arrivée des unités programmables dans les GPU n’a pas changé cette manière de procéder ; les GPU sont encore largement basé sur un pipeline fixe. En revanche, des langages spécifiques sont nécessaires pour programmer les shaders s’exécutant sur le GPU. On trouve ainsi des langages bas niveau de type assembleur (VS/PS pour DirectX et ARBvp/ARBfp pour OpenGL), mais qui restent indépendants du matériel et ne correspondent pas forcément au langage machine utilisé par le GPU. À plus haut 7 niveau, il existe des langages inspirés du C : Cg de nVidia, HLSL de Microsoft et GLSL de l’ARB. 1.2.2 Programmation généraliste L’utilisation de GPU pour le calcul généraliste a donné lieu à de nouveaux environnements et langages ad-hoc. Certains de ces langages sont issus du monde académique, notamment une implémentation du langage Brook développé à Stanford3 . Les principaux constructeurs de GPU fournissent également chacun leur environnement de développement pour le GPGPU, mais avec des approches différentes : CUDA de nVidia tente de fournir une abstraction maximale par rapport au matériel, en offrant un langage très proche du C, le compilateur et l’environnement s’occupant des transferts mémoire et de l’ordonnancement. À l’inverse, CTM d’ATI donne plus de contrôle sur le matériel. Enfin, des outils commerciaux indépendants existent, tels que Peakstream (récemment racheté par Google) ou RapidMind [13]. Il s’agit de middlewares permettant d’exécuter un même code écrit dans un langage proche du C ou du C++ indifféremment sur GPU, processeur Cell ou processeur multi-cœur. 1.2.3 Pour notre application Bien que nous utilisions les GPU pour des calculs non graphiques, nous choisirons d’utiliser OpenGL et le langage assembleur associé. En effet, notre objectif n’est pas simplement de porter des applications de calcul scientifique sur GPU, mais d’identifier des caractéristiques des GPU utilisables pour ce type de calcul et développer des méthodes pour les exploiter. Ces méthodes pourront ensuite être automatisées pour la prochaine génération de langages et compilateurs GPGPU. La plate-forme de développement choisie doit donc donner un accès bas niveau aux fonctionnalités des GPU. Le choix d’OpenGL répond également à des exigences de portabilité par rapport aux constructeurs de GPU et aux systèmes d’exploitation. 1.2.4 Contraintes L’architecture des GPU présente un certain nombre de contraintes pour la programmation généraliste. S’agissant d’une architecture de flot de données, il n’est pas possible de lire et d’écrire à une même adresse dans le même fragment de code. Le contrôle exercé sur l’adresse de destination est faible, et sa mise en place complexe. La bande passante disponible entre le GPU et le reste du système étant plus faible d’un facteur 10 à 20 que celle de la mémoire graphique, il va être nécessaire autant que possible de conserver les données dans la mémoire graphique. Cela nécessite d’effectuer des compromis. Par exemple, certaines tâches à faible parallélisme peuvent être exécutées plus vite par le processeur central que le GPU ; cependant, migrer les données et les résultats vers la mémoire centrale présentera un surcoût à prendre en compte. La mémoire graphique elle-même dispose d’une latence élevée, contrepartie de son architecture profondément pipelinée. Le GPU comportant également peu de mémoire cache, les accès mémoire sont relativement coûteux, et leur latence doit pouvoir être masquée par des calculs exécutable en parallèle. Les shaders utilisent plusieurs types de registres : registres d’entrée, de destination, registres constants et registres temporaires. Ces derniers semblent à première vue 3 http://graphics.stanford.edu/projects/brookgpu 8 en nombre relativement important : les vertex shaders du nVidia GeForce 6800 en comptent par exemple 32 par cœur [15]. Cependant, ils sont partagés entre tous les threads s’exécutant sur le cœur considéré. Utiliser plus de registres dans un calcul permettra d’exploiter plus de parallélisme d’instruction, mais cela sera aux dépends du parallélisme de tâche entre les différents threads. Les branchements dynamiques sont également une opération complexe. De fait, c’est une fonctionnalité qui n’a été introduite que récemment dans les langages de shaders, avec le Shader Model 3.0 de la version 9.0c de Microsoft DirectX. Dans les pixel shaders en particulier, le même chemin d’exécution est suivi pour tout un bloc de pixels qui sera traité en parallèle. S’il y a divergence entre les branchement de deux pixels du bloc, tout le bloc devra exécuter les deux branches de manière prédiquée. Ainsi, l’efficacité des branchements dépend d’un critère de localité spatiale plutôt que de localité temporelle comme avec les algorithmes de prédiction de branchement des processeurs généralistes. 1.3 Déterminer les caractéristiques des GPU Un obstacle pratique au développement du GPGPU est l’absence de spécifications et de documentations publiques de la part des constructeurs de GPU. Pour obtenir des informations pouvant être publiées, il est donc nécessaire d’inférer le fonctionnement interne des GPU au moyen de tests spécifiques. Ainsi, l’outil GPUBench développé à Stanford[2] permet d’obtenir des informations sur l’architecture des GPU modernes, à travers des tests sur la hiérarchie mémoire, les unités d’exécution ou les transferts avec la mémoire centrale. De la même façon, les caractéristiques des opérateurs flottants n’étant pas documentées, des tests des unités flottantes seront nécessaires pour la conception de tout algorithme numérique. Les unités de calcul des GPU actuels travaillent sur des nombre flottants dont la représentation est équivalente à celle de la simple précision de la norme IEEE-754. L’environnement Direct3D 10 définit des règles que les GPU compatibles devront respecter. Il s’agit d’une version affaiblie d’IEEE-754, sans nombres dénormaux, une exigence d’arrondi fidèle uniquement pour l’addition et la multiplication, et d’arrondi à 2 ulp pour la division et la racine carrée [1]. Cependant, pour les GPU antérieurs à ces spécifications, aucune garantie n’est donnée sur les calculs, et en particulier les règles d’arrondi. De plus, les GPU postérieurs à la norme peuvent suivre des règles plus strictes que celles requises par Microsoft. Ainsi, nVidia fournit deux modes d’arrondi (au plus près et vers 0) et garantit l’arrondi correct des additions et multiplications depuis la génération GeForce 8000 [16]. On se retrouve donc dans une situation similaire à celle d’avant la généralisation de la norme IEEE-754 sur les processeurs généralistes. À cette époque, plusieurs jeux de tests ont été développés. Ces tests ont divers objectifs : ceux de Schryer[20] visent par exemple à détecter des bugs dans le matériel, tandis que les tests de Gentleman[9] permettent de récupérer des informations sur les caractéristiques des unités flottantes. Après la normalisation, des tests comme Paranoia de Kahan et Karpinski[12] vérifient la conformité d’une implantation avec la norme. Un sous-ensemble de Paranoia et des tests de Schryer a été adapté pour s’exécuter sur GPU [11]. Ces tests révèlent un certain nombre de caractéristiques des opérateurs de nos deux GPU. Cependant, ces tests sont directement adaptés du Paranoia de 1985, et ne prennent pas en compte les particularités des GPU, notamment la présence d’unités de calcul hétérogène. Par exemple, on trouve des multiplieurs situés à divers niveaux dans le pipeline graphique, et rien n’indique qu’il soient identiques. Les unités testées 9 ne sont pas indiquées, bien qu’on puisse supposer que le code est exécuté par les pixel shaders. D’autres tests ont été effectués dans l’équipe DALI avant mon arrivée [6]. Ces tests, exécutés par les pixel shaders, distinguent les deux additionneurs présents dans ces unités sur nos GPU. Il restait cependant à tester les opérateurs flottants des vertex shaders. C’est ce que j’ai réalisé au début de mon stage. Ces nouveaux tests représentent une extension de la suite GPUBench. Le code de GPUBench a cependant du être adapté pour utiliser des extensions OpenGL plus récentes et portables (utilisation de Framebuffer Object plutôt que des pbuffers). De plus, GPUBench n’incluant aucun test sur les vertex shaders, il m’a été nécessaire d’ajouter les routines de gestion correspondantes. Ce travail a fait l’objet d’un rapport de recherche dans lequel l’ensemble des tests et leurs résultats détaillés sont présentés [5]. Un résultat digne d’intérêt est la présence d’unités correctement arrondies dans les unités de vertex shader du GPU d’ATI. 2 2.1 Modélisation d’un récepteur solaire Modèle physique L’application étudiée au cours de ce stage est la modélisation du fonctionnement d’un récepteur solaire de type four solaire. Un tel système est utilisé sur le four solaire Thémis du projet PEGASE du laboratoire PROMES du CNRS [8]. Un projet de réhabilitation pour ce four est en cours et consiste à expérimenter un mode de fonctionnement innovant, par gaz pressurisé. Les modèles physiques couramment utilisés pour la simulation d’échanges thermiques (modélisation par bandes étroites) ne sont cependant pas adaptés à ces conditions de température et de pression. Cela nécessite de recourir au modèle raie-par-raie, plus fin mais nécessitant des ressources de calcul bien plus importantes [3]. Le modèle raie-par-raie étant basé sur des calculs massivement parallèles, il semble bien adapté à une accélération sur GPU. Dans un récepteur solaire à gaz pressurisé, le rayonnement solaire concentré par un jeu de miroirs est utilisé pour chauffer un tuyau métallique dans lequel circulent des gaz à effet de serre sous pression (principalement dioxyde de carbone et vapeur d’eau). Le tuyau chauffé émet du rayonnement infra-rouge qui va chauffer à son tour les gaz à effet de serre. Ces gaz entraînent alors une turbine produisant de l’électricité [7]. La partie de la modélisation que nous cherchons à porter sur GPU est l’interaction des rayons infra-rouges émis par le tuyau avec les gaz pressurisés. Ces interactions sont estimées par une méthode de Monte-Carlo consistant à lancer des rayons dans des directions aléatoires. Le chemin parcouru par ces rayons sera alors suivi dans un espace discrétisé (modélisation par éléments finis). Dans chaque volume élémentaire de gaz, on calcule l’énergie absorbée sur chaque longueur d’onde. En effet, les gaz ont des caractéristiques d’absorption très dépendantes de la longueur d’onde de la lumière reçue. Le spectre d’absorption d’un gaz, exprimant la fraction d’énergie absorbée par unité de distance en fonction de la longueur d’onde se présente comme un ensemble de raies étroites. Une partie du spectre du dioxyde de carbone à 1000 K et 800 kPa est représenté sur la figure 6. Plusieurs bases de données contenant les spectres d’absorption des molécules usuelles existent. HITRAN [19] fournit les spectres de 39 molécules, mais uniquement dans des conditions atmosphériques. Pour des températures plus élevées, on utilisera HITEMP [18] ou CDSD [21]. Nous utiliserons ici HITEMP. 10 6e−18 Absorption de H2O à 1000K Facteur d’absorption (cm−1) 5e−18 4e−18 3e−18 2e−18 1e−18 0 0 50 100 150 200 250 300 350 400 450 −1 Nombre d’onde (cm ) F IG . 6 – Spectre d’absorption du dioxyde de carbone. 2.1.1 Correction d’intensité et calcul de l’étalement La première étape du calcul consiste à calculer un facteur de correction de l’intensité pour chaque raie. Ce facteur est dépendant à la fois de la température et de paramètres liés à la raie considérée. Ce calcul doit donc être réalisé à chaque fois que la température change. Comme les raies sont ici indépendantes les unes des autres, on dispose d’un large parallélisme de donnée qui peut être exploité directement par le GPU. Les formules à évaluer sont tirées de [19] et sont reproduites ici avec les mêmes notations : c2 ν 0 c E − Tηη 1 − e − 2T η Q(Tref ) e · · Sηη0 (T ) = Sηη0 (Tref ) · (1) c2 ν 0 Q(T ) e− cT2 Erefη − T ηη ref 1−e où Sηη0 (Tref ) représente l’intensité de la raie à la température de référence Tref donné par HITRAN ou HITEMP, Sηη0 (T ) l’intensité corrigée à la température considérée T , νηη0 le nombre d’onde de la raie considérée. L’étape suivante consiste à calculer l’étalement de chaque raie, fonction de la pression. Plusieurs modèles physiques existent pour décrire le profil d’une raie, notamment le profil de Voigt et le profil de Lorentz, qui correspondent à différentes conditions de pression. Indépendamment du modèle utilisé, le profil consiste en une densité de probabilité, c’est à dire une fonction dont l’intégrale sur l’ensemble du domaine vaut 1. Un facteur d’étalement dépendant de la pression doit tout d’abord être calculé pour chaque raie. n Tref γ(T, p, ps ) = (γair (pref , Tref )(p − ps ) + γself (pref , Tref )ps ) . (2) T 11 Nous utilisons un profil de Lorentz, consistant en la formule suivante : f (ν, νηη0 , T, p) = 1 γ(T, p) . π γ(T, p)2 + (ν − νηη0 )2 (3) Ce profil sera alors multiplié par l’intensité de la raie par la formule suivante : kηη0 (ν, T, p) = Sηη0 (T )f (ν, νηη0 , T, p). 2.1.2 (4) Calcul de l’énergie absorbée Une fois la somme des contributions des raies en chaque point du spectre effectuée, nous calculons l’énergie absorbée par le volume de gaz considéré sur chaque longueur d’onde. L’énergie du rayon entrant Iin est décomposée en chaque longueur d’onde, formant également un spectre. L’opération à effectuer consiste à calculer le spectre de l’intensité sortante Iout d’un rayon traversant une section de longueur l du volume élémentaire de gaz considéré. Ce résultat dépendant de l’intensité entrante Iin et du coefficient d’absorption τ (ν) est donné par la loi de Beer-Lamber pour l’absorption (premier terme de Iout ci-dessous) et la loi de Planck pour l’émission induite par la chaleur (second terme de Iout ). Iout = Iin · e−τ (ν)·l + 1 2hν 3 −τ (ν)·l · · 1 − e c2 ν c2 e T −1 (5) Finalement, on calcule l’énergie totale absorbée par le volume de gaz depuis le rayon lancé. Cela demande de calculer l’intégrale de la différence entre l’énergie du rayon entrant et celle du rayon transmis Iout − Iin sur l’ensemble du spectre. Cette intégrale est approximée par une somme des échantillons. 2.2 Implantation sur GPU 2.2.1 Correction d’intensité et calcul de l’étalement : raies de largeur fixe Dans un premier temps, nous avons considéré le cas où les raies spectrales sont suffisamment étroites et espacées pour justifier l’utilisation d’une structure creuse, et où leur largeur varie peu. On considère également que le profil des raies ne va pas varier de manière significative au cours de la simulation. Dans cette implantation, le calcul du facteur de correction est effectué par les pixel shaders, unité généralement utilisée en GPGPU du fait de sa puissance de calcul élevée et de sa simplicité d’utilisation. Chaque paramètre lié aux raies (ν, Eη ) est placé dans une texture distincte. Ainsi, chaque pixel contiendra un vecteur contenant le même paramètre pour quatre raies différentes. Cela permet de mieux exploiter le parallélisme présent dans les unités de calcul vectorielles en groupant les données par vecteurs de quatre éléments. Les sous-expressions qui ne varient pas en fonction de la raie considérée sont précalculées sur le CPU et transmises au GPU en tant que paramètre. Le résultat du calcul est stocké dans une texture en écriture seule, mais qui sera accédée en lecture pour les opérations suivantes. Pour faire travailler le pixel shader sur chaque pixel de la texture de destination, on envoie au GPU un carré couvrant toute la surface de destination4 . Les sommets 4 On peut également utiliser un triangle plus grand que la destination, l’étape de clipping se chargeant d’éliminer les parties non visibles. 12 du carré passeront par un vertex shader minimal se contentant de retransmettre ces sommets. L’étape de rasterization va ensuite discrétiser le carré en fragments correspondants aux pixels de la texture de destination. Ces fragments seront transmis au pixel shader, permettant ainsi d’effectuer un calcul par pixel (fig. 7). Carré −c2 Eη Sommets νηη 0 Lecture des sommets Sηη0 (T ) Sηη0 (Tref ) f (Tref ) Échantillonnages de texture Vertex shader Rasterizer Sommets Pixel shader Fragments Pixels ROP Fragments F IG . 7 – Calcul des facteurs de correction des spectres. Pour le calcul de l’étalement, chaque raie est discrétisée de manière statique en un nombre fixe d’échantillons de largeur constante. Nous avons utilisé une décomposition en 16 échantillons par raie, avec un pas d’échantillonage de 5·10−5 . Le profil (équation 3) est ici considéré constant pour une raie donnée et est pré-calculé sur le CPU. Ces profils sont envoyés à la carte graphique et stockés dans une texture. À chaque pas de calcul, le GPU aura à multiplier l’intensité de chaque raie avec chaque échantillon du profil de cette raie. Cette implantation est décrite plus en détail dans une de nos publications[4]. 2.2.2 Correction d’intensité et calcul de l’étalement : raies de largeur variable Les approximations faites précédemment sont acceptables dans des conditions atmosphériques (400 K, 100 kPa). Cependant, dans les conditions correspondant à un récepteur solaire à gaz pressurisé (1000 K, 800 kPa), la situation est différente. En effet, avec l’augmentation de la température, le nombre de raies spectrales non négligeables augmente, de même que l’étalement de ces raies. On se retrouve donc dans une situation où les raies présentent un grand nombre de superpositions entre elles. De plus, la température pouvant changer significativement d’un point à l’autre du récepteur solaire, le profil des raies va également varier entre deux pas de calcul. Il ne sera donc plus possible de pré-calculer le profil d’étalement des raies comme dans la méthode précédente. Cette tâche sera donc réalisée par le GPU à chaque pas de calcul. En contrepartie de ces contraintes supplémentaires, la densité de raies spectrales est suffisante pour rendre l’utilisation d’une structure pleine efficace. On peut donc représenter un spectre échantillonné sous la forme d’un tableau (ou une texture), permettant les accès aléatoires. Pour avoir des estimations plus précises pour décider de l’implantation à utiliser, nous avons effectué quelques statistiques sur la base de données HITEMP dans les conditions visées. Celles-ci sont présentées sur le tableau 1. Le spectre échantillonné est conservé dans une texture. Comme nos GPU ne peuvent pas manipuler de textures unidimensionnelles suffisamment grandes, nous définissons une bijection depuis l’axe des nombres d’onde du spectre vers une texture carrée. Cela se réalise par une simple opération modulo. Pour une texture de taille s × s, l’échantillon de nombre d’onde ν est placé aux coordonnées (x, y) par les transformations 13 TAB . 1 – Statistiques sur les calculs nécessaires pour différentes valeurs de la limite sur kηη0 (ν, T, p) Limite 1 · 10−13 1 · 10−14 1 · 10−15 1 · 10−16 1 · 10−17 1 · 10−18 Raies CO2 H2 O 353 4 051 2 060 19 819 7 117 75 620 20 883 222 660 58 039 525 544 155 565 686 791 Échantillons H2 O CO2 7 324 366 156 126 552 2 435 108 927 628 12 617 948 4 678 780 54 189 736 20 700 852 204 068 100 81 948 032 684 901 816 Superp. max H2 O CO2 2 5 3 15 4 22 4 28 6 54 10 122 Largeur max H2 O CO2 19 145 113 461 395 1 457 1 259 4 609 3 987 14 573 12 609 46 085 suivantes : = νmin + (x + s × y) × νstep (6) x(ν) = (ν − νmin )/νstep mod s (7) y(ν) = ν(x, y) ((ν − νmin )/νstep − x(ν))/s (8) où νmin est l’extrémité gauche du spectre. Nous pouvons alors voir l’opération d’échantillonage des raies de manière graphique. Les raies sont dessinées sous forme de lignes sur la texture de destination. Ainsi, nous utilisons chaque étage du pipeline graphique. Les vertex shaders effectuent la correction de l’intensité des raies. L’unité de rasterization se charge ensuite de faire l’échantillonage de la raie, en créant un fragment par échantillon. Les paramètres seront interpolés à partir des valeurs des extrémités, ce qui nous permet de calculer les valeurs des nombres d’onde intermédiaires ν. Comme les raies peuvent franchir les bords de la texture pour continuer sur la ligne suivante, il va être nécessaire de scinder ces raies. Cette opération est réalisée en prétraitement sur le CPU, en considérant le pire cas (étalement maximal). Le calcul du facteur de correction va devoir être fait pour chaque sommet, et non pour chaque raie. La même opération devra donc être effectuée plusieurs fois. Cela n’a cependant pas d’impact perceptible, étant donné que les statistiques du tableau 1 nous montrent que le nombre de raies est très faible devant le nombre d’échantillons. Nos GPU utilisant des unités indépendantes pour l’exécution des vertex et pixel shaders, le calcul supplémentaire ne ralentit pas les autres unités. Une fois la calcul de l’étalement effectué et les raies discrétisées, le pixel shader nous permet de travailler sur chaque échantillon. Nous l’utilisons pour calculer le profil d’étalement des raies. Il faut ensuite considérer que plusieurs raies peuvent se superposer en un point donné. Les contributions respectives de chaque raie devront être sommées pour calculer le facteur d’absorption de l’échantillon. L’architecture de flot de données des shaders du GPU ne permet pas d’accéder à la fois en lecture et en écriture à une zone mémoire donnée. L’opération d’accumulation n’est donc pas possible dans les pixel shaders. Cependant, une unité est capable de réaliser des opérations de lecture-écriture à une même adresse. Il s’agit des unités de blending, qui permettent d’effectuer une opération entre le fragment calculé et le pixel déjà présent en mémoire à l’adresse de destination. Cette unité située après le pixel shader dans le pipeline graphique (dans l’unité ROP de 14 la figure 4) n’est pas programmable mais uniquement paramétrable. Elle gère donc un nombre limité d’opérations, parmi lesquelles des sommes pondérées. Un problème pratique se pose néanmoins : les unités de blending de nos GPU nVidia GeForce 7800 GTX et ATI Radeon X1800XL ne permettent pas les opérations en simple précision, mais sont limitées à la demi précision (format flottant 16 bit avec 10 bits de mantisse et 5 bits d’exposant) ou aux formats virgule fixe. Ces formats n’offrent pas une dynamique suffisante pour notre application. La génération suivante de GPU (nVidia GeForce série 8000 et ATI Radeon série 2000) permet le blending en mode simple précision. Pour les GPU antérieurs, une solution logicielle est possible. Nous utilisons une méthode basé sur la technique pingpong[17]. La méthode générale consiste utiliser deux textures qui serviront alternativement de source et de destination. On effectue autant de passes successives qu’il y a de niveaux de superpositions possibles (fig. 8). Lors de chaque passe, on lit chaque valeur de la texture source, effectue une opération dessus, et on écrit le résultat à l’adresse correspondante dans la texture de destination. Nouvelles donnes Texture A Nouvelles donnes Texture B Texture A (a) Passe 2n. Texture B (b) Passe 2n + 1. F IG . 8 – Méthode ping-pong. Considérons l’exemple des conditions de la troisième ligne du tableau 1, avec une texture de taille 2048 × 2048. Si la méthode ping-pong classique est appliquée directement, chaque pixel de la texture devra être recopié à chacune des 26 étapes, nécessitant le traitement de 109 051 904 pixels, soit 1,6 Go à transférer. Lors de la passe n, il est nécessaire de recopier des valeurs qui ne sont pas modifiées à cette étape, sous peine de perdre une partie des informations calculées à la passe précédente. Cependant, nous remarquons qu’il n’est pas nécessaire de recopier tous les pixels de la texture. D’une part, les pixels qui n’ont jamais été modifiés dans les passes précédentes n’ont pas besoin d’être recopiés. D’autre part, seuls les pixels qui ont été modifiés lors de la passe n − 1 ont besoin d’être recopiés. En effet, les autres ont déjà été écrits sur la texture de destination lors de la passe n − 2 ou une passe antérieure n − 2i. La mémoire n’étant pas effacée entre deux traversées du pipeline graphique, le résultat est déjà présent en mémoire dans ces deux cas. La méthode que nous avons mise au point est représentée figure 9, pour une texture source A et une texture destination B. Chaque passe est décomposée en trois phases. Dans un premier temps, les lignes ajoutées lors de la passe précédente sont envoyées à nouveau dans le pipeline graphique (fig. 9(a)). Ces lignes servent à sélectionner les pixels qu’il est nécessaire de recopier. Une valeur de profondeur positive est associée aux sommets correspondants. Le pixel shader utilisé se charge uniquement de copier les pixels sélectionnés. Lors de la deuxième phase (fig. 9(b)), les nouvelles lignes sont dessinées. Le pixel shader est utilisé ici pour réaliser la fusion entre la valeur à écrire et une valeur éventuellement déjà présente dans la texture A. 15 (a) Phase 1 (vertex et pixel shaders) : recopie. (b) Phase 2 (vertex et pixel shaders) : ajout et fusion. (c) Phase 3 (ROP) : arbitrage. F IG . 9 – Méthode ping-pong améliorée pour fusion de lignes. 16 Enfin, la dernière phase (fig. 9(c)) consiste à réaliser un arbitrage lorsqu’il y a superposition de fragments. L’unité de test d’occlusion est utilisée pour sélectionner le fragment issu de la fusion plutôt que de la copie. Les valeurs de profondeur ont en effet été ajustées de manière à placer les fragments fusionnés devant les fragments recopiés. En pratique, ces trois phases sont effectuées en parallèle et le pixel shader utilisé est le même pour les deux premières phases. Cela devrait permettre d’exploiter le test de profondeur anticipé (early z-culling) du GPU, qui permet d’abandonner des fragments cachés avant l’exécution du pixel shader, économisant des ressources de calcul et de bande passante. D’après nos tests, cela représente une amélioration des performances de 2 à 5% sur le nVidia GeForce 7800 GTX. Pour limiter les transferts entre la mémoire centrale et le GPU, nous utilisons les vertex buffer object d’OpenGL pour conserver les informations des sommets dans la mémoire graphique. 2.2.3 Calcul de l’énergie absorbée Nous représentons le spectre du rayon entrant dans le volume sous forme échantillonnée dans une texture, indépendamment de la représentation choisie (structure creuse ou pleine). Cette étape étant entièrement parallèle, elle est effectuée dans les pixel shaders, chaque pixel représentant 4 échantillons des spectres (fig. 10). Iin τ (ν) Iout Échantillonnages de texture Pixels Pixel shader Fragments ROP Fragments F IG . 10 – Calcul de l’énergie absorbée sur chaque longueur d’onde. En raison du modèle mémoire utilisé sur les GPU, il n’est pas possible d’utiliser un (ou plusieurs) accumulateur(s) pour calculer cette somme itérativement (somme récursive). La méthode utilisée pour le GPGPU pour de telles opérations de réduction consiste à effectuer des sommes locales successives [17] (fig. 11). F IG . 11 – Utilisation d’une méthode de réduction pour l’intégration. À chaque étape, le pixel shader est utilisé pour calculer les sommes partielles des valeurs de blocs de 4 pixels. 17 On remarque que l’ordre des opérations n’est pas le même suivant que l’on utilise une méthode par réduction ou une accumulation récursive traditionnelle. L’addition flottante n’étant pas associative, le résultat final pourra être différent suivant la méthode utilisée. La méthode par réduction (sommation deux-à-deux) utilisée par le GPU aura une borne sur l’erreur directe inférieure à celle de la somme récursive pratiquée par le CPU, d’après l’analyse d’erreur de Higham ([10] 4.2, p. 83). 2.3 Résultats 2.3.1 Correction d’intensité et calcul de l’étalement : raies de largeur fixe Les résultats du calcul complet en utilisant notre première implantation sont représentés figure 12, comparés avec un processeur de référence Intel Pentium 4 à 3 GHz équipé de 1 Go de DDR2-533. Tous les calculs sont effectués en simple précision IEEE. Sur le processeur, nous utilisons le jeu d’instruction SSE. 1000 1e+08 Intel Pentium 4 3GHz Nvidia 7800 GTX ATI X1800 XL Accélération 100 1e+06 Facteur d’accélération Raies par seconde 1e+07 100000 1000 10000 Raies spectrales 100000 10 1e+06 F IG . 12 – Comparaison de performance pour des raies de largeur fixe. On observe un facteur d’accélération de 420 pour un nombre de raies de l’ordre de 100000. Ce facteur s’explique en grande partie par la présence d’une unité de calcul matérielle de calcul de l’exponentielle dans les pixel shaders. Ainsi, le GeForce 7800 GTX dispose de 24 unités de pixel shaders pouvant chacune amorcer un calcul d’exponentielle par cycle à 486MHz, calculant au total 13,2 109 exponentielles par seconde. Le Pentium 4, malgré sa fréquence supérieure de 3GHz, évalue l’exponentielle par une méthode logicielle nécessitant typiquement une centaine de cycles, pour un débit de 30 106 exponentielles par seconde. La comparaison n’est cependant pas totalement juste. Le GPU calcule l’exponentielle en simple précision, avec une erreur maximale comprise entre 1 et 2 ulps sur le GeForce 7800 GTX. Sur le processeur, nous utilisons la fonction exp du standard C, qui donne un résultat en double précision avec une erreur maximale de l’ordre de 0,501 ulp. Malgré tout, une implantation logicielle de la fonction exponentielle en simple 18 précision nécessiterait au minimum 20 cycles sur le même processeur. Le facteur d’accélération obtenu serait alors de l’ordre de 80. 2.3.2 Correction d’intensité et calcul de l’étalement : raies de largeur variable Les résultats de cette étape sur notre GPU nVidia sont présentés sur la figure 13. 1e+10 40 Intel Pentium 4 3GHz (32−bit) Intel Pentium 4 3GHz (64−bit) Nvidia 7800 GTX avec blending (16−bit) Nvidia 7800 GTX sans blending (16−bit) Nvidia 7800 GTX sans blending (32−bit) Accélération avec blending Accélération sans blending (32−bit) 35 1e+09 25 20 15 1e+08 Facteur d’accélération Échantillons par seconde 30 10 5 1e+07 0 1e+06 1e+07 Nombre d’échantillons 1e+08 F IG . 13 – Performance de l’étape 1 sur notre première configuration. Pour déterminer si la performance est limitée ou non par le débit mémoire, nous considérons plusieurs arrangements de données. Le processeur écrit soit dans un tableau de flottants simple précision 32 bits, soit dans un tableau de doubles 64 bits. Sur le GPU, nous réalisons l’étape de fusion des raies soit dans une texture simple précision 32 bits, soit dans une texture demi précision 16 bits. Les temps d’exécution sont mesurés sur 100 itérations sur le GPU, pour minimiser l’impact du temps d’initialisation des pilotes, expliquant l’augmentation graduelle des performances avec celle de la taille du jeu de données. D’une part, pour bénéficier de la puissance de calcul du GPU, de gros jeux de données sont nécessaires pour exhiber suffisamment de parallélisme et amortir le temps constant d’initialisation du pipeline graphique et du surcoût dû aux pilotes. D’autre part, le processeur bénéficie aussi d’une augmentation de la densité de données. Cela amène plus de localité spatiale et temporelle, diminuant le taux de défauts de cache. Le facteur d’accélération redescend donc pour les densités les plus élevées (facteur de 37 pour près de 109 échantillons). Le facteur deux entre les performances obtenues en utilisant des textures 16 bits et 32 bits révèle que cette étape est limitée par la bande passante mémoire sur le GPU. À l’inverse, changer le format des données n’a pas d’influence significative sur la performance du processeur. Le temps de calcul sur CPU est donc limité soit par les calculs eux-même, soit par la latence mémoire, voire une combinaison des deux. Une des raisons de cette différence est la présence de divisions dans le calcul. En effet, notre GPU possède des unités d’évaluation de la fonction réciproque câblées et entièrement 19 pipelinées. Le processeur central calcule par contre les divisions avec des méthodes semi-logicielles, nécessitant 40 cycles d’horloge sur notre Pentium 4 Prescott. L’émulation de l’opération de blending dans les pixel shaders requiert approximativement le double de la bande passante. Cela explique le facteur deux obtenu entre les deux méthodes de fusion pour des jeux de données suffisants (plus de 2 · 106 échantillons). Considérant ces résultats entièrement dépendants de la bande passante, nous pouvons supposer qu’un GPU ayant des unités de blending pouvant travailler en 32 bits fonctionnerait environ deux fois plus lentement qu’en mode 16 bits. Cela représenterait un facteur d’accélération de 20 à 30 par rapport à la version tournant sur le CPU. 1e+10 25 Intel Pentium 4 3.2GHz (32−bit) ATI X1800 XL avec blending (16−bit) ATI X1800 XL sans blending (16−bit) ATI X1800 XL sans blending (32−bit) Accélération avec blending 20 15 1e+08 10 Facteur d’accélération Échantillons par seconde 1e+09 1e+07 5 1e+06 0 1e+06 1e+07 Nombre d’échantillons 1e+08 F IG . 14 – Performance de l’étape 1 sur notre seconde configuration. Notre seconde configuration de test présente des résultats similaires lors de l’utilisation de textures 16 bits (fig. 14). Cependant, l’utilisation de textures 32 bits introduit un ralentissement majeur, d’un facteur 20 à 100. Cela est vraisemblablement dû à un manque de mémoire graphique, qui oblige le GPU ou le driver à utiliser la mémoire centrale. De même, aucune de nos deux configurations n’a été capable de terminer le dernier test avec des textures 32 bits, les deux expériences résultant en un plantage de l’application. Cela est dû cette fois à un manque de mémoire centrale. Les deux systèmes comportent 1 Go de mémoire centrale et 256 Mo de mémoire vidéo. 2.3.3 Calcul de l’énergie absorbée Une comparaison des performances de notre implantation sur les pixel shaders de la nVidia 7800 GTX et notre processeur de référence est représentée figure 15. Le nombre d’échantillons calculés par seconde est présenté en fonction du nombre d’échantillons après fusion. Ces résultats ont été obtenus en variant la taille de la texture utilisée. Cette deuxième étape présente un facteur d’accélération proche de 330 pour 5 · 106 échantillons. De la même façon que pour le cas des raies de largeur fixe, la présence d’exponentielle dans le calcul permet de bénéficier des unités de fonctions spéciales câblées des GPU. Le calcul est ainsi effectué à une vitesse proche de celle du débit mémoire. 20 1e+10 350 Intel Pentium 4 3GHz Nvidia 7800 GTX (16−bit) Nvidia 7800 GTX (32−bit) Accélération (sans sommation) Nvidia 7800 GTX avec sommation (16−bit) Nvidia 7800 GTX avec sommation (32−bit) Accélération avec sommation 300 1e+09 200 1e+08 150 Facteur d’accélération Échantillons par seconde 250 100 1e+07 50 1e+06 0 10000 100000 1e+06 Nombre d’échantillons (fusionnés) 1e+07 F IG . 15 – Performance de l’étape 2 sur notre première configuration. Chaque étape de l’opération de réduction ne sollicitant que très peu les unités de calcul, les performances de l’intégration sont entièrement limitées par la bande passante mémoire. À l’inverse, le surcoût dû à l’opération de sommation sur le CPU est négligeable, étant donné que cela nécessite uniquement l’utilisation d’un registre supplémentaire et d’une opération d’addition à chaque itération. Calcul global Temps de calcul pour 6000 rayons (heures:minutes) 2.4 Intel Pentium 4 3GHz Nvidia 7800 GTX avec blending Nvidia 7800 GTX sans blending (16−bit) Nvidia 7800 GTX sans blending (32−bit) 10:00 01:00 00:10 1e+07 1e+08 Nombre d’échantillons F IG . 16 – Performance du calcul complet sur notre première configuration. 21 La figure 16 présente les temps d’exécution comparés pour le calcul d’un nombre de rayons typique. Le calcul qui nécessite 10 à 30 heures sur un processeur de station de travail nécessite ainsi de 10 minutes à 1 heure sur GPU. 3 Perspectives Plusieurs améliorations sont possibles pour une version ultérieure du programme. Concernant la méthode logicielle de fusion des lignes, plutôt que de redessiner toutes les lignes de la passe précédente pour éliminer ensuite les fragments en trop avec le test de profondeur, une amélioration possible est de pré-calculer un ensemble de lignes représentant la différence ensembliste entre les données de l’étape n − 1 et les données de l’étape n. Ainsi, seules les données nécessaires seront recopiées et le tampon de profondeur pourra être désactivé, libérant de la bande passante mémoire. Cette alternative semble particulièrement intéressante lorsque la densité de la géométrie est élevée. Cependant, elle est difficilement généralisable à d’autre primitives que des lignes horizontales. Pour des triangles ou des lignes non alignées, il est probable que le calcul de la différence géométrique résulte en une augmentation trop importante de la complexité géométrique. Les GPU compatibles DirectX 10 offrent également de nouvelles opportunités. L’architecture des GPU que nous utilisons ne nous permet pas d’ajouter de nouveaux sommets dynamiquement, ni d’effectuer un calcul unique pour un groupe de sommets, le résultat étant associé à chaque sommet du groupe. Le geometry shader de DirectX 10 pourra être utilisé pour réaliser sur le GPU la scission des raies qui intersectent les bords de la texture. De plus, le calcul de l’étalement pourra être fait dans le geometry shader. Il sera alors effectué une fois par raie et non plus une fois par sommet. Cette amélioration permettra non seulement de diminuer la charge sur les unités de calcul (maintenant unifiées) en éliminant les calculs inutiles, mais aussi de réduire la quantité de données qui devront être envoyées au GPU. Conclusion La principale raison expliquant les gains que nous avons obtenu est la présence des unités matérielles d’évaluation de fonctions spéciales sur les GPU. Ce choix qui a été abandonné depuis les années 1980 sur les processeurs généralistes conformément à la philosophie RISC revient sur les processeurs spécialisés comme les GPU. En plus de bénéficier aux applications graphiques pour les calculs géométriques (fonctions trigonométriques, racine carrée) et d’éclairement (exponentielle, logarithme), ces unités peuvent être utilisées pour des applications de modélisation. Il est probable que les facteurs d’accélérations supérieurs à 30 obtenus dans la littérature pour le calcul des équations de Maxwell5 ou de Black-Scholes[14] font usage des mêmes fonctionnalités. Une autre constante qui ressort de toutes les étapes de calcul effectuées est qu’à chaque fois, le facteur limitant est le débit mémoire et non celui des unités de calcul. Même dans ces conditions, le GPU garde un avantage sur le CPU, d’une part grâce à des débits mémoire crête nettement supérieurs, d’autre part grâce au parallélisme de données explicite permettant de mieux masquer la latence élevée des mémoires actuelles. 5 http://www.emphotonics.com/fastfdtd.php 22 Cette asymétrie entre calcul et mémoire s’explique par le fait que la mémoire se trouve à l’extérieur de la puce GPU, nécessitant des connexions externes. Ces larges bus de communication posent des problèmes de routage sur le circuit imprimé, montent difficilement en fréquence, consomment de l’énergie et sont coûteux. Ainsi, le Radeon HD 2900 XT d’AMD/ATI possède 2140 points d’entrée/sorties (I/O pads) pour un bus mémoire de 512 bits à 1650 MHz. À titre de comparaison, un processeur Intel Core 2 Duo E6650 possède 775 points pour un bus 64 bits à 1333 MHz. Cette limitation sur la largeur des bus mémoire conduit à utiliser des technologies DRAM fonctionnant à des fréquences élevées telles que la GDDR3 ou GDDR4, plus chères que la DDR2 utilisée comme mémoire centrale. L’absence de localité temporelle dans les calculs effectués ne permet pas d’utiliser de caches pour mitiger ce facteur. La mémoire de type SRAM a un coût trop élevé pour être intégrée en grande quantité. Cependant, il est possible d’intégrer des mémoires de types embedded DRAM soit sur le même die que le GPU, soit en intégrant un die de DRAM dans le même circuit intégré (package). Ces méthodes sont notamment utilisées par les processeurs graphiques de la PlayStation 2 de Sony et de la XBox 360 de Microsoft. Une autre limitation est le faible débit et la latence élevée du lien entre le GPU et le reste du système. Une solution actuellement envisagée consiste en un rapprochement entre le GPU et le CPU, le coprocesseur graphique étant probablement amené à être intégré au sein du processeur central, comme le furent les coprocesseurs arithmétiques. Références [1] David Blythe. The Direct3D 10 system. ACM Trans. Graph., 25(3) :724–734, 2006. [2] Ian Buck, Kayvon Fatahalian, and Pat Hanrahan. GPUbench : evaluating GPU performance for numerical and scientific application. In Proceedings of the ACM Workshop on General-Purpose Computing on Graphics Processors, pages C–20, Los Angeles, California, 2004. [3] Cyril Caliot. Modélisation et simulation de l’émission énergétique et spectrale d’un jet réactif composé de gaz et de particules à haute température issus de la combustion d’un objet pyrotechnique. PhD thesis, École des Mines d’AlbiCarmaux, 2006. [4] Sylvain Collange, Marc Daumas, and David Defour. Graphic processors to speed up simulations for the design of high performance solar receptors. In IEEE 18th international conference on application-specific systems, architectures and processors, 2007. [5] Sylvain Collange, Marc Daumas, and David Defour. État de l’intégration de la virgule flottante dans les processeurs graphiques. Technical report, ELIAUS, Université de Perpignan Via Domitia, 2007. [6] Marc Daumas, Guillaume Da Graça, and David Defour. Caractéristiques arithmétiques des processeurs graphiques. In Symposium en Architecture de Machines, Canet en Roussillon, France, 2006. [7] Alain Ferrière and Gilles Flamant. Projet PEGASE à THEMIS. Rapport numéro 3, Laboratoire PROMES, 2005. [8] Gilles Flamant. Proposition pour la réhabilitation du site de THEMIS. Rapport numéro 2, Laboratoire PROMES, 2004. 23 [9] W. Morven Gentleman and Scott B. Marovitch. More on algorithms that reveal properties of floating point arithmetic units. Communications of the ACM, 17(5), 1974. [10] Nicholas J. Higham. Accuracy and stability of numerical algorithms. SIAM, 2002. Second edition. [11] Karl Hillesland and Anselmo Lastra. GPU floating-point paranoia. In ACM Workshop on General Purpose Computing on Graphics Processors, page C8, August 2004. [12] Richard Karpinski. PARANOIA : a floating-point benchmark. Byte, 10(2) :223– 235, 1985. [13] Michael D. McCool. Data-parallel programming on the Cell BE and the GPU using the RapidMind development platform. In Presented at the GSPx Multicore Applications Conference, Santa Clara, October 31 to November 2, 2006, 2006. [14] Michael D. McCool, Kevin Wadleigh, Brent Henderson, and Hsin-Ying Lin. Performance evaluation of GPUs using the RapidMind development platform. Technical report, RapidMind, 2006. [15] John Montrym and Henry Moreton. The GeForce 6800. IEEE Micro, 25(2) :41– 51, 2005. [16] nVidia. NVIDIA CUDA Compute Unified Device Architecture Programming Guide, version 0.8.2, 2007. [17] Matt Pharr, editor. GPUGems 2 : Programming Techniques for HighPerformance Graphics and General-Purpose Computation. Addison-Wesley, 2005. [18] L. S. Rothman, R. B. Wattson, R. Gamache, J. W. Schroeder, and A. McCann. HITRAN HAWKS and HITEMP : high-temperature molecular database. In J. C. Dainty, editor, Proc. SPIE Vol. 2471, p. 105-111, Atmospheric Propagation and Remote Sensing IV, J. Christopher Dainty ; Ed., volume 2471 of Presented at the Society of Photo-Optical Instrumentation Engineers (SPIE) Conference, pages 105–111, June 1995. [19] L.S. Rothman et al. The HITRAN molecular spectroscopic database and HAWKS (hitran atmospheric workstation) : 1996 edition. Journal of Quantitative Spectroscopy and Radiative Transfer, 60(5) :665–710, 1998. [20] N. L. Schryer. A test of computer’s floating-point arithmetic unit. Technical report 89, AT&T Bell Laboratories, 1981. [21] S. A. Tashkun, V. I. Perevalov, J-L. Teffo, A. D. Bykov, and N. N. Lavrentieva. CDSD-1000, the high-temperature carbon dioxide spectroscopic databank. Journal of Quantitative Spectroscopy and Radiative Transfer, 82 :165–196, 2003. 24