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 seu código C++ AMP. Um sistema pode ter vários dispositivos ou emuladores que diferem pela quantidade de memória, suporte de memória compartilhada, suporte de depuração ou o suporte de precisão dupla. O C++ Accelerated Massive Parallelism (C++ AMP) fornece APIs que você pode usar para examinar os aceleradores disponíveis, para definir um como padrão, para especificar várias accelerator_views para chamadas múltiplas ao parallel_for_each, e para executar tarefas de depuração especial.
Usando o acelerador padrão
O tempo de execução de C++ AMP escolhe um acelerador padrão, a menos que você escreva código para escolher um específico. O tempo de execução escolhe o acelerador padrão da seguinte maneira:
Se o aplicativo estiver executado no modo de depuração, um acelerador que oferece suporte à depuração.
Caso contrário, o acelerador que é especificado pela variável de ambiente CPPAMP_DEFAULT_ACCELERATOR, se estiver definido.
Caso contrário, um dispositivo não emulado.
Caso contrário, o dispositivo que tem a maior quantidade de memória disponível.
Caso contrário, um dispositivo que não está anexado à tela.
Além disso, o tempo de execução especifica um access_type de access_type_auto para o acelerador padrão. Isso significa que o acelerador padrão usa a memória compartilhada se ela tiver suporte e se suas características de desempenho (largura de banda e latência) estiverem iguais à memória dedicada (não compartilhada).
Você pode determinar as propriedades dos aceleradores padrão construindo o acelerador padrão e examinando suas propriedades. O exemplo de código a seguir mostra o caminho, a quantidade de memória do acelerador, o suporte de memória compartilhada, o suporte de precisão dupla e o suporte de precisão dupla limitado 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 é dependente de hardware. O código a seguir usa a função accelerator::get_all para recuperar uma lista de aceleradores disponíveis e, em seguida, 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";
}
}
Selecionando um acelerador
Para selecionar um acelerador, use o método accelerator::get_all para recuperar uma lista de aceleradores disponíveis e então selecione um com base em suas propriedades. Este exemplo mostra como escolher o acelerador que tem a maior 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";
}
Dica
Um dos aceleradores que são retornados por accelerator::get_all é o acelerador de CPU.Você não pode executar o código no acelerador CPU.Para filtrar o acelerador de CPU, compare o valor da propriedade device_path do acelerador que é retornado por accelerator::get_all com o valor de accelerator::cpu_accelerator.Para obter mais informações, consulte a seção “Aceleradores Especiais” neste artigo.
Memória compartilhada
A memória compartilhada é a que pode ser acessada pela CPU e pelo acelerador. O uso de memória compartilhada elimina ou reduz significativamente a sobrecarga de copiar dados entre a CPU e o acelerador. Embora a memória seja compartilhada, não pode ser acessada simultaneamente pela CPU e pelo acelerador, e fazendo isso provocaria um comportamento indefinido. A propriedade do acelerador supports_cpu_shared_memory retorna true se o acelerador oferece suporte à memória compartilhada, e a propriedade default_cpu_access_type obtém o access_type padrão da memória alocada no accelerator, por exemplo, o array associada ao accelerator ou a objetos array_view acessados no accelerator.
O tempo de execução de 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 do acelerador (não compartilhada) dedicada ao ler da CPU, gravar da CPU ou ambos. Se a memória compartilhada funcionar tão bem como a memória dedicada para ler e gravar da CPU, o tempo de execução terá como padrão access_type_read_write; caso contrário, o tempo de execução escolherá um access_type padrão mais conservador e permitirá que o aplicativo o substitua caso os padrões de acesso à memória de seus kernels de computação se beneficiem de um access_typediferente.
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 com o qual está associado, e não fornece nenhuma interface para substituir ou alterar seu access_type.
Alterando o acelerador padrão
Você pode alterar o acelerador padrão chamando o método accelerator::set_default. É possível alterar o acelerador padrão apenas uma vez por execução de aplicativo e você deve alterá-lo antes que qualquer código seja executado no GPU. Quaisquer chamadas de função subsequentes para alterar o acelerador retornam false. Se você deseja usar um acelerador diferente em uma chamada para parallel_for_each leia a seção “Usando múltiplos aceleradores” neste artigo. O exemplo de código a seguir define o acelerador padrão para um que não é emulado, não é conectado a uma exibição e oferece suporte de 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;
}
Usando múltiplos aceleradores
Há duas maneiras de usar múltiplos aceleradores em seu aplicativo:
Você pode passar objetos accelerator_view para chamadas ao método parallel_for_each.
Você pode criar um objeto array usando um objeto específico accelerator_view. O tempo de execução C++ AMP pegará o objeto accelerator_view do objeto capturado array na expressão lambda.
Aceleradores especiais
Os caminhos de dispositivo dos três aceleradores especiais estão disponíveis como propriedades da classe accelerator:
Membro de dados accelerator::direct3d_ref: Este acelerador de thread único usa software no CPU para emular uma placa gráfica genérica. Usado por padrão para a depuração, mas não é útil para a produção porque é mais lento do que os aceleradores de hardware. Além de isso, está disponível somente em DirectX SDK e o Windows SDK, e é improvável que esteja instalado nos computadores dos seus clientes. Para obter mais informações, consulte Depurando código de GPU.
Membro de dados accelerator::direct3d_warp: Este acelerador fornece uma solução de retorno para executar o código de C++ AMP em CPUs multi-core que usam extensões Streaming SIMD (SSE, em inglês).
Membro de dados accelerator::cpu_accelerator: Você pode usar esse acelerador para configurar matrizes de teste. Não pode executar o código de C++ AMP. Para obter mais informações, consultea postagem Preparando matrizes no C++ AMP no blog Programação em paralelo em código nativo.
Interoperabilidade
O tempo de execução de C++ AMP suporta a interoperabilidade entre a classe accelerator_view e a interface do Direct3D ID3D11Device. O método create_accelerator_view usa uma interface IUnknown e retorna um objeto accelerator_view. O método get_device utiliza um objeto accelerator_view e retorna uma interface IUknown.