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

Documents pareils