Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este passo a passo demonstra como usar o C++ AMP para acelerar a execução da multiplicação de matrizes. Dois algoritmos são apresentados, um sem segmentação e outro com segmentação.
Pré-requisitos
Antes de começar:
- Leia Visão geral do C++ AMP.
- Leia Usando azulejos.
- Verifique se você está executando pelo menos o Windows 7 ou o Windows Server 2008 R2.
Observação
Os cabeçalhos AMP C++ foram preteridos a partir do Visual Studio 2022 versão 17.0.
A inclusão de cabeçalhos AMP gerará erros de compilação. Defina _SILENCE_AMP_DEPRECATION_WARNINGS antes de incluir quaisquer cabeçalhos AMP para silenciar os avisos.
Para criar o projeto
As instruções para criar um novo projeto variam dependendo de qual versão do Visual Studio você instalou. Para ver a documentação da sua versão preferida do Visual Studio, use o controlador de seleção Versão . Encontra-se na parte superior do índice desta página.
Para criar o projeto no Visual Studio
Na barra de menus, escolha Arquivo>Novo>Projeto para abrir a caixa de diálogo Criar um Novo Projeto .
Na parte superior da caixa de diálogo, defina Language como C++, defina Platform como Windows e defina Project type como Console.
Na lista filtrada de tipos de projeto, escolha Projeto vazio e, em seguida, escolha Avançar. Na próxima página, digite MatrixMultiply na caixa Nome para especificar um nome para o projeto e especifique o local do projeto, se desejado.
Escolha o botão Criar para criar o projeto cliente.
No Gerenciador de Soluções, abra o menu de atalho para Arquivos de Origem e escolha Adicionar>Novo Item.
Na caixa de diálogo Adicionar Novo Item , selecione Arquivo C++ (.cpp), digite MatrixMultiply.cpp na caixa Nome e escolha o botão Adicionar .
Para criar um projeto no Visual Studio 2017 ou 2015
Na barra de menus do Visual Studio, escolha Arquivo>Novo>Projeto.
Em Instalado no painel de modelos, selecione Visual C++.
Selecione Projeto vazio, digite MatrixMultiply na caixa Nome e escolha o botão OK .
Escolha o botão Next .
No Gerenciador de Soluções, abra o menu de atalho para Arquivos de Origem e escolha Adicionar>Novo Item.
Na caixa de diálogo Adicionar Novo Item , selecione Arquivo C++ (.cpp), digite MatrixMultiply.cpp na caixa Nome e escolha o botão Adicionar .
Multiplicação sem pavimentação
Nesta seção, considere a multiplicação de duas matrizes, A e B, que são definidas da seguinte forma:
A é uma matriz 3 por 2 e B é uma matriz 2 por 3. O produto da multiplicação de A por B é a seguinte matriz 3 por 3. O produto é calculado multiplicando as linhas de A pelas colunas de B elemento por elemento.
Para multiplicar sem usar C++ AMP
Abra MatrixMultiply.cpp e use o código a seguir para substituir o código existente.
#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(); }O algoritmo é uma implementação direta da definição de multiplicação matricial. Ele não usa nenhum algoritmo paralelo ou encadeado para reduzir o tempo de computação.
Na barra de menus, escolha Arquivo>Salvar tudo.
Escolha o atalho de teclado F5 para iniciar a depuração e verifique se a saída está correta.
Escolha Enter para sair do aplicativo.
Para multiplicar usando C++ AMP
No MatrixMultiply.cpp, adicione o seguinte código antes do
mainmétodo.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"; } }O código AMP é semelhante ao código não-AMP. A chamada para
parallel_for_eachinicia um thread para cada elemento noproduct.extente substitui osforloops de linha e coluna. O valor da célula na linha e coluna está disponível emidx. Você pode acessar os elementos de umarray_viewobjeto usando o[]operador e uma variável de índice, ou o()operador e as variáveis de linha e coluna. O exemplo demonstra ambos os métodos. Oarray_view::synchronizemétodo copia osproductvalores da variável de volta para aproductMatrixvariável.Adicione as seguintes instruções
includeeusingna parte superior de MatrixMultiply.cpp.#include <amp.h> using namespace concurrency;Modifique o
mainmétodo para chamar oMultiplyWithAMPmétodo.int main() { MultiplyWithOutAMP(); MultiplyWithAMP(); getchar(); }Pressione o atalho de teclado Ctrl+F5 para iniciar a depuração e verificar se a saída está correta.
Pressione a barra de espaço para sair do aplicativo.
Multiplicação com mosaicos
A técnica de mosaico consiste em particionar dados em subconjuntos de tamanho igual, conhecidos como tiles. Três coisas mudam quando usas revestimento.
Você pode criar
tile_staticvariáveis. O acesso aos dados notile_staticespaço pode ser muitas vezes mais rápido do que o acesso aos dados no espaço global. Uma instância de umatile_staticvariável é criada para cada bloco, e todos os threads no bloco têm acesso à variável. O principal benefício do azulejamento é o ganho de desempenho devido aotile_staticacesso.Você pode chamar o método tile_barrier::wait para parar todos os encadeamentos num tile numa linha de código específica. Você não pode garantir a ordem em que os threads serão executados, apenas que todos os threads em um bloco pararão na chamada para
tile_barrier::waitantes de continuarem a execução.Você tem acesso ao índice da linha de execução relativo ao objeto inteiro
array_viewe ao índice relativo ao tile. Usando o índice local, você pode tornar seu código mais fácil de ler e depurar.
Para tirar proveito do particionamento em blocos na multiplicação de matrizes, o algoritmo deve dividir a matriz em azulejos e, em seguida, copiar os dados desses azulejos em tile_static variáveis para acesso mais rápido. Neste exemplo, a matriz é particionada em submatrizes de tamanho igual. O produto é encontrado multiplicando as submatrizes. As duas matrizes e seu produto neste exemplo são:
As matrizes são particionadas em quatro matrizes 2x2, que são definidas da seguinte forma:
O produto de A e B pode agora ser escrito e calculado da seguinte forma:
Como as matrizes de a a h são matrizes 2x2, todos os produtos e somas delas também são matrizes 2x2. Segue-se também que o produto de A e B é uma matriz 4x4, como esperado. Para verificar rapidamente o algoritmo, calcule o valor do elemento na primeira linha, primeira coluna do produto. No exemplo, esse seria o valor do elemento na primeira linha e na primeira coluna de ae + bg. Só tem de calcular a primeira coluna e a primeira linha de ae e bg para cada termo. Esse valor para ae é (1 * 1) + (2 * 5) = 11. O valor para bg é (3 * 1) + (4 * 5) = 23. O valor final é 11 + 23 = 34, o que está correto.
Para implementar esse algoritmo, o código:
Usa um
tiled_extentobjeto em vez de umextentobjeto na chamadaparallel_for_each.Usa um
tiled_indexobjeto em vez de umindexobjeto na chamadaparallel_for_each.Cria
tile_staticvariáveis para manter as submatrizes.Usa o método
tile_barrier::waitpara suspender as threads que calculam os produtos das submatrizes.
Para multiplicar usando AMP e mosaico
No MatrixMultiply.cpp, adicione o seguinte código antes do
mainmétodo.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"; } }Este exemplo é significativamente diferente do exemplo sem revestimento. O código usa estas etapas conceituais:
Copie os elementos de tile[0,0] de
aemlocA. Copie os elementos de tile[0,0] debemlocB. Observe queproductestá lado a lado, nãoaeb. Portanto, você usa índices globais para acessara, beproduct. A chamada paratile_barrier::waité essencial. Pára todos os processos no bloco até que amboslocAelocBestejam preenchidos.Multiplique
locAelocBcoloque os resultados emproduct.Copie os elementos de tile[0,1] de
aemlocA. Copie os elementos do mosaico [1,0] debemlocB.Multiplique
locAelocBadicione-os aos resultados que já estão emproduct.A multiplicação da telha[0,0] está completa.
Repita para os outros quatro azulejos. Não há indexação especificamente para os blocos e os threads podem ser executados em qualquer ordem. À medida que cada thread é executada, as variáveis
tile_staticsão criadas adequadamente para cada bloco, e a chamada paratile_barrier::waitcontrola o fluxo do programa.Ao examinar o algoritmo de perto, observe que cada submatriz é carregada em uma
tile_staticmemória duas vezes. Essa transferência de dados leva tempo. No entanto, uma vez que os dados estão natile_staticmemória, o acesso aos dados é muito mais rápido. Como o cálculo dos produtos requer acesso repetido aos valores nas submatrizes, há um ganho de desempenho global. Para cada algoritmo, a experimentação é necessária para encontrar o algoritmo ideal e o tamanho do bloco.
Nos exemplos não-AMP e não-tile, cada elemento de A e B é acessado quatro vezes a partir da memória global para calcular o produto. No exemplo de bloco, cada elemento é acessado duas vezes da memória global e quatro vezes da memória
tile_static. Não se trata de um ganho de desempenho significativo. No entanto, se as matrizes A e B fossem 1024x1024 e o tamanho da telha fosse 16, haveria um ganho de desempenho significativo. Nesse caso, cada elemento seria copiado natile_staticmemória apenas 16 vezes e acessado datile_staticmemória 1024 vezes.Modifique o método principal para chamar o
MultiplyWithTilingmétodo, conforme mostrado.int main() { MultiplyWithOutAMP(); MultiplyWithAMP(); MultiplyWithTiling(); getchar(); }Pressione o atalho de teclado Ctrl+F5 para iniciar a depuração e verificar se a saída está correta.
Pressione a barra de espaço para sair do aplicativo.
Ver também
C++ AMP (paralelismo maciço acelerado em C++)
Guia: Depuração de uma Aplicação AMP C++