Introduction
GPGPU
General Purpose Graphic Processor Unit
Programmation GPGPU
Technique de programmation visant à l'implantation des applications en exécution sur
les cartes graphiques (GPU) au lieu des processeurs centraux (CPU).
Technique développée depuis le début des années 2000, moment où les fabriquants de
cartes graphiques (NVidia, ATI, 3D Labs, ...) ont rendu possible l'implantation de
programmes tiers sur leurs adaptateurs graphiques (fonctionnalité obligatoire pour
l'implantation de Microsoft DirectX 9).
Historiquement il n'existait pas d'API spécifique.
-> Les développements se faisaient par détournement des langages de programmation de
shaders graphiques à des fins autres que la programmation d'algorithmes de l'infographie:
- GLSL pour OpenGL,
- HLSL pour DirectX,
- CG pour les cartes graphiques NVidia.
Apparition d'API spécifiques indépendantes des API graphiques:
- CUDA pour les cartes graphiques NVidia
- Streams pour les cartes graphiques ATI
- OpenCL, API générique spécifiée par khronos
Avantages
- La puissance de calcul théoriquement disponible (puissance de crête) sur les GPU est
très largement supérieure à celle proposée par les CPU les plus rapides.
CPU |
Puissance de crête |
TDP |
Intel Pentium 4 - 2.8 Ghz - 1 cur - x86 |
2.7 GFlops |
70 W |
Intel Centrino Duo - 1.66 Ghz - 2 curs - x86 |
3.1 GFlops |
30 W |
Intel Xeon 5410 - 2.33 Ghz - 4 curs - x64 |
9,5 GFlops |
100 W |
Intel Core I7 980x - 3.33 Ghz - 6 coeurs - x64 |
20 GFlops |
130 W |
AMD Athlon 64 x2 - 3.0 Ghz - 2 curs - x64 |
5,9 GFlops |
65 W |
AMD Phenom II x6 - 3.0 Ghz - 6 curs - x64 |
17,8 GFlops |
100 W |
|
GPU |
Puissance de crête |
TDP |
NVidia 9800 GT - 112 curs - 600 Mhz |
370 GFlops |
230 W |
NVidia GT240 - 96 coeurs - 550 Mhz |
260 GFlops |
180 W |
NVidia GTX 285 - 240 curs - 648 Mhz |
700 GFlops |
370 W |
NVidia GTX 480 - 480 curs - 700 Mhz |
1350 GFlops |
410 W |
ATI HD 3870 - 320 curs - 775 Mhz |
490 GFlops |
240 W |
ATI HD 4890 - 800 curs - 850 Mhz |
1360 GFlops |
290 W |
ATI HD 5970 - 3200 curs - 725 Mhz |
4640 GFlops |
430 W |
|
Les adaptateurs graphiques sont peu couteux.
L'efficacité énergétique est meilleure sur les GPU que sur les CPU:
- de 5 à 10 Watt / GFlops pour les CPU,
- de 0.1 à 1 Watt / GFlops pour les GPU.
Inconvénients
Les GPU ayant prioritairement été conçus pour une utilisation dans le
cadre d'algorithmes de l'infographie, ils peuvent présenter certaines limites techniques
(abscence de certaines opérations mathématiques, entiers et réels simple précision,
...).
Les API actuelles ne font pas appel aux concepts de la programmation
orientée objet mais seulement à la programmation structurée.
La programmation parallèle est obligatoire.
Les modèles de programmation parallèle utilisables sont limités.
-> Il faut que l'application développée se prête bien à ce type de développement.
L'exploitation pleine et entière de la puissance de calcul disponible
n'est pas simple à obtenir.
Les langages de développement de shader
Les API 3D modernes permettent de développer de petits programmes, les
"shaders", destinés à être exécutés directement sur les GPU de la carte
graphique.
C'est le cas sous DirectX depuis la version 9.0 de cette bibliothèque. Sous OpenGL, c'est
aussi le cas depuis la version 2.0.
Il existe différents types de shaders. Les types les plus courants sont
les "vertex shaders" et les "fragment shaders" aussi appelés
"pixel shaders".
Le vertex shader configuré pour une opération d'affichage sera appelé
pour traiter chacun des vertices (sommets) de définition des primitives graphiques de
modélisation géométrique de la scène.
Le fragment shader configuré sera appelé pour traiter chacun des pixels issus de la
rasterisation des primitives graphiques de modélisation.
Un premier type de parallèlisation pourra être obtenu en utilisant les
vertex shaders:
En effet, les sommets d'une primitive graphique seront gérés en parallèle par
traitement au moyen du vertex shader simultanément sur plusieurs GPU. Plusieurs
primitives graphiques pourront même voir leurs sommets traités en parallèle sur
plusieurs GPU.
Un deuxième type de parallèlisation pourra être obtenu en utilisant les
fragment shaders:
Les pixels de rasterisation d'une primitive seront gérés en parallèle par traitement au
moyen du fragment shader exécuté simultanément sur plusieurs GPU.
Les shaders sont de "relativement petits" programmes destinés
à être exécutés un très très grand nombre de fois. Les problèmes solvables au moins
d'une tâche unique exécutée de multiple fois sur des données différentes de type
identique sont succeptibles de bien être adaptés à une programmation au moyen d'un
langage de shader.
Contrainte: La communication des données et la
récupération des résultats doivent s'intégrer dans le cadre d'un fonctionnement de
type "programme avec affichage graphique 3D".
Données:
- positions de sommets
- normales
- couleur de tracé
- valeur de matériel
- texture
- variable d'environnement
- ...
Résultats:
- une image
OpenGL GLSL
GLSL est le langage de développement de shaders introduit dans OpenGL
avec la version 2.0 de l'API. Il permet de créer des vertex shaders et des fragment
shaders.
Si ni vertex shader ni fragment shader n'est spécifié, OpenGL utilise
ses propres shaders "fixes". Si un vertex shader est employé, celui-ci sera
utilisé en lieu et place du vertex shader d'OpenGL. Si un fragment shader est employé,
celui-ci sera utilisé en lieu et place du fragment shader d'OpenGL.
-> Pour obtenir le même résultat écran qu'avec les shaders de base OpenGL, tout
shader installé devra réaliser l'ensemble des opérations réalisées par le shader de
base qu'il remplace.
Rôles des vertex shaders GLSL
Le but du vertex shader est de traiter le sommet qu'il reçoit en gestion
pour préparer les informations qui seront transmises au vertex shader exécuté pour
chacun des pixels de rastérisation de la primitive auquel appartient le sommet.
- Réalisation de la transformation géométrique de passage des coordonnées de
modélisation spécifiées par glVertex en coordonnées écran.
- Réalisation des calculs d'éclairage si l'illumination "flat" ou Gouraud est
utilisée.
- Calcul de toute autre information à transmettre au fragment shader.
- ...
Rôles des fragment shaders GLSL
Le but du fragment shader est de calculer la couleur du pixel pour lequel
il est lancé. Toute information reçue du vertex shader est automatiquement interpolée
entre les valeurs calculées par le vertex shader et en fonction de la position du pixel
au sein de la primitive à laquelle il appartient.
- Réalisation des calculs d'éclairage si l'illumination Phong est utilisée.
- Réalisation du plaçage de texture.
- ...
Syntaxe
Syntaxe proche de celle du langage C.
Exemple de vertex shader
uniform float RotationY;
varying vec3 MCposition;
varying vec3 Direction;
void main() {
Direction = vec3(0.0,0.0,-1.0);
mat3 ry = mat3( cos(RotationY), 0.0, sin(RotationY),
0.0, 1.0, 0.0,
-sin(RotationY), 0.0, cos(RotationY));
MCposition = ry*vec3(gl_Vertex);
Direction = ry*Direction;
gl_Position = ftransform();
}
|
Exemple de fragment shader
uniform vec3 Dimensions;
uniform sampler3D Texture3D;
varying vec3 MCposition;
varying vec3 Direction;
vec4 rayTracing(float x,float y,float z,
float incx,float incy,float incz) {
float alpha = 1.0;
vec4 cl = vec4(0.0,0.0,0.0,1.0);
do {
vec4 c = texture3D(Texture3D,vec3(x,y,z));
float aa = c.a*alpha;
cl.r = cl.r + c.r*aa;
cl.g = cl.g + c.g*aa;
cl.b = cl.b + c.b*aa;
alpha *= (1.0-c.a);
x += incx;
y += incy;
z += incz; }
while ( ( x >= 0.0 ) && ( x < 1.0 ) &&
( y >= 0.0 ) && ( y < 1.0 ) &&
( z >= 0.0 ) && ( z < 1.0 ) );
return(cl);
}
void main() {
float x = MCposition.x/Dimensions.x+0.5;
float y = MCposition.y/Dimensions.y+0.5;
float z = MCposition.z/Dimensions.z+0.5;
gl_FragColor = rayTracing(x,y,z,
Direction.x/Dimensions.x,
Direction.y/Dimensions.y,
Direction.z/Dimensions.z);
}
|
Les variables "uniform" sont directement reçue de l'host. Les variables
"varying" permettent la transmission d'information (avec interpolation) du
vertex shader vers le fragment shader.
Dans le vertex shader, la variable gl_position est la variable qui doit être affectée
pour indiquer la coordonnée écran du sommet.
Dans le fragment shader, la variable gl_FragColor est la variable qui doit être affectée
pour indiquer la couleur du pixel.
Les shaders sont chargés et compilés directement sur la carte graphique par le pilote
logiciel de la carte au moyen d'un jeu de fonction d'extension d'OpenGL.
Limitations
- Tous les processeurs exécutent le même vertex shader.
- Tous les processeurs exécutent le même fragment shader.
- Pas de réel double précision.
- Pas d'entier long.
- Pas de récursivité!
Les API spécialisées
Les contraintes liées à l'utilisation des API graphiques pour les
détourner à une autre fin que le calcul d'images sont trop importantes. Elles ne rendent
pas cette technique suffisamment souple pour qu'elle soit adaptée à un large spectre
applicatif.
Des API spécialisées ont été créées pour rendre plus versatile
l'emploi des GPU.
Les trois principales sont NVidia CUDA, ATI Streams et OpenCL. CUDA et
Streams sont des API propriétaires. OpenCL est une API libre développée par le
consortium Kronos qui est en charge d'OpenGL, OpenML, ...
CUDA
CUDA permet la création de programmes destinés à être exécutés sur
les multiples processeurs présents sur une carte graphique.
Le kit de développement CUDA inclut un compilateur destiné à générer
le code exécutable sur la carte graphique.
Il inclut aussi une API permettant de contrôler les interactions entre le CPU, nommé
host, et la(les) carte(s) graphique(s) utilisée(s), nommé device(s). Entre autres, cette
API permet d'organiser l'allocation de mémoire sur les devices, les transferts mémoire
host -> devices et devices -> host et bien entendu le lancement des programmes CUDA
sur les devices.
La syntaxe du langage associé à CUDA, le CUDA-C est proche de celle du
langage C.
Avantages
- Beaucoup plus souple que les langages de shading
- Assez propre
- Langage très proche du langage C -> pas déstabilisant
- ...
Inconvénients
- Allers et retours mémoire entre host et devices très néfastes en terme de performance
- Saturation possible de la bande passante mémoire
- Gros challenge pour obtenir les performances de crête (exploitation maximale des caches
mémoire, ...).
...
|