Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 partitionnement et un avec partitionnement.
Prerequisites
Avant de commencer :
- Lire Aperçu de C++ AMP.
- Lire Utilisation des tuiles.
- Vérifiez que vous exécutez au moins Windows 7 ou Windows Server 2008 R2.
Note
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 build. Définissez _SILENCE_AMP_DEPRECATION_WARNINGS avant d’inclure des en-têtes AMP pour éviter 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 afficher la documentation relative à votre version préférée de Visual Studio, utilisez le contrôle de sélecteur 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>.
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 tuilage
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 en utilisant C++ AMP
Dans MatrixMultiply.cpp, ajoutez le code suivant avant la
mainmé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_eachdémarre un thread pour chaque élément deproduct.extentet remplace les boucles de lignes et de colonnesfor. La valeur de la cellule à la ligne et à la colonne est disponible dansidx. Vous pouvez accéder aux éléments d’unarray_viewobjet à 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::synchronizeméthode copie les valeurs de laproductvariable vers laproductMatrixvariable.Ajoutez les instructions suivantes
includeetusingau sommet de MatrixMultiply.cpp.#include <amp.h> using namespace concurrency;Modifiez la
mainméthode pour appeler laMultiplyWithAMPmé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_staticvariables. L’accès aux données danstile_staticl’espace peut être plusieurs fois plus rapide que l’accès aux données dans l’espace global. Une instance d’unetile_staticvariable est créée pour chaque vignette, et tous les threads de la vignette ont accès à la variable. Le principal avantage du tuilage 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 dans une tuile à une ligne de code spécifiée. Vous ne pouvez pas garantir l'ordre exact dans lequel les threads s'exécuteront, mais seulement que tous les threads d'une même vignette s'arrêteront à l'appel de
tile_barrier::waitavant de reprendre leur exécution.Vous avez accès à l’index du thread par rapport à l’objet entier
array_viewet à 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 du découpage en multiplication de matrices, l’algorithme doit partitionner la matrice en tuiles, puis copier les données des tuiles dans des variables tile_static 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 de ae et de bg pour chaque terme. La valeur pour ae est (1 * 1) + (2 * 5) = 11. La valeur pour bg 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_extentobjet au lieu d’unextentobjet dans l’appelparallel_for_each.Utilise un
tiled_indexobjet au lieu d’unindexobjet dans l’appelparallel_for_each.Crée des
tile_staticvariables pour contenir des sous-matrices.Utilise la méthode
tile_barrier::waitpour arrêter les threads lors du calcul des produits des sous-matrices.
Pour multiplier à l’aide d’AMP et de mosaïne
Dans MatrixMultiply.cpp, ajoutez le code suivant avant la
mainmé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
alocA. Copiez les éléments de la vignette[0,0] dansblocB. Notez queproductest en mosaïque, contrairement àaetb. Par conséquent, vous utilisez des index globaux pour accéder àa, betproduct. L'appel àtile_barrier::waitest essentiel. Il arrête tous les threads de la vignette jusqu'à ce quelocAetlocBsoient tous deux remplis.Multipliez
locAetlocBet placez les résultats dansproduct.Copiez les éléments de la vignette[0,1] dans
a. Copiez les éléments de la vignette [1,0] dansblocB.Multipliez
locAetlocBajoutez-les aux résultats déjà présentsproduct.La multiplication de la tuile[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_staticvariables sont créées pour chaque tuile de manière appropriée et l'appel àtile_barrier::waitcontrôle le flux du programme.Lorsque vous examinez étroitement l’algorithme, notez que chaque sous-matrix est chargé dans une
tile_staticmémoire deux fois. Ce transfert de données prend du temps. Toutefois, une fois que les données sont entile_staticmé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-tuile, chaque élément de A et B est accédé quatre fois depuis 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_staticmé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é dans la mémoiretile_staticseulement 16 fois et accessible à partir de la mémoiretile_static1024 fois.Modifiez la méthode principale pour appeler la
MultiplyWithTilingmé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 également
C++ AMP (Parallélisme Massif Accéléré en C++)
Procédure pas-à-pas : débogage d’une application C++ AMP