Compartilhar via


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:

  1. Se o aplicativo estiver em execução no modo de depuração, um acelerador que dê suporte à depuração.

  2. Caso contrário, o acelerador especificado pela CPPAMP_DEFAULT_ACCELERATOR variável de ambiente, se estiver definido.

  3. Senão, um dispositivo não emulado.

  4. Ou então, o dispositivo que tenha a maior quantidade de memória disponível.

  5. 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 objeto accelerator_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