Usar objetos accelerator y accelerator_view

Puede usar las clases accelerator y accelerator_view para especificar el dispositivo o emulador en el que ejecutar el código de C++ AMP. Un sistema puede tener varios dispositivos o emuladores que difieran según la cantidad de memoria, la compatibilidad con la memoria compartida, la compatibilidad con la depuración o la compatibilidad de doble precisión. C++ Accelerated Massive Parallelism (C++ AMP) proporciona API que puede usar para examinar los aceleradores disponibles, establecer uno como predeterminado, especificar varios objetos accelerator_views para varias llamadas a parallel_for_each y realizar tareas de depuración especiales.

Nota:

Los encabezados de C++ AMP están en desuso a partir de la versión 17.0 de Visual Studio 2022. Si se incluyen encabezados AMP, se generarán errores de compilación. Defina _SILENCE_AMP_DEPRECATION_WARNINGS antes de incluir encabezados AMP para silenciar las advertencias.

Uso del acelerador predeterminado

El runtime de C++ AMP elige un acelerador predeterminado, a menos que escriba código para elegir uno específico. El runtime elige el acelerador predeterminado de la siguiente manera:

  1. Si la aplicación se ejecuta en modo de depuración, elegirá un acelerador que admita la depuración.

  2. De lo contrario, el acelerador especificado por la CPPAMP_DEFAULT_ACCELERATOR variable de entorno, si se establece.

  3. De lo contrario, elegirá un dispositivo no emulado.

  4. De lo contrario, elegirá el dispositivo que tenga la mayor cantidad de memoria disponible.

  5. De lo contrario, elegirá un dispositivo que no esté conectado a la pantalla.

Además, el runtime especifica un elemento access_type de access_type_auto para el acelerador predeterminado. Esto significa que el acelerador predeterminado usa memoria compartida si se admite y si se sabe que sus características de rendimiento (ancho de banda y latencia) son iguales que la memoria dedicada (no compartida).

Puede determinar las propiedades del acelerador predeterminado construyendo el acelerador predeterminado y el examen de sus propiedades. En el ejemplo de código siguiente se imprime la ruta de acceso, la cantidad de memoria del acelerador, la compatibilidad con memoria compartida, la compatibilidad de doble precisión y la compatibilidad limitada de doble precisión del acelerador predeterminado.

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";
}

Variable de entorno de CPPAMP_DEFAULT_ACCELERATOR

Puede establecer la variable de entorno de CPPAMP_DEFAULT_ACCELERATOR para especificar accelerator::device_path del acelerador predeterminado. La ruta de acceso depende del hardware. El código siguiente usa la función accelerator::get_all para recuperar una lista de los aceleradores disponibles y, después, muestra la ruta de acceso y las 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";
    }
}

Selección de un acelerador

Para seleccionar un acelerador, use el método accelerator::get_all a fin de recuperar una lista de los aceleradores disponibles y, después, seleccione uno en función de sus propiedades. En este ejemplo se muestra cómo seleccionar el acelerador que tiene más memoria:

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";
}

Nota:

Uno de los aceleradores que devuelve accelerator::get_all es el acelerador de CPU. No se puede ejecutar código en el acelerador de CPU. Para filtrar el acelerador de CPU, compare el valor de la propiedad device_path del acelerador que devuelve accelerator::get_all con el valor de accelerator::cpu_accelerator. Para obtener más información, vea la sección "Aceleradores especiales" en este artículo.

Memoria compartida

La memoria compartida es aquella a la que pueden acceder tanto la CPU como el acelerador. El uso de memoria compartida elimina o reduce considerablemente la sobrecarga de copiar datos entre la CPU y el acelerador. Aunque la memoria se comparte, la CPU y el acelerador no pueden acceder a ella simultáneamente; si lo hacen, se produce un comportamiento indefinido. La propiedad supports_cpu_shared_memory del acelerador devuelve true si el acelerador admite memoria compartida y la propiedad default_cpu_access_type obtiene el valor predeterminado access_type para la memoria asignada en accelerator; por ejemplo, las matrices asociadas a accelerator o los objetos array_view a los que se accede en accelerator.

El runtime de C++ AMP elige automáticamente el mejor valor predeterminado access_type para cada accelerator, pero las características de rendimiento (ancho de banda y latencia) de la memoria compartida pueden ser peores que las de la memoria del acelerador dedicada (no compartida) al leer desde la CPU, escribir desde la CPU o en ambos casos. Si la memoria compartida funciona tan bien como la memoria dedicada para leer y escribir desde la CPU, el runtime tiene como valor predeterminado access_type_read_write; de lo contrario, el runtime elige un valor predeterminado access_type más conservador y permite a la aplicación invalidarlo si los patrones de acceso a la memoria de sus kernels de cálculo se benefician de otro access_type.

En el ejemplo de código siguiente se muestra cómo determinar si el acelerador predeterminado admite memoria compartida y, después, invalida su tipo de acceso predeterminado y crea un elemento accelerator_view a partir de él.

#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;
}

Un accelerator_view objeto siempre refleja el default_cpu_access_type de que accelerator está asociado y no proporciona ninguna interfaz para invalidar ni cambiar su access_type.

Cambio del acelerador predeterminado

Puede cambiar el acelerador predeterminado llamando al método accelerator::set_default. Puede cambiar el acelerador predeterminado solo una vez por cada ejecución de la aplicación y debe cambiarlo antes de que se ejecute cualquier código en la GPU. Las llamadas de función posteriores para cambiar el acelerador devuelven false. Si quiere usar un acelerador diferente en una llamada a parallel_for_each, lea la sección "Uso de varios aceleradores" de este artículo. En el ejemplo de código siguiente se establece el acelerador predeterminado en uno que no se emula, no está conectado a una pantalla y es compatible con la doble precisión.

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;
}

Uso de varios aceleradores

Hay dos maneras de usar varios aceleradores en la aplicación:

  • Puede pasar objetos accelerator_view a las llamadas al método parallel_for_each.

  • Puede construir un objeto array mediante un objeto accelerator_view específico. El runtime de C+AMP recogerá el objeto accelerator_view del objeto array capturado en la expresión lambda.

Aceleradores especiales

Las rutas de acceso de dispositivo de tres aceleradores especiales están disponibles como propiedades de la clase accelerator:

  • Miembro de datos accelerator::direct3d_ref: este acelerador de un solo subproceso usa software en la CPU para emular una tarjeta gráfica genérica. Se usa de manera predeterminada para la depuración, pero no es útil en producción porque es más lento que los aceleradores de hardware. Además, solo está disponible en el SDK de DirectX y Windows SDK, y es poco probable que se instale en los equipos de los clientes. Para obtener más información, consulte Depurar código GPU.

  • Miembro de datos accelerator::direct3d_warp: este acelerador proporciona una solución de reserva para ejecutar código de C++ AMP en CPU de varios núcleos que usen Extensiones SIMD de streaming (SSE).

  • Miembro de datos accelerator::cpu_accelerator: puede usar este acelerador para configurar matrices de almacenamiento provisional. No se puede ejecutar código de C++ AMP. Para obtener más información, vea la publicación Almacenamiento provisional de matrices en C++ AMP en el blog de programación en paralelo en código nativo.

Interoperabilidad

El runtime de C++ AMP admite la interoperabilidad entre la clase accelerator_view y la interfaz ID3D11Device de Direct3D. El método create_accelerator_view toma una interfaz IUnknown y devuelve un objeto accelerator_view. El método get_device toma un objeto accelerator_view y devuelve una interfaz IUnknown.

Consulte también

C++ AMP (C++ Accelerated Massive Parallelism)
Depurar código de GPU
accelerator_view (clase)