Partilhar via


Usando objetos de acelerador e vista_do_acelerador

Você pode usar as classes accelerator e accelerator_view para especificar o dispositivo ou emulador no qual executar seu código AMP C++. Um sistema pode ter vários dispositivos ou emuladores que diferem por quantidade de memória, suporte de memória compartilhada, suporte de depuração ou suporte de precisão dupla. O C++ Accelerated Massive Parallelism (C++ AMP) 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. 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.

Usando o acelerador padrão

O tempo de execução do 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:

  1. Se o aplicativo estiver sendo executado no modo de depuração, um acelerador que ofereça suporte à depuração.

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

  3. Caso contrário, um dispositivo não emulado.

  4. Caso contrário, o dispositivo que tem a maior quantidade de memória disponível.

  5. Caso contrário, um dispositivo que não esteja conectado à 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 memória compartilhada se for suportado e se suas características de desempenho (largura de banda e latência) forem conhecidas por serem as mesmas da 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 de precisão dupla e o suporte limitado de 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";
}

CPPAMP_DEFAULT_ACCELERATOR variável de ambiente

Você pode definir a CPPAMP_DEFAULT_ACCELERATOR variável de ambiente para especificar o accelerator::device_path do acelerador padrão. O caminho depende do hardware. O código a seguir usa a accelerator::get_all função para recuperar uma lista dos 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 accelerator::get_all método para recuperar uma lista dos aceleradores disponíveis e, em seguida, 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 por accelerator::get_all é o acelerador de CPU. Não é possível executar código no acelerador de CPU. Para filtrar o acelerador da CPU, compare o valor da propriedade device_path do acelerador retornado por accelerator::get_all com o valor de acelerador::cpu_accelerator. Para obter mais informações, consulte a seção "Aceleradores especiais" neste artigo.

Memória compartilhada

A memória compartilhada é a memória 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, ela não pode ser acessada simultaneamente pela CPU e pelo acelerador, e isso causa um comportamento indefinido. A propriedade accelerator supports_cpu_shared_memory retornará true se o acelerador oferecer suporte à memória compartilhada e a propriedade default_cpu_access_type obtiver o access_type padrão para memória alocada no accelerator—por exemplo, matrizesassociadas ao accelerator, ou array_view objetos acessados no accelerator.

O tempo de execução do C++ AMP escolhe automaticamente o melhor padrão access_type 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 aceleradora dedicada (não compartilhada) ao ler da CPU, gravar a partir da CPU ou ambos. Se a memória compartilhada funcionar tão bem quanto a memória dedicada para leitura e gravação da CPU, o tempo de execução assume o padrão access_type_read_write; caso contrário, o tempo de execução escolhe um padrão mais conservador access_type e permite que o aplicativo o substitua se os padrões de acesso à memória dos seus kernels de computação se beneficiarem de um diferente access_type.

O exemplo de código a seguir mostra como determinar se o acelerador padrão oferece 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 a que está associado e não fornece qualquer interface para substituir ou alterar o seu access_type.

Alterando o acelerador padrão

Você pode alterar o acelerador padrão chamando o accelerator::set_default método. 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. Qualquer função subsequente chama para alterar o retorno falsedo acelerador. Se você quiser usar um acelerador diferente em uma chamada para parallel_for_each, leia a seção "Usando 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 um monitor e oferece 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;
}

Usando vários aceleradores

Há duas maneiras de usar vários aceleradores em seu aplicativo:

  • Você pode passar accelerator_view objetos para as chamadas do método parallel_for_each.

  • Você pode construir um objeto array usando um objeto específico accelerator_view . O tempo de execução do C++ AMP vai captar o objeto accelerator_view do array capturado na expressão lambda.

Aceleradores Especiais

Os caminhos de dispositivo de três aceleradores especiais estão disponíveis como propriedades da accelerator classe:

  • accelerator::direct3d_ref Data Member: Este acelerador único de thread usa software na CPU para emular uma placa gráfica comum. Ele é usado por padrão para depuração, mas não é útil na produção porque é mais lento do que os aceleradores de hardware. Além disso, está disponível apenas no SDK do DirectX e no SDK do Windows, e é improvável que seja instalado nos computadores dos seus clientes. Para obter mais informações, consulte Depuração de código GPU.

  • accelerator::direct3d_warp Data Member: Este acelerador fornece uma solução alternativa para executar código AMP C++ em CPUs multi-core que usam Extensões SIMD de Streaming (SSE).

  • accelerator::cpu_accelerator Data Member: Você pode usar este acelerador para configurar arrays de estágio. Ele não pode executar código AMP C++. Para obter mais informações, consulte a postagem Matrizes de preparo em C++ AMP no blog Programação paralela em código nativo.

Interoperabilidade

O runtime C++ AMP suporta a interoperabilidade entre a classe accelerator_view e a interface ID3D11Device do Direct3D. O método create_accelerator_view usa uma IUnknown interface e retorna um accelerator_view objeto. O método get_device pega um accelerator_view objeto e retorna uma IUnknown interface.

Ver também

C++ AMP (paralelismo maciço acelerado em C++)
Depurando código da GPU
accelerator_view Classe