Procédure pas à pas : Multiplication des matrices
Cette procédure pas à pas montre comment utiliser C++ AMP pour accélérer l’exécution de la multiplication de matrices. Deux algorithmes sont présentés, un sans mosaïques et un avec mosaïques.
Prérequis
Avant de commencer :
- Lire la vue d’ensemble de L’AMP C++.
- Lire à l’aide de vignettes.
- Vérifiez que vous exécutez au moins Windows 7 ou Windows Server 2008 R2.
Remarque
Les en-têtes AMP C++ sont déconseillés à partir de Visual Studio 2022 version 17.0.
L’inclusion d’en-têtes AMP génère des erreurs de génération. Définissez _SILENCE_AMP_DEPRECATION_WARNINGS
avant d’inclure tous les en-têtes AMP pour silence les avertissements.
Pour créer le projet
Les instructions de création d’un projet varient en fonction de la version de Visual Studio que vous avez installée. Pour consulter la documentation sur votre version préférée de Visual Studio, utilisez le contrôle de sélection de Version . Il se trouve en haut de la table des matières de cette page.
Pour créer le projet dans Visual Studio
Dans la barre de menus, choisissez Fichier>Nouveau>Projet pour ouvrir la boîte de dialogue Créer un projet.
En haut de la boîte de dialogue, définissez Langage sur C++, Plateforme sur Windows et Type de projet sur Console.
Dans la liste filtrée des types de projets, choisissez Projet vide, puis Suivant. Dans la page suivante, entrez MatrixMultiply dans la zone Nom pour spécifier un nom pour le projet, puis spécifiez l’emplacement du projet si vous le souhaitez.
Choisissez le bouton Créer pour créer le projet client.
Dans Explorateur de solutions, ouvrez le menu contextuel des fichiers sources, puis choisissez Ajouter>un nouvel élément.
Dans la boîte de dialogue Ajouter un nouvel élément , sélectionnez Fichier C++ (.cpp), entrez MatrixMultiply.cpp dans la zone Nom , puis choisissez le bouton Ajouter .
Pour créer un projet dans Visual Studio 2017 ou 2015
Dans la barre de menus de Visual Studio, choisissez Fichier>Nouveau>Projet.
Sous Installé dans le volet modèles, sélectionnez Visual C++.
Sélectionnez Projet vide, entrez MatrixMultiply dans la zone Nom , puis choisissez le bouton OK .
Choisissez le bouton Suivant.
Dans Explorateur de solutions, ouvrez le menu contextuel des fichiers sources, puis choisissez Ajouter>un nouvel élément.
Dans la boîte de dialogue Ajouter un nouvel élément , sélectionnez Fichier C++ (.cpp), entrez MatrixMultiply.cpp dans la zone Nom , puis choisissez le bouton Ajouter .
Multiplication sans mosaïcage
Dans cette section, tenez compte de la multiplication de deux matrices, A et B, qui sont définies comme suit :
A est une matrice de 3 par 2 et B est une matrice de 2 par 3. Le produit de la multiplication de A par B est la matrice suivante de 3 à 3. Le produit est calculé en multipliant les lignes d’A par les colonnes de l’élément B par élément.
Pour multiplier sans utiliser L’AMP C++
Ouvrez MatrixMultiply.cpp et utilisez le code suivant pour remplacer le code existant.
#include <iostream> void MultiplyWithOutAMP() { int aMatrix[3][2] = {{1, 4}, {2, 5}, {3, 6}}; int bMatrix[2][3] = {{7, 8, 9}, {10, 11, 12}}; int product[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { // Multiply the row of A by the column of B to get the row, column of product. for (int inner = 0; inner < 2; inner++) { product[row][col] += aMatrix[row][inner] * bMatrix[inner][col]; } std::cout << product[row][col] << " "; } std::cout << "\n"; } } int main() { MultiplyWithOutAMP(); getchar(); }
L’algorithme est une implémentation simple de la définition de la multiplication de matrices. Il n’utilise aucun algorithme parallèle ou thread pour réduire le temps de calcul.
Dans la barre de menus, sélectionnez Fichier>Enregistrer tout.
Choisissez le raccourci clavier F5 pour démarrer le débogage et vérifiez que la sortie est correcte.
Choisissez Entrée pour quitter l’application.
Pour multiplier à l’aide de C++ AMP
Dans MatrixMultiply.cpp, ajoutez le code suivant avant la
main
méthode.void MultiplyWithAMP() { int aMatrix[] = { 1, 4, 2, 5, 3, 6 }; int bMatrix[] = { 7, 8, 9, 10, 11, 12 }; int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; array_view<int, 2> a(3, 2, aMatrix); array_view<int, 2> b(2, 3, bMatrix); array_view<int, 2> product(3, 3, productMatrix); parallel_for_each(product.extent, [=] (index<2> idx) restrict(amp) { int row = idx[0]; int col = idx[1]; for (int inner = 0; inner <2; inner++) { product[idx] += a(row, inner)* b(inner, col); } }); product.synchronize(); for (int row = 0; row <3; row++) { for (int col = 0; col <3; col++) { //std::cout << productMatrix[row*3 + col] << " "; std::cout << product(row, col) << " "; } std::cout << "\n"; } }
Le code AMP ressemble au code non AMP. L’appel à
parallel_for_each
démarrer un thread pour chaque élément dansproduct.extent
, remplace lesfor
boucles de ligne et de colonne. La valeur de la cellule à la ligne et à la colonne est disponible dansidx
. Vous pouvez accéder aux éléments d’unarray_view
objet à l’aide de l’opérateur[]
et d’une variable d’index, ou de l’opérateur()
et des variables de ligne et de colonne. L’exemple illustre les deux méthodes. Laarray_view::synchronize
méthode copie les valeurs de laproduct
variable vers laproductMatrix
variable.Ajoutez les instructions suivantes
include
enusing
haut de MatrixMultiply.cpp.#include <amp.h> using namespace concurrency;
Modifiez la
main
méthode pour appeler laMultiplyWithAMP
méthode.int main() { MultiplyWithOutAMP(); MultiplyWithAMP(); getchar(); }
Appuyez sur le raccourci clavier Ctrl+F5 pour démarrer le débogage et vérifier que la sortie est correcte.
Appuyez sur La barre d’espace pour quitter l’application.
Multiplication avec mosaïcage
Le tiling est une technique dans laquelle vous partitionnez des données en sous-ensembles de taille égale, appelées vignettes. Trois choses changent lorsque vous utilisez le mosaïme.
Vous pouvez créer des
tile_static
variables. L’accès aux données danstile_static
l’espace peut être plusieurs fois plus rapide que l’accès aux données dans l’espace global. Une instance d’unetile_static
variable est créée pour chaque vignette, et tous les threads de la vignette ont accès à la variable. Le principal avantage de la mosaïne est le gain de performances en raison de l’accèstile_static
.Vous pouvez appeler la méthode tile_barrier ::wait pour arrêter tous les threads d’une vignette à une ligne de code spécifiée. Vous ne pouvez pas garantir l’ordre dans lequel les threads s’exécutent, uniquement que tous les threads d’une vignette s’arrêtent à l’appel
tile_barrier::wait
avant qu’ils continuent l’exécution.Vous avez accès à l’index du thread par rapport à l’objet entier
array_view
et à l’index par rapport à la vignette. En utilisant l’index local, vous pouvez faciliter la lecture et le débogage de votre code.
Pour tirer parti de la mosaïque en multiplication de matrices, l’algorithme doit partitionner la matrice en vignettes, puis copier les données de vignette dans des tile_static
variables pour un accès plus rapide. Dans cet exemple, la matrice est partitionnée en sous-entités de taille égale. Le produit est trouvé en multipliant les submatrices. Les deux matrices et leur produit dans cet exemple sont les suivants :
Les matrices sont partitionnées en quatre matrices 2x2, qui sont définies comme suit :
Le produit de A et B peut maintenant être écrit et calculé comme suit :
Étant donné que les matrices a
à travers h
sont des matrices 2x2, tous les produits et les sommes d’entre eux sont également des matrices 2x2. Il suit également que le produit de A et B est une matrice 4x4, comme prévu. Pour vérifier rapidement l’algorithme, calculez la valeur de l’élément dans la première ligne, première colonne du produit. Dans l’exemple, il s’agirait de la valeur de l’élément dans la première ligne et la première colonne de ae + bg
. Vous devez uniquement calculer la première colonne, la première ligne et ae
bg
pour chaque terme. Cette valeur est ae
(1 * 1) + (2 * 5) = 11
. La valeur pour bg
laquelle est (3 * 1) + (4 * 5) = 23
. La valeur finale est 11 + 23 = 34
, qui est correcte.
Pour implémenter cet algorithme, le code :
Utilise un
tiled_extent
objet au lieu d’unextent
objet dans l’appelparallel_for_each
.Utilise un
tiled_index
objet au lieu d’unindex
objet dans l’appelparallel_for_each
.Crée des
tile_static
variables pour contenir les sous-atrices.Utilise la
tile_barrier::wait
méthode pour arrêter les threads pour le calcul des produits des submatrices.
Pour multiplier à l’aide d’AMP et de mosaïne
Dans MatrixMultiply.cpp, ajoutez le code suivant avant la
main
méthode.void MultiplyWithTiling() { // The tile size is 2. static const int TS = 2; // The raw data. int aMatrix[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; int bMatrix[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Create the array_view objects. array_view<int, 2> a(4, 4, aMatrix); array_view<int, 2> b(4, 4, bMatrix); array_view<int, 2> product(4, 4, productMatrix); // Call parallel_for_each by using 2x2 tiles. parallel_for_each(product.extent.tile<TS, TS>(), [=] (tiled_index<TS, TS> t_idx) restrict(amp) { // Get the location of the thread relative to the tile (row, col) // and the entire array_view (rowGlobal, colGlobal). int row = t_idx.local[0]; int col = t_idx.local[1]; int rowGlobal = t_idx.global[0]; int colGlobal = t_idx.global[1]; int sum = 0; // Given a 4x4 matrix and a 2x2 tile size, this loop executes twice for each thread. // For the first tile and the first loop, it copies a into locA and e into locB. // For the first tile and the second loop, it copies b into locA and g into locB. for (int i = 0; i < 4; i += TS) { tile_static int locA[TS][TS]; tile_static int locB[TS][TS]; locA[row][col] = a(rowGlobal, col + i); locB[row][col] = b(row + i, colGlobal); // The threads in the tile all wait here until locA and locB are filled. t_idx.barrier.wait(); // Return the product for the thread. The sum is retained across // both iterations of the loop, in effect adding the two products // together, for example, a*e. for (int k = 0; k < TS; k++) { sum += locA[row][k] * locB[k][col]; } // All threads must wait until the sums are calculated. If any threads // moved ahead, the values in locA and locB would change. t_idx.barrier.wait(); // Now go on to the next iteration of the loop. } // After both iterations of the loop, copy the sum to the product variable by using the global location. product[t_idx.global] = sum; }); // Copy the contents of product back to the productMatrix variable. product.synchronize(); for (int row = 0; row <4; row++) { for (int col = 0; col <4; col++) { // The results are available from both the product and productMatrix variables. //std::cout << productMatrix[row*3 + col] << " "; std::cout << product(row, col) << " "; } std::cout << "\n"; } }
Cet exemple est sensiblement différent de l’exemple sans mosaïne. Le code utilise ces étapes conceptuelles :
Copiez les éléments de la vignette[0,0] dans
a
locA
. Copiez les éléments de la vignette[0,0] dansb
locB
. Notez qu’ilproduct
est en mosaïque, pasa
etb
. Par conséquent, vous utilisez des index globaux pour accédera, b
à , etproduct
. L’appel esttile_barrier::wait
essentiel. Il arrête tous les threads de la vignette jusqu’à ce que les deux etlocB
soientlocA
remplis.Multipliez et placez
locA
les résultats enproduct
.locB
Copiez les éléments de la vignette[0,1] dans
a
locA
. Copiez les éléments de la vignette [1,0] dansb
locB
.Multipliez
locA
etlocB
ajoutez-les aux résultats déjà présentsproduct
.La multiplication de la vignette[0,0] est terminée.
Répétez pour les quatre autres vignettes. Il n’existe aucune indexation spécifique pour les vignettes et les threads peuvent s’exécuter dans n’importe quel ordre. À mesure que chaque thread s’exécute, les
tile_static
variables sont créées pour chaque vignette de manière appropriée et l’appel auxtile_barrier::wait
contrôles du flux de programme.Lorsque vous examinez étroitement l’algorithme, notez que chaque sous-matrix est chargé dans une
tile_static
mémoire deux fois. Ce transfert de données prend du temps. Toutefois, une fois que les données sont entile_static
mémoire, l’accès aux données est beaucoup plus rapide. Étant donné que le calcul des produits nécessite un accès répété aux valeurs des submatrices, il existe un gain global de performances. Pour chaque algorithme, l’expérimentation est nécessaire pour trouver l’algorithme optimal et la taille de vignette.
Dans les exemples non AMP et non-vignettes, chaque élément de A et B est accessible quatre fois à partir de la mémoire globale pour calculer le produit. Dans l’exemple de vignette, chaque élément est accessible deux fois à partir de la mémoire globale et quatre fois à partir de la
tile_static
mémoire. Ce n’est pas un gain de performance significatif. Toutefois, si les matrices A et B étaient de 1024 x 1024 et que la taille des mosaïques était de 16, il y aurait un gain de performances significatif. Dans ce cas, chaque élément est copié entile_static
mémoire seulement 16 fois et accessible à partir detile_static
la mémoire 1024 fois.Modifiez la méthode principale pour appeler la
MultiplyWithTiling
méthode, comme indiqué.int main() { MultiplyWithOutAMP(); MultiplyWithAMP(); MultiplyWithTiling(); getchar(); }
Appuyez sur le raccourci clavier Ctrl+F5 pour démarrer le débogage et vérifier que la sortie est correcte.
Appuyez sur la barre d’espace pour quitter l’application.
Voir aussi
C++ AMP (C++ Accelerated Massive Parallelism)
Procédure pas-à-pas : débogage d’une application C++ AMP