Compartir a través de


Información general sobre C++ AMP

++ Accelerated Massive Parallelism (C++ AMP) acelera la ejecución del código C++ aprovechándose del hardware de paralelismo de datos, por ejemplo una unidad de procesamiento gráfico (GPU), en una tarjeta gráfica discreta.Al utilizar C++ AMP, se pueden codificar algoritmos para datos multidimensionales de modo que se acelere la ejecución mediante el uso del paralelismo en hardware heterogéneo.El modelo de programación C++ AMP incluye matrices multidimensionales, indexado, transferencia de memoria, mosaicos y una biblioteca de funciones matemáticas.Se pueden utilizar extensiones de lenguaje de C++ AMP para controlar cómo pasan los datos de la CPU a la GPU y viceversa con objeto de que se mejore el rendimiento.

Requisitos del sistema

  • Windows 7, Windows 8, Windows Server 2008 R2 o Windows Server 2012

  • Nivel 11,0 de la característica de DirectX 11 o hardware posterior

  • Para depurar en el emulador de software, se requiere Windows 8 o Windows Server 2012.Para depurar en el hardware, debe instalar controladores para su tarjeta gráfica.Para obtener más información, vea Depurar código de GPU.

Introduction

Los siguientes dos ejemplos muestran los componentes principales de C++ AMP.Suponga que desea agregar los elementos correspondientes de dos matrices unidimensionales.Por ejemplo, quizás desee añadir {1, 2, 3, 4, 5} y {6, 7, 8, 9, 10} para obtener {7, 9, 11, 13, 15}.Sin utilizar C++ AMP, se puede escribir el siguiente código para agregar los números y mostrar los resultados.

#include <iostream>

void StandardMethod() {

    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[5];

    for (int idx = 0; idx < 5; idx++)
    {
        sumCPP[idx] = aCPP[idx] + bCPP[idx];
    }

    for (int idx = 0; idx < 5; idx++)
    {
        std::cout << sumCPP[idx] << "\n";
    }
}

Las partes importantes de código son las siguientes:

  • Datos: Los datos se componen de tres matrices.Todos tienen el mismo rango (uno) y la misma longitud (cinco).

  • Iteración: El primer bucle de for proporciona un mecanismo para recorrer en iteración los elementos de las matrices.El código que se desea ejecutar para calcular las sumas se encuentra en el primer bloque de for.

  • Índice: la variable de idx tiene acceso a los elementos individuales de las matrices.

Mediante el AMP de C++, se puede escribir el siguiente código.

#include <amp.h>
#include <iostream>
using namespace concurrency;

const int size = 5;

void CppAmpMethod() {
    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[size];
    
    // Create C++ AMP objects.
    array_view<const int, 1> a(size, aCPP);
    array_view<const int, 1> b(size, bCPP);
    array_view<int, 1> sum(size, sumCPP);
    sum.discard_data();

    parallel_for_each( 
        // Define the compute domain, which is the set of threads that are created.
        sum.extent, 
        // Define the code to run on each thread on the accelerator.
        [=](index<1> idx) restrict(amp)
    {
        sum[idx] = a[idx] + b[idx];
    }
    );

    // Print the results. The expected output is "7, 9, 11, 13, 15".
    for (int i = 0; i < size; i++) {
        std::cout << sum[i] << "\n";
    }
}

Los mismos elementos básicos están presentes, pero se utilizan las construcciones de C++ AMP:

  • Datos: Se utilizan las matrices de C++ para construir tres objetos de array_view de C++ AMP.Se proporcionan cuatro valores para construir un objeto array_view: los valores de datos, el rango, el tipo de elemento, y la longitud del objeto array_view en cada dimensión.El rango y el tipo se pasan como parámetros de tipo.Los datos y la longitud se pasan como parámetros del constructor.En este ejemplo, la matriz de C++ que se pasa al constructor es unidimensional.Se usan el rango y la longitud para construir la forma rectangular de los datos en el objeto array_view, y los valores de datos se utilizan para rellenar la matriz.la biblioteca de runtime también incluye array (Clase), que tiene una interfaz similar a la clase de array_view y que se describe más adelante en este artículo.

  • Iteración: parallel_for_each (Función) (C++ AMP) proporciona un mecanismo para recorrer en iteración los datos, o el dominio del cálculo.En este ejemplo, el dominio del cálculo viene especificado por sum.extent.El código que se desea ejecutar se encuentra en una expresión lambda, o función de kernel.restrict(amp) indica que sólo se utiliza el subconjunto del lenguaje C++ que el C++ AMP puede acelerar.

  • Índice: La variable de index (Clase) , idx, se declara con rango de uno para correspoder al rango del objeto array_view.Utilizando el índice,se puede tener acceso a los elementos individuales de los objetos de array_view.

Dando forma e indexando datos: índice y extensión

Se deben definir los valores de los datos y declarar la forma de los datos antes de poder ejecutar el código del kernel.Todos los datos se definen como una matriz (rectangular), y se puede definir la matriz para que tenga cualquier rango (número de dimensiones).Los datos pueden tener cualquier tamaño en cualquiera de las dimensiones.

Hh265136.collapse_all(es-es,VS.110).gifÍndice (Clase)

index (Clase) especifica una ubicación en el objeto array o array_view encapsulando el desplazamiento desde el origen en cada dimensión en un objeto.Cuando se obtiene acceso a una ubicación en la matriz, se pasa un objeto index al operador de indexación, [], en lugar de una lista de índices enteros.Se puede tener acceso a los elementos de cada dimensión a través de array::operator() (Operador) o de array_view::operator() (Operador).

El siguiente ejemplo crea un índice unidimensional que especifica el tercer elemento de un objeto unidimensional array_view.El índice se utiliza para imprimir el tercer elemento en el objeto array_view.El resultado es 3.

    
int aCPP[] = {1, 2, 3, 4, 5};
array_view<int, 1> a(5, aCPP);
index<1> idx(2);
std::cout << a[idx] << "\n";  
// Output: 3

El ejemplo siguiente crea un índice bidimensional que especifica el elemento en el cual la fila = 1 y la columna = 2 en un objeto bidimensional array_view.El primer parámetro del constructor index es el componente de la fila, y el segundo parámetro es el componente de la columna.El resultado es 6.

int aCPP[] = {1, 2, 3,
              4, 5, 6};
array_view<int, 2> a(2, 3, aCPP);
index<2> idx(1, 2);
std::cout << a[idx] << "\n";
// Output: 6

El ejemplo siguiente crea un índice tridimensional que especifica el elemento en el que la profundidad = 0, el rango = 1, y la columna = 3 en un objeto tridimensional de array_view.Observe que el primer parámetro es la profundidad, el segundo parámetro es la fila y el tercer parámetro es la columna.El resultado es 8.

int aCPP[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

array_view<int, 3> a(2, 3, 4, aCPP); 
// Specifies the element at 3, 1, 0.
index<3> idx(0, 1, 3);               
std::cout << a[idx] << "\n";

// Output: 8

Hh265136.collapse_all(es-es,VS.110).gifLa clase extent

extent (Clase) (C++ AMP) especifica la longitud de los datos en cada dimensión del objeto array o de array_view.Se puede crear una extensión y utilizarla para crear un objeto array o un objeto array_view.También se puede recuperar la extensión de array o de un objeto existente de array_view.El ejemplo siguiente imprime la longitud de la extensión en cada dimensión de un objeto array_view.

int aCPP[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// There are 3 rows and 4 columns, and the depth is two.
array_view<int, 3> a(2, 3, 4, aCPP); 
std::cout << "The number of columns is " << a.extent[2] << "\n";
std::cout << "The number of rows is " << a.extent[1] << "\n";
std::cout << "The depth is " << a.extent[0]<< "\n";
std::cout << "Length in most significant dimension is " << a.extent[0] << "\n";

El siguiente ejemplo crea un objeto array_view que tiene las mismas dimensiones que el objeto del ejemplo anterior, pero este ejemplo utiliza un objeto extent en lugar de utilizar parámetros explícitos en el constructor de array_view.

int aCPP[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
extent<3> e(2, 3, 4);
array_view<int, 3> a(e, aCPP);
std::cout << "The number of columns is " << a.extent[2] << "\n";
std::cout << "The number of rows is " << a.extent[1] << "\n";
std::cout << "The depth is " << a.extent[0] << "\n";

Mover datos al Acelerador: matriz y array_view

En la biblioteca de runtime se definen dos contenedores de datos que se utilizan para mover datos al acelerador.Estos son array (Clase) y array_view (Clase).La clase array es una clase contenedora que crea una copia profunda de datos cuando se construye el objeto.La clase array_view es una clase envoltorio que copia los datos cuando la función de kernel tiene acceso a los datos.Cuando los datos son necesarios en el dispositivo de origen los datos se vuelven a copiar.

Hh265136.collapse_all(es-es,VS.110).gifLa clase array

Cuando se construye un objeto array, se crea en el acelerador una copia en profundidad de los datos si se utiliza un constructor que incluya un puntero al conjunto de los datos.La función kernel modifica la copia en el acelerador.Cuando la ejecución de la función kernel finaliza, se deben copiar los datos en la estructura de la fuente de datos.El ejemplo siguiente multiplica cada elemento de un vector por 10.Una vez finalizada la función kernel, el operador de conversión de vectores se utiliza para copiar los datos en el objeto vector.

std::vector<int> data(5);
for (int count = 0; count < 5; count++) 
{
     data[count] = count;
}

array<int, 1> a(5, data.begin(), data.end());

parallel_for_each(
    a.extent, 
    [=, &a](index<1> idx) restrict(amp)
    {
        a[idx] = a[idx] * 10;
    }
);

data = a;
for (int i = 0; i < 5; i++) 
{
    std::cout << data[i] << "\n";
}

Hh265136.collapse_all(es-es,VS.110).gifLa clase array_view

array_view tiene casi los mismos miembros que la clase de array, pero el comportamiento subyacente no es el mismo.Los datos que se pasan al constructor array_view no se replican en la GPU como se replican con un constructor array.De hecho,los datos se copian al acelerador cuando se ejecuta la función kernel.Por consiguiente, si se crean dos objetos array_view que utilizan los mismos datos, ambos objetos array_view harán referencia al mismo espacio de memoria.Si lo hace así, tendrá que sincronizar cualquier acceso multiproceso.La ventaja principal de utilizar la clase array_view es que los datos se mueven sólo si es necesario.

Hh265136.collapse_all(es-es,VS.110).gifComparación de array y array_view

La tabla siguiente resume las similitudes y diferencias que hay entre array y las clases array_view.

Descripción

La clase array

La clase array_view

Cuando se determina el rango.

En tiempo de compilación.

En tiempo de compilación.

Cuando se determina la extensión

En tiempo de ejecución

En tiempo de ejecución

Forma

Rectangular.

Rectangular.

Almacenamiento de datos

Es un contenedor de datos.

Es un envoltorio de datos.

Copiar

Copia explícita y en profundidad en la definición.

Copia implícita cuando se tiene acceso a la misma con la función kernel.

Recuperación de datos

Copiar los datos de la matriz de nuevo a un objeto CPU subproceso.

Mediante el acceso directo array_view o llamando a array_view::synchronize (Método) para continuar teniendo acceso a los datos en el contenedor original.

Ejecutando código sobre los datos: parallel_for_each

La función parallel_for_each define el código que se desea ejecutar en el acelerador contra los datos del objeto array o de array_view.Considere el siguiente código proveniente de la introducción de este tema.

#include <amp.h>
#include <iostream>
using namespace concurrency;

void AddArrays() {
    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[5] = {0, 0, 0, 0, 0};

    array_view<int, 1> a(5, aCPP);
    array_view<int, 1> b(5, bCPP);
    array_view<int, 1> sum(5, sumCPP);

    parallel_for_each(
        sum.extent, 
        [=](index<1> idx) restrict(amp)
        {
            sum[idx] = a[idx] + b[idx];
        }
    );

    for (int i = 0; i < 5; i++) {
        std::cout << sum[i] << "\n";
    }
}

El método parallel_for_each toma dos argumentos, un dominio de cálculo y una expresión lambda.

El dominio del cálculo es un objeto extent o un objeto tiled_extent que define el conjunto de subprocesos que hay que crear para la ejecución en paralelo.Se genera un subproceso para cada elemento en el dominio del cálculo.En este caso, el objeto extent es unidimensional y tiene cinco elementos.Por consiguiente, se inician cinco subprocesos.

La expresión lambda define el código que hay que ejecutar en cada subproceso.La cláusula de captura, [=], especifica que el cuerpo de la expresión lambda tiene acceso a todas las variables capturadas por valor, que en este caso son a , b, y sum.En este ejemplo, la lista de parámetros crea una variable unidimensional index denominada idx.El valor de idx[0] es 0 en el primer subproceso y aumenta en uno en cada subproceso siguiente.restrict(amp) indica que sólo se utiliza el subconjunto del lenguaje C++ que el C++ AMP puede acelerar.Las limitaciones de las funciones con el modificador limitado se describen en Cláusula de restricción (AMP de C++).Para obtener más información, vea Sintaxis de la expresión lambda.

La expresión lambda puede incluir código para ejecutar o puede llamar a una función independiente del kernel.La función kernel debe incluir el modificador de restrict(amp).El siguiente ejemplo es equivalente al ejemplo anterior, pero llama a una función kernel independiente.

#include <amp.h>
#include <iostream>
using namespace concurrency;

void AddElements(index<1> idx, array_view<int, 1> sum, array_view<int, 1> a, array_view<int, 1> b) restrict(amp)
{
    sum[idx] = a[idx] + b[idx];
}


void AddArraysWithFunction() {

    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[5] = {0, 0, 0, 0, 0};

    array_view<int, 1> a(5, aCPP);
    array_view<int, 1> b(5, bCPP);
    array_view<int, 1> sum(5, sumCPP);

    parallel_for_each(
        sum.extent, 
        [=](index<1> idx) restrict(amp)
        {
            AddElements(idx, sum, a, b);
        }
    );

    for (int i = 0; i < 5; i++) {
        std::cout << sum[i] << "\n";
    }
}

Acelerar código: Teselas y Barreras

Se puede aprovechar la aceleración adicional utilizando el teselado.El teselado divide los subprocesos en subconjuntos rectangulares iguales o teselas.Se determina el tamaño apropiado de la tesela según el conjunto de datos y el algoritmo que se está escribiendo.Para cada subproceso, se tiene acceso a la ubicación global de un elemento de datos en relación con todo array o array_view y el acceso a la ubicación local en relación con la tesela.Al utilizar el valor de índice local se simplifica el código porque no hay que escribir el código para convertir valores de índices globales a locales.Para utilizar el teselado, llame a extent::tile (Método) en el dominio del cálculo en el método parallel_for_each , y utilice un objeto tiled_index en la expresión lambda.

En las aplicaciones típicas, los elementos de una tesela están en cierto modo relacionados, y el código tiene que tener acceso y poder seguir los valores a través del mosaico.Utilice la palabra clave palabra clave tile_static y tile_barrier::wait (Método) para lograrlo.Una variable que tiene la palabra clave tile_static tiene un ámbito a través de una tesela completa, y por cada tesela se crea una instancia de la variable.Debe controlar la sincronización del acceso del subproceso de las teselas a la variable.tile_barrier::wait (Método) detiene la ejecución del subproceso actual hasta que todos los subprocesos de la tesela hayan alcanzado la llamada a tile_barrier::wait.De forma que se puedan acumular los valores a través de la tesela mediante las variables tile_static.A continuación se puede finalizar cualquier cálculo que requiera acceso a todos los valores.

El diagrama siguiente representa una matriz bidimensional de los datos de muestreo que se organiza en teselas.

Valores de índice en una extensión en mosaico

El siguiente ejemplo de código utiliza los datos de muestreo del diagrama anterior.El código reemplaza cada valor de la tesela por el promedio de los valores de la tesela.

// Sample data:
int sampledata[] = {
    2, 2, 9, 7, 1, 4,
    4, 4, 8, 8, 3, 4,
    1, 5, 1, 2, 5, 2,
    6, 8, 3, 2, 7, 2};

// The tiles:
// 2 2    9 7    1 4
// 4 4    8 8    3 4
//
// 1 5    1 2    5 2
// 6 8    3 2    7 2

// Averages:
int averagedata[] = { 
    0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 
};

array_view<int, 2> sample(4, 6, sampledata);
array_view<int, 2> average(4, 6, averagedata);

parallel_for_each(
    // Create threads for sample.extent and divide the extent into 2 x 2 tiles.
    sample.extent.tile<2,2>(),
    [=](tiled_index<2,2> idx) restrict(amp)
    {
        // Create a 2 x 2 array to hold the values in this tile.
        tile_static int nums[2][2];
        // Copy the values for the tile into the 2 x 2 array.
        nums[idx.local[1]][idx.local[0]] = sample[idx.global];
        // When all the threads have executed and the 2 x 2 array is complete, find the average.
        idx.barrier.wait();
        int sum = nums[0][0] + nums[0][1] + nums[1][0] + nums[1][1];
        // Copy the average into the array_view.
        average[idx.global] = sum / 4;
      }
);

for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 6; j++) {
        std::cout << average(i,j) << " ";
    }
    std::cout << "\n";
}

// Output:
// 3 3 8 8 3 3
// 3 3 8 8 3 3
// 5 5 2 2 4 4
// 5 5 2 2 4 4

Bibliotecas matemáticas

C++ AMP incluye dos bibliotecas matemáticas.La biblioteca de doble precisión en Concurrency::precise_math (Espacio de nombres) proporciona compatibilidad para las funciones de doble precisión.También proporciona compatibilidad a las funciones de precisión simple, aunque todavía se requiere la compatibilidad de doble precisión en hardware.Cumple la especificación C99 (ISO/IEC 9899).El acelerador debe admitir doble precisión completa.Se puede determinar si lo hace comprobando el valor de accelerator::supports_double_precision (Miembro de datos).La librería matemática rápida, en Concurrency::fast_math (Espacio de nombres), contiene otro conjunto de funciones matemáticas.Estas funciones, que solo admiten los operandos float, se ejecutan más rápidamente pero no son tan exactas como las de la librería matemática de doble precisión.Las funciones se incluyen en el archivo de encabezado <amp_math.h> y todos se declaran con restrict(amp).Las funciones del archivo de encabezado de <cmath> se importan a los espacios de nombres de fast_math y de precise_math.La palabra clave restrict se utiliza para distinguir la versión de <cmath> y la versión de C++ AMP. El siguiente código calcula el logaritmo en base 10, con el método rápido, de cada valor que esté en el dominio del cálculo.

#include <amp.h>
#include <amp_math.h>
#include <iostream>
using namespace concurrency;


void MathExample() {

    double numbers[] = { 1.0, 10.0, 60.0, 100.0, 600.0, 1000.0 };
    array_view<double, 1> logs(6, numbers);

    parallel_for_each(
        logs.extent,
         [=] (index<1> idx) restrict(amp) {
            logs[idx] = concurrency::fast_math::log10(logs[idx]);
        }
    );

    for (int i = 0; i < 6; i++) {
        std::cout << logs[i] << "\n";
    }
}

Biblioteca de gráficos

C++ AMP incluye una biblioteca de gráficos que está diseñada para la programación de gráficos acelerados.Esta biblioteca se utiliza sólo en los dispositivos que admiten funcionalidad de gráficos nativos.Los métodos están en Concurrency::graphics (Espacio de nombres) y están incluidos en el archivo de encabezado de <amp_graphics.h>.Los componentes clave de la biblioteca de gráficos son:

  • texture (Clase): Se puede utilizar la clase textura para crear texturas desde la memoria o desde un archivo.Las texturas se parecen a las matrices porque contienen datos, y se parecen a los contenedores de la biblioteca de plantilas estandar(STL) en lo que se refiere la asignación y construcción de copia.Para obtener más información, vea Contenedores STL.Los parámetros de la plantilla para la clase texture son el tipo de elemento y el rango.El rango puede ser 1, 2, 3.El tipo de elemento puede ser uno de los tipos de vector corto que se describen más adelante en este artículo.

  • writeonly_texture_view (Clase): Proporciona acceso sólo de escritura a cualquier textura.

  • Biblioteca corta de vector: Define un conjunto de tipos de vector corto de longitud 2, 3 y 4, basados en int, uint, float, double, estándar, o unorm.

aplicaciones de Tienda Windows

Como otras bibliotecas de C++, puede utilizar C++ AMP en sus aplicaciones de Tienda Windows.Estos artículos describen cómo incluir código de C++ AMP en aplicaciones que se crean con C++, C#, Visual Basic, o JavaScript:

C++ AMP y Visualizador de simultaneidad

El visualizador de simultaneidad incluye compatibilidad para analizar el rendimiento del código C++ AMP.Estos artículos describen estas características:

Recomendaciones acerca del rendimiento

El módulo y la división de enteros sin signo tienen un rendimiento mucho mejor que el módulo y la división de enteros con signo.Se recomienda usar enteros sin signo cuando sea posible.

Vea también

Referencia

Sintaxis de la expresión lambda

Otros recursos

C++ AMP (C++ Accelerated Massive Parallelism)

Referencia (C++ AMP)

Programación paralela en el blog de código nativo