Visual Studio 2012 propose une nouvelle librairie appelée C++ Accelerated Massive Parallelism (C++ AMP). Cette librairie permet d'exploiter facilement les nombreux cœurs des cartes GPU afin d'obtenir des performances extrêmes. À l'instar des librairies parallèles fournies avec Visual Studio 2010, la librairie C++ AMP est accompagnée d'un écosystème facilitant le cycle de vie d'une application utilisant du code GPU. Que vous soyez développeur C++ ou bien même C# sans connaissance sur la programmation GPU, cette session vous permettra de comprendre à la fois les concepts inhérents à la programmation sur GPU et leurs implémentations respectives avec la librairie C++ AMP. Si au contraire, vous êtes déjà familier avec les technologies comme CUDA ou OpenCL, cette session vous permettra d'apprécier l'expressivité de C++ AMP associé à un outillage à la fois simple et efficace.
La programmation GPU avec C++ AMP pour les performances extrêmes
1. Donnez votre avis !
Depuis votre smartphone, sur :
http://notes.mstechdays.fr
De nombreux lots à gagner toutes les heures !!!
Claviers, souris et jeux Microsoft…
Merci de nous aider à améliorer les TechDays
http://notes.mstechdays.fr
2. La programmation GPU
avec C++ AMP pour les
performances extrêmes
Bruno Boucard
Expert Parallèle
FINAXYS
LAN401 http://blogs.msdn.com/devpara/
Code / Développement
3. CONSEIL ET ARCHITECTURE IT-
•
•
160 collaborateurs spécialisés en IT-Finance
CA 2012 (prév.) de 16M€
FINANCE
• Une large palette d’expertises :
• Conseil SI & fonctionnel Finance,
• AMOA SI et métiers Finance
• Ingénierie logicielle (architecture, études, conception, développement, intégration continue)
• Technologies Microsoft C++ et C#/.NET
• Support et production applicative
• Un accompagnement global sur l’ensemble du cycle de vie projet, du cadrage à la recette, avec une démarche qualité
intégrée
• Une contractualisation sur mesure : forfait, centre de service, assistance technique groupée ou assistance technique
simple
• Nos références :
Ma société
5. Agenda
• 1ière Partie : L’essentiel de C++ AMP
– Contexte
– Fondamentaux
• 2ième Partie: Concepts plus avancés
– Comprendre le calcul tuilé
– Mise au point avec Visual Studio 2012
• Conclusion
La programmation GPU avec C++ AMP
6. CPU vs GPU aujourd’hui
• Faible bande passante mémoire • Bande passante mémoire élevée
• Forte consommation d’énergie/FLOP • Faible consommation d'énergie/FLOP
• Niveau moyen de parallélisme • Haut niveau de parallélisme
• Pipelines d’exécution profonds • Pipelines d'exécution peu profonds
• Accès cache mémoire aléatoires • Accès cache mémoire séquentiels
• Supporte la programmation généraliste • Supporte la programmation parallèle orientée
• Programmation grand public données
• Programmation réservée à des spécialistes
Contexte
7. CPU vs GPU aujourd’hui
image
• CPUs et GPUs se rapprochent de plus en plus … source: AMD
• …mais rien n’est figé, le sujet est toujours en mouvement …
• C++ AMP a été pensé comme une solution grand public pas
seulement pour aujourd’hui, mais aussi pour demain
Contexte
8. Plateformes d’exécution de C++ AMP*
• Windows Azure • Windows Phone
• Windows RT
• Windows Desktop
• Windows Embedded
• Windows Server
• Xbox
• Windows HPC Server
... tourne aussi sur des plateformes non Microsoft via une spécification
ouverte
Contexte * Certaines plateformes seront disponibles dans la prochaine version de C++ AMP
9. Qu’est que C++ AMP ?
• Appartient à Visual C++
• Intégrée à Visual Studio
• Librairie STL-like pour gérer
les données performance
multidimensionnelles
• Construite sur Direct3D 11 productivité
• Spécification ouverte
portabilité
Contexte
10. Transformer ce code séquentiel en code
GPU #include <amp.h>
using namespace concurrency;
void AddArrays(int n, int * pA, int * pB, int * pC) void AddArrays(int n, int * pA, int * pB, int * pC)
{ {
array_view<int,1> a(n, pA);
array_view<int,1> b(n, pB);
array_view<int,1> c(n, pC);
for (int i=0; i<n; i++) parallel_for_each(
for (int i=0; i<n; i++)
c.extent,
[=](index<1> i) restrict(amp)
{ {
pC[i] = pA[i] + pB[i]; c[i] = = pA[i] + pB[i];
pC[i] a[i] + b[i];
} }
);
} }
Fondamentaux
11. Comprendre le code C++ AMP
parallel_for_each: void AddArrays(int n, int * pA, int * pB, int * pC)
restrict(amp): dit au compilateur de
exécute l’expression { forcer l’exécution de ce code sur le
lambda sur array_view<int,1> a(n, pA); GPU
l’accélérateur une fois array_view<int,1> b(n, pB);
par thread
array_view<int,1> c(n, pC);
array_view: wrapping des
parallel_for_each( données pour les traiter sur
l’accélérateur
extent: le nombre de
c.extent,
threads et leur topologie [=](index<1> i) restrict(amp)
pour exécuter {
l’expression lambda c[i] = a[i] + b[i];
}
); Les variables array_view sont
capturées par valeur et leurs
index: le thread ID qui exécute l’expression
} données associées sont copiées
lambda, est utilisé pour retrouver les données
dans l’accélérateur à la demande
Fondamentaux
12. Comprendre le fonctionnement du
parallel_for_each
• Exécute l’expression lambda pour chaque point de l’extent
• Semble synchrone, mais en réalité est asynchrone
parallel_for_each(
c.extent,
[=](index<1> idx) restrict(amp)
c[1] = a[1] + b[1]
c[0] = a[0] + b[0]
c[2] = a[2] + b[2]
c[3] = a[3] + b[3]
c[4] = a[4] + b[4]
c[5] = a[5] + b[5]
c[6] = a[6] + b[6]
c[7] = a[7] + b[7]
{
Code noyau
c[idx] = a[idx] + b[idx];
}
);
}
0 1 2 3 4 5 6 7
Fondamentaux
13. Le mot clef restrict
• S'applique aux fonctions (y compris les lambda)
• resctrict(…) informe le compilateur d’appliquer des
restrictions au langage
– e.g. restrictions spécifiques, optimisations,
génération de code
– Dans cette 1ère version, supporte deux directives
• cpu – est le défaut implicite
• amp – vérifie la conformité de la fonction vis à vis
de C++ AMP
Fondamentaux
14. Les restrictions du mot clef restrict
• Pas de • Pas de
• récusions • goto ou labels
• ‘volatile' • throw, try, catch
• fonctions virtuelles • Variables globales ou statiques
• pointeurs de fonctions • dynamic_cast ou typeid
• pointeurs vers des fonctions membres • déclarations __asm__
• pointeurs dans des structures • varargs
• pointeurs de pointeurs • types non supportés
• champs de bits • e.g. char, short, long double
Fondamentaux
15. Surcharge via le mot clef restrict
double cos( double d ); // 1a code cpu
double cos( double d ) restrict(amp); // 1b code amp
double bar( double d ) restrict(cpu,amp); // 2 supporte les deux mode
void some_method(array_view<double,2> c) {
parallel_for_each( c.extent, [=](index<2> idx) restrict(amp)
{
//…
double d0 = c[idx];
double d1 = bar(d0); // ok, bar supporte amp aussi
double d2 = cos(d0); // ok, sélection de la surcharge amp
//…
});
}
Fondamentaux
16. Gestion des données multidimensionnelles
• index<N> - représente un point à N coordonnées
• extent<N> - taille de la données d’un espace à N dimensions
index<2> i(0,2); index<3> i(2,0,1);
index<1> i(2);
3 2
6 3
extent<1> e(6); 4 2
extent<2> e(3,4); extent<3> e(3,2,2);
• N peut être n’importe quel nombre <=128
Fondamentaux
17. Le conteneur: array_view<T, N>
• Vue sur les données stockées dans la mémoire CPU ou
la mémoire GPU
• Capture par valeur [=] dans l’expression lambda
• De type T et de rang N
• Réclame un extent
vector<int> v(10);
• Rectangulaire extent<2> e(2,5); array_view<int,2> a(e, v);
• Accès aux éléments déclenchent
• des copies implicites
index<2> i(3,9); // Les 2 lignes peuvent aussi s’écrire
// array_view<int,2> a(2,5,v);
int o = a[i]; // ou a[i] = 16;
// ou int o = a(3, 9);
Fondamentaux
18. Le second conteneur: array<T, N>
• Tableau multidimensionnel qui contient des éléments de type T de dimension N
• Les données sont stockées dans la mémoire de l'accélérateur
• Capture par référence [&] dans l’expression lambda
• Copie explicit
• Presque identique à l’interface array_view<T,N>
vector<int> v(8 * 12); parallel_for_each(e, [&](index<2> idx) restrict(amp)
extent<2> e(8,12); {
a[idx] += 1;
array<int,2> a(e); });
copy_async(v.begin(), v.end(), a); copy(a, v.begin());
Fondamentaux
19. Comprendre les accélérateurs Host Accélérateurs
• accelerator
– DX11 GPU
• Support GPU
PCIe
– WARP (Windows Advanced Rasterisation
Platform)
• multi-core & vectorisazion (SSE)
• Accessible via Direct3D
– REF
• Emulateur qui simule un pilote direct3d sur • accelerator View
CPU – Contexte pour
– CPU ordonnancer les threads
GPU et gérer les données
• Support pour optimiser les transferts de
en mémoire
données entre l'hôte et un
accelerator_view
Fondamentaux
21. Algorithme produit matriciel
• Aij * Bjk = Cik
– Chaque ligne A est multipliée par toutes les
colonnes entières de B
– Chaque multiplication est sommée dans une
variable temporaire
– Après avoir multiplié tous les éléments de la
ligne avec tous les éléments de la colonne
courante
– Le résultat temporaire est placé dans la cellule
correspondante de la « matrice produit » C
Fondamentaux
22. Produit matriciel du CPU au GPU
void MatrixMultiplySerial( vector<float>& vC, void MatrixMultiplyAMP( vector<float>& vC,
const vector<float>& vA, const vector<float>& vA,
const vector<float>& vB, int M, int N, int W ) const vector<float>& vB, int M, int N, int W )
{ {
array_view<const float,2> a(M,W,vA),b(W,N,vB);
array_view<float,2> c(M,N,vC);c.discard_data();
parallel_for_each(c.extent,
for (int row = 0; row < M; row++) { [=](index<2> idx) restrict(amp) {
for (int col = 0; col < N; col++){ int row = idx[0]; int col = idx[1];
float sum = 0.0f; float sum = 0.0f;
for(int i = 0; i < W; i++) for(int i = 0; i < W; i++)
sum += vA[row * W + i] * vB[i * N + col]; sum += a(row, i) * b(i, col);
vC[row * N + col] = sum; c[idx] = sum;
} }
} );
} }
Fondamentaux
24. 1ière Partie: ce que vous avez appris !
Sur des volumes de données immenses, le calcul sur GPU offre des performances
extraordinaires
Microsoft souhaite démocratiser la programmation GPU avec C++ AMP sur toutes les
plateformes
Les éléments fondamentaux
class index<N> : pointe une valeur dans un conteneur multidimensionnel
class extent<N> : taille d’un conteneur multidimensionnel
restrict(amp, cpu) : restreint la syntaxe C++ pour exécuter une méthode ou une
lambda en mode noyau
parallel_for_each : exécute une boucle en parallèle sur des données
multidimensionnelle
class array_view<T,N> : wrapper multidimensionnel sur des données
class accelerator : accélérateur utilisé en interne pour exécuter le code parallèle
class accelerator_view : isole l’accélérateur des différents usages multithreads
class array<T,N> : conteneur multidimensionnel sur des données
Résumé de la première partie
25. Une vue du matériel GPU pour un développeur
Thread
… …
… …… … ……
Registres par Threads
N’est pas montré:
• Mémoire constante
• Mémoire des
Mémoire Globale contrôleurs
• Schedulers
• Autres caches
• Cas Multi-GPU
Comprendre le calcul tuilé
26. Une vue du matériel GPU pour un développeur
… … Tuile composée de
threads
… …… … ……
Registres par Threads Registres par Threads
N’est pas montré:
• Mémoire constante
• Mémoire des
Mémoire Globale contrôleurs
• Schedulers
• Autres caches
• Cas Multi-GPU
Comprendre le calcul tuilé
27. Une vue du matériel GPU pour un développeur
… … Tuile composée de
threads
… …… … …… Les variable tile_static
sont partagée par les
threads dans la même
tuile
Registres par Threads Registres par Thread
Cache Programmable Cache Programmable N’est pas montré:
• Mémoire constante
• Mémoire des contrôleurs
• Schedulers
Mémoire Globale • Autres caches
• Cas Multi-GPU
Comprendre le calcul tuilé
28. parallel_for_each: surcharge pour les tuiles
• Orchestrer des threads array_view<int,1> data(12, my_data);
dans des tuiles
– La version de base ne
le permet pas parallel_for_each(data.extent,
• parallel_for_each version [=] (index<1> idx) restrict(amp)
tuile accepte ces { … });
surcharges
– tiled_extent<D0> ou
tiled_extent<D0, D1> ou parallel_for_each(data.extent.tile<6>(),
tiled_extent<D0, D1, D2>
• L’expression lambda [=] (tiled_index<6> t_idx) restrict(amp)
accepte { … });
– tiled_index<D0> ou
tiled_index<D0, D1> ou
tiled_index<D0, D1, D2>
Comprendre le calcul tuilé
30. tiled_index
• Soit col 0 col 1 col 2 col 3 col 4 col 5
array_view<int,2> data(2, 6, p_my_data); row
parallel_for_each( 0
data.extent.tile<2,2>(),
[=] (tiled_index<2,2> t_idx)… { … });
row
1
T
• Lorsque la lambda est exécutée par T
– t_idx.global // index<2> (1,3)
– t_idx.local // index<2> (1,1)
– t_idx.tile // index<2> (0,1)
– t_idx.tile_origin // index<2> (0,2)
Comprendre le calcul tuilé
31. tile_static
• Le mot clef tile_static dédié au stockage mémoire
• Second mot clef ajouté au langage C++
• Représente la mémoire locale du GPU
• Usage du mot clé tile_static
– Dans une boucle parallel_for_each tuilée
– Le mot clef tile_static devant une déclaration de
variable locale place cette variable dans la mémoire
locale GPU pour la tuile courante
– Cette mémoire est partagée par tous les threads
associés à la tuile
– Applicable seulement pour les fonctions restrict(amp)
Comprendre le calcul tuilé
32. tile_barrier
• classe tile_barrier
– Synchronise tous les threads au sein d’une tuile jusqu'à
ce que tous les accès à la mémoire soient terminés
• t_idx.barrier.wait();
• ~ t_idx.barrier.wait_with_all_memory_fence();
– Synchronise tous les threads au sein d’une tuile jusqu'à
ce que tous les accès à la mémoire globale soient
terminés
• t_idx.barrier.wait_with_global_memory_fence();
– Synchronise tous les threads au sein d’une tuile jusqu'à
ce que tous les accès à la mémoire la mémoire tile_static
(locale) soient terminés
• t_idx.barrier.wait_with_tile_static_memory_fence();
Comprendre le calcul tuilé
33. Cacher avec tile_static 0,0 0,1 0,2 0,3 0,4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
1 static const int TS = 2;
2 array_view<int, 2> av(2, 6, my_vector);
3 parallel_for_each(av.extent.tile<TS,TS>(),
[=](tiled_index<TS,TS> t_idx) restrict(amp)
4 {
5 tile_static int t[TS][TS];
6 t[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global];
7
8 if (t_idx.local == index<2>(0,0)) {
9 int temp = t[0][0] + t[0][1] + t[1][0] + t[1][1];
10 av[t_idx.tile_origin] = temp;
11 }
12 });
13 int sum = av(0,0) + av(0,2) + av(0,4); //the three tile_origins
Comprendre le calcul tuilé
34. tile_static en action 0,0 0,1 0,2 0,3 0,4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
1 static const int TS = 2;
2 array_view<int, 2> av(2, 6, my_vector);
3 parallel_for_each(av.extent.tile<TS,TS>(),
[=](tiled_index<TS,TS> t_idx) restrict(amp)
4 {
5 tile_static int t[TS][TS];
6 t[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global];
7
8 if (t_idx.local == index<2>(0,0)) {
9 int temp = t[0][0] + t[0][1] + t[1][0] + t[1][1];
10 av[t_idx.tile_origin] = temp;
11 }
12 });
13 int sum = av(0,0) + av(0,2) + av(0,4); //the three tile_origins
Comprendre le calcul tuilé
35. Synchro avec tile_barrier0,0 0,1 0,2 0,3 0,4 0,5
1,0 1,1 1,2 1,3 1,4 1,5
1 static const int TS = 2;
2 array_view<int, 2> av(2, 6, my_vector);
3 parallel_for_each(av.extent.tile<TS,TS>(),
[=](tiled_index<TS,TS> t_idx) restrict(amp)
4 {
5 tile_static int t[TS][TS];
6 t[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global];
7 tile_barrier.wait();
8 if (t_idx.local == index<2>(0,0)) {
9 int temp = t[0][0] + t[0][1] + t[1][0] + t[1][1];
10 av[t_idx.tile_origin] = temp;
11 }
12 });
13 int sum = av(0,0) + av(0,2) + av(0,4); //the three tile_origins
Comprendre le calcul tuilé
36. Retour sur la Multiplication Matricielle
• Chargement des données pour 4 threads
• Les threads chargent plusieurs fois la même
valeur depuis la mémoire globale !
– Comment éviter ce problème ?
Comprendre le calcul tuilé
37. Multiplication Matricielle – Exemple (tuilée)
void MatrixMultSimple(vector<float>& vC, const vector<float>& vA, const void MatrixMultTiled(vector<float>& vC, const vector<float>& vA, const
void
vector<float>& vB, int M, int N, int W ) vector<float>& vB, int M, int N, W)
const vector<float>& vB, int M, int N, int W )
{ {
static const int TS = 16;
const int TS = 16;
array_view<const float,2> a(M, W, vA), b(W, N, vB); array_view<const float,2> a(M, W, vA), b(W, N, vB);
array_view<float,2> c(M,N,vC); c.discard_data(); array_view<float,2> c(M,N,vC); c.discard_data();
parallel_for_each(c.extent, parallel_for_each(c.extent.tile< TS, TS >(),
TS, TS >(),
[=] (index<2> idx) restrict(amp) { [=] (tiled_index<TS, TS> t_idx) restrict(amp) { {
(tiled_index< TS, TS> t_idx) restrict(amp)
int row = idx[0]; int col = idx[1]; int row = t_idx.local[0]; int col = t_idx.local[1];
t_idx.local[1];
float sum = 0.0f; float sum = 0.0f;
for (int i = 0; ii < W; ii += TS) {{
0; < W; += TS)
tile_static float locA[TS][TS], locB[TS][TS];
tile_static float locA[TS][TS], locB[TS][TS];
Phase 1
locA[row][col] = a(t_idx.global[0], col i);
locA[row][col]= a(t_idx.global[0], col ++ i);
locB[row][col] = b(row + i, t_idx.global[1]);
locB[row][col] = b(row + i, t_idx.global[1]);
t_idx.barrier.wait();
t_idx.barrier.wait();
for(int k = 0; k < W; k++) for (int k = 0; k < TS; k++)
Phase 2
sum += a(row, k) * b(k, col); sum += locA[row][k] * locB[k][col];
t_idx.barrier.wait();
t_idx.barrier.wait();
}
c[idx] = sum; c[t_idx.global] = sum;
c[t_idx.global] = sum;
} ); } );
} }
Techniques plus avancées: calcul tuilé
41. 2ième Partie: ce que vous avez appris !
Comprendre le calcul tuilé
La macro architecture GPU pour les développeurs
La classe tile_static pour cacher des données pour un groupe de threads
Le classe tiled_extent< , , > permet à la surcharge parallel_foreach de simplifier le
calcul en tuilé
La classe tiled_index< , , > permet à la surcharge parallel_foreach de simplifier le
calcul en tuilé
La classe tile_barrier permet de synchroniser les threads au sein d’une tuile
Mise au point avec Visual Studio
Configurer le mode Debug GPU
Détecter les races conditions
Utiliser la fenêtre Parallel GPU pour observer l’état de tous les threads et naviguer
entre eux
Utiliser la fenêtre Parallel Watch pour observer des données pour chacun des threads
Exécuter toute une tuile de threads jusqu’à votre curseur
Résumé de la seconde partie
42. Conclusion
• Démocratisation de la programmation parallèle hétérogène
– Haute performance accessible au grand public
– Abstraction de la plateforme matérielle
– Abstractions de haut niveau en C++ (pas en langage C)
– Repose sur un jeu minimal d’API
– C++ AMP est une spécification libre
– Etat de l’art de l’IDE de Visual Studio
C++ AMP en quelques mots
43. Pour aller plus loin …
MSDN Forums to ask questions
• http://social.msdn.microsoft.com/Forums/en/parallelcppnative/threads
MSDN Native parallelism blog (team blog)
• http://blogs.msdn.com/nativeconcurrency/
Libraries for C++ AMP
C++ AMP Samples • http://blogs.msdn.com/b/nativeconcurrency/archive/2012/05/19/libraries-for-
• http://blogs.msdn.com/b/nativeconcurrency/archive/2012/01/30/c-amp-sample- c-amp.aspx
projects-for-download.aspx
Graphics in C++ AMP C++ AMP for the OpenCLProgrammer
• http://blogs.msdn.com/b/nativeconcurrency/archive/2012/01/25/concurrency-
• http://blogs.msdn.com/b/nativeconcurrency/archive/2012/04/10/c-amp-for-
graphics-in-c-amp.aspx the-opencl-programmer.aspx
Interoperability between Direct 3D and C++ AMP C++ AMP for the CUDA Programmer
• http://blogs.msdn.com/b/nativeconcurrency/archive/2011/12/29/interoperabilit • http://blogs.msdn.com/b/nativeconcurrency/archive/2012/04/11/c-amp-for-
y-between-direct-3d-and-c-amp.aspx the-cuda-programmer.aspx
C++ AMP open spec C++ AMP for the DirectCompute Programmer
• http://blogs.msdn.com/b/nativeconcurrency/archive/2012/02/03/c-amp-open- • http://blogs.msdn.com/b/nativeconcurrency/archive/2012/04/09/c-amp-for-
spec-published.aspx the-directcompute-programmer.aspx
Pour creuser un peu plus …
44. Développeurs Pros de l’IT
http://aka.ms/generation-app Formez-vous en ligne www.microsoftvirtualacademy.com
http://aka.ms/evenements-
developpeurs Retrouvez nos évènements http://aka.ms/itcamps-france
Les accélérateurs
Faites-vous accompagner
Windows Azure, Windows Phone,
gratuitement
Windows 8
Essayer gratuitement nos http://aka.ms/telechargements
solutions IT
La Dev’Team sur MSDN Retrouver nos experts L’IT Team sur TechNet
http://aka.ms/devteam Microsoft http://aka.ms/itteam
Hinweis der Redaktion
Notation
Intro code / dev
Utilisation de Visual C++ component extensions (C++/CX ) afin de produire un application Windows Store sur le base de l’application full C++.