Usando objetos accelerator e accelerator_view
Você pode usar as classes accelerator e accelerator_view para especificar o dispositivo ou o emulador no qual executar o código C++ AMP. Um sistema pode ter vários dispositivos ou emuladores que diferem por quantidade de memória, suporte de memória compartilhada, suporte à depuração ou suporte à precisão dupla. O C++ AMP (C++ Accelerated Massive Parallelism) fornece APIs que você pode usar para examinar os aceleradores disponíveis, definir um como padrão, especificar vários accelerator_views para várias chamadas a parallel_for_each e executar tarefas especiais de depuração.
Observação
Os cabeçalhos AMP C++ foram preteridos a partir do Visual Studio 2022 versão 17.0.
Incluir todos os cabeçalhos AMP gerará erros de build. Defina _SILENCE_AMP_DEPRECATION_WARNINGS
antes de incluir qualquer cabeçalho AMP para silenciar os avisos.
Usar o acelerador padrão
O runtime do C++ AMP escolhe um acelerador padrão, a menos que você escreva um código para escolher um específico. O runtime escolhe o acelerador padrão da seguinte maneira:
Se o aplicativo estiver em execução no modo de depuração, um acelerador que dê suporte à depuração.
Caso contrário, o acelerador especificado pela
CPPAMP_DEFAULT_ACCELERATOR
variável de ambiente, se estiver definido.Senão, um dispositivo não emulado.
Ou então, o dispositivo que tenha a maior quantidade de memória disponível.
Ou ainda, um dispositivo que não esteja anexado à exibição.
Além disso, o runtime especifica um access_type
de access_type_auto
para o acelerador padrão. Isso significa que o acelerador padrão usa memória compartilhada se houver suporte e se suas características de desempenho (largura de banda e latência) forem conhecidas por serem as mesmas que a memória dedicada (não compartilhada).
Você pode determinar as propriedades do acelerador padrão construindo o acelerador padrão e examinando suas propriedades. O exemplo de código a seguir imprime o caminho, a quantidade de memória do acelerador, o suporte à memória compartilhada, o suporte à precisão dupla e o suporte limitado à precisão dupla do acelerador padrão.
void default_properties() {
accelerator default_acc;
std::wcout << default_acc.device_path << "\n";
std::wcout << default_acc.dedicated_memory << "\n";
std::wcout << (accs[i].supports_cpu_shared_memory ?
"CPU shared memory: true" : "CPU shared memory: false") << "\n";
std::wcout << (accs[i].supports_double_precision ?
"double precision: true" : "double precision: false") << "\n";
std::wcout << (accs[i].supports_limited_double_precision ?
"limited double precision: true" : "limited double precision: false") << "\n";
}
Variável de ambiente CPPAMP_DEFAULT_ACCELERATOR
Você pode definir a variável de ambiente CPPAMP_DEFAULT_ACCELERATOR para especificar o accelerator::device_path
do acelerador padrão. O caminho depende do hardware. O código a seguir usa a função accelerator::get_all
para recuperar uma lista dos aceleradores disponíveis e exibe o caminho e as características de cada acelerador.
void list_all_accelerators()
{
std::vector<accelerator> accs = accelerator::get_all();
for (int i = 0; i <accs.size(); i++) {
std::wcout << accs[i].device_path << "\n";
std::wcout << accs[i].dedicated_memory << "\n";
std::wcout << (accs[i].supports_cpu_shared_memory ?
"CPU shared memory: true" : "CPU shared memory: false") << "\n";
std::wcout << (accs[i].supports_double_precision ?
"double precision: true" : "double precision: false") << "\n";
std::wcout << (accs[i].supports_limited_double_precision ?
"limited double precision: true" : "limited double precision: false") << "\n";
}
}
Selecionar um acelerador
Para selecionar um acelerador, use o método accelerator::get_all
para recuperar uma lista dos aceleradores disponíveis e selecione um com base em suas propriedades. Este exemplo mostra como escolher o acelerador que tem mais memória:
void pick_with_most_memory()
{
std::vector<accelerator> accs = accelerator::get_all();
accelerator acc_chosen = accs[0];
for (int i = 0; i <accs.size(); i++) {
if (accs[i].dedicated_memory> acc_chosen.dedicated_memory) {
acc_chosen = accs[i];
}
}
std::wcout << "The accelerator with the most memory is "
<< acc_chosen.device_path << "\n"
<< acc_chosen.dedicated_memory << ".\n";
}
Observação
Um dos aceleradores que são retornados pelo accelerator::get_all
é o acelerador de CPU. Não é possível executar código no acelerador de CPU. Para filtrar o acelerador de CPU, compare o valor da propriedade device_path do acelerador retornado por accelerator::get_all
com o valor do acelerador::cpu_accelerator. Para obter mais informações, confira a seção “Aceleradores especiais” neste artigo.
Memória compartilhada
Memória compartilhada é memória que pode ser acessada pela CPU e pelo acelerador. O uso de memória compartilhada elimina ou reduz significativamente a sobrecarga de cópia de dados entre a CPU e o acelerador. Embora a memória seja compartilhada, ela não pode ser acessada simultaneamente pela CPU e pelo acelerador e fazer isso causa um comportamento indefinido. A propriedade do acelerador supports_cpu_shared_memory retorna true
se o acelerador dá suporte à memória compartilhada e a propriedade default_cpu_access_type obtém o access_type padrão para a memória alocada no accelerator
— por exemplo, matrizes associadas aos objetos accelerator
ou array_view
acessados no accelerator
.
O runtime do C++ AMP escolhe automaticamente o melhor access_type
padrão para cada accelerator
, mas as características de desempenho (largura de banda e latência) da memória compartilhada podem ser piores do que as da memória de acelerador dedicada (não compartilhada) ao ler da CPU, gravar da CPU ou ambos. Se a memória compartilhada tiver desempenho tão bom quanto da memória dedicada para leitura e gravação da CPU, o runtime padronizará para access_type_read_write
; caso contrário, o runtime escolherá um padrão mais conservador access_type
e permitirá que o aplicativo o substitua se os padrões de acesso à memória de seus kernels de computação se beneficiarem de um access_type
diferente.
O exemplo de código a seguir mostra como determinar se o acelerador padrão dá suporte à memória compartilhada e, em seguida, substitui seu tipo de acesso padrão e cria um accelerator_view
a partir dele.
#include <amp.h>
#include <iostream>
using namespace Concurrency;
int main()
{
accelerator acc = accelerator(accelerator::default_accelerator);
// Early out if the default accelerator doesn't support shared memory.
if (!acc.supports_cpu_shared_memory)
{
std::cout << "The default accelerator does not support shared memory" << std::endl;
return 1;
}
// Override the default CPU access type.
acc.set_default_cpu_access_type(access_type_read_write);
// Create an accelerator_view from the default accelerator. The
// accelerator_view reflects the default_cpu_access_type of the
// accelerator it's associated with.
accelerator_view acc_v = acc.default_view;
}
Um accelerator_view
sempre reflete o default_cpu_access_type
do accelerator
que está associado e não fornece nenhuma interface para substituir ou alterar seu access_type
.
Alterar o acelerador padrão
Você pode alterar o acelerador padrão chamando o método accelerator::set_default
. Você pode alterar o acelerador padrão apenas uma vez por execução de aplicativo e deve alterá-lo antes que qualquer código seja executado na GPU. Todas as chamadas de função subsequentes para alterar o retorno false
do acelerador. Se você quiser usar um acelerador diferente em uma chamada para parallel_for_each
, leia a seção "Usar vários aceleradores" neste artigo. O exemplo de código a seguir define o acelerador padrão como um que não é emulado, não está conectado a uma exibição e dá suporte a precisão dupla.
bool pick_accelerator()
{
std::vector<accelerator> accs = accelerator::get_all();
accelerator chosen_one;
auto result = std::find_if(accs.begin(), accs.end(),
[] (const accelerator& acc) {
return !acc.is_emulated &&
acc.supports_double_precision &&
!acc.has_display;
});
if (result != accs.end()) {
chosen_one = *(result);
}
std::wcout <<chosen_one.description <<std::endl;
bool success = accelerator::set_default(chosen_one.device_path);
return success;
}
Usar vários aceleradores
Há duas maneiras de usar vários aceleradores em seu aplicativo:
Você pode passar objetos
accelerator_view
para as chamadas para o método parallel_for_each.Você pode construir um objeto de matriz usando um objeto específico
accelerator_view
. O runtime do C+AMP coletará o objetoaccelerator_view
do objeto de matriz capturado na expressão lambda.
Aceleradores Especiais
Os caminhos do dispositivo de três aceleradores especiais estão disponíveis como propriedades da classe accelerator
:
Membro de Dados accelerator::d irect3d_ref: esse acelerador de thread único usa software na CPU para emular uma placa gráfica genérica. Ele é usado por padrão para depuração, mas não é útil em produção porque é mais lento que os aceleradores de hardware. Além disso, ele está disponível apenas no SDK do DirectX e no SDK do Windows, e é improvável que ele esteja instalado nos computadores de seus clientes. Para obter mais informações, confira Depurando Código GPU.
Membro de Dados accelerator::d irect3d_warp: esse acelerador fornece uma solução de fallback para executar código C++ AMP em CPUs de vários núcleos que usam SSE (Extensões SIMD de Streaming).
Membro de Dados accelerator::cpu_accelerator: você pode usar esse acelerador para configurar matrizes de preparo. Ele não pode executar código C++ AMP. Para obter mais informações, confira o post Matrizes de preparo em C++ AMP no blog Programação paralela no código nativo.
Interoperabilidade
O runtime do C++ AMP dá suporte à interoperabilidade entre a classe accelerator_view
e a interface Direct3D ID3D11Device. O método create_accelerator_view recebe uma interface IUnknown
e retorna um objeto accelerator_view
. O método get_device recebe um objeto accelerator_view
e retorna uma interface IUnknown
.
Confira também
C++ AMP (C++ Accelerated Massive Parallelism)
Depurando código de GPU
Classe accelerator_view