Este artículo proviene de un motor de traducción automática.

C++ AMP

Introducción a la organización en mosaicos en C++ AMP

Daniel Moth

Descargar el ejemplo de código

Este artículo describe una tecnología preliminar llamada AMP de C++ que enviará con Visual Studio 11.Toda la información está sujeta a cambios.

Visual Studio 11 trae soporte para computación heterogéneos a la corriente mediante C++ acelerado masivo paralelismo (AMP de C++).Introduje AMP de C++ en otro artículo en este asunto, que considero necesario para lectura de este artículo.Así que si no has leído aún, hágalo, comenzando en p.28.

Antes de presentarle la GPU programación técnica de optimización del performance denominada "mosaico", recuerda que en el artículo anterior, aprendió sobre el índice <N>, medida <N>, array_view < T, N >, restrict(amp) y parallel_for_each.Eres capaz de usar la API de AMP de C++ para implementar sus propios algoritmos paralelos de datos, tales como la multiplicación de la matriz compartida en el artículo anterior y repite aquí en figura 1.

Figura 1 matrices, modelo Simple

1  void MatMul(vector<int>& vC, const vector<int>& vA,
     const vector<int>& vB, int M, int N, int W )
2  {
3    array_view<const int, 2> a(M, W, vA), b(W, N, vB);
4    array_view<int, 2> c(M, N, vC);
5    c.discard_data();
6    parallel_for_each(c.extent, [=](index<2> idx) restrict(amp)
7    {
8      int row = idx[0]; int col = idx[1];
9      int sum = 0;
10     for(int i = 0; i < b.extent[0]; i++)
11       sum += a(row, i) * b(i, col);
12     c[idx] = sum;
13   });
14   c.synchronize();
15 }

Si usted no está familiarizado con la multiplicación de matrices, esto es una referencia en línea: bit.ly/HiuP.

En este artículo, voy presentarles teselación, cuál es la técnica más común de optimización al programar la GPU. La única razón para introducir la segmentación en su algoritmo es el nivel de rendimiento que potencialmente puede lograr a través de la reutilización de datos y mejores patrones de acceso a la memoria adicional. Teselación permite aprovechar mejor la jerarquía de memoria de la GPU en un nivel menor que lo que se puede hacer con el modelo simple, que sabe desde el artículo introductorio.

Hay dos pasos para mosaico. En primer lugar, explícitamente debe mosaico el cómputo (este primer paso ocurre para usted bajo las cubiertas con el modelo simple); en segundo lugar, debe tomar ventaja de la memoria de tile_static (este segundo paso no ocurre para usted automáticamente, por lo que tienes que hacer explícitamente uno mismo).

tiled_extent clase

Usted sabe que parallel_for_each acepta un objeto de la medida como su primer argumento. La medida describe el dominio de la informática — es decir, cuántos hilos (tamaño) y de qué forma (dimensiones) ejecutará el cálculo. Considere los siguientes ejemplos de dos grado:

extent<1> e(12);   // 12 threads in a single dimension
extent<2> ee(2,6); // 12 threads in two-dimensional space

Hay una sobrecarga de mosaico para parallel_for_each que acepta un argumento tiled_extent. Un tiled_extent describe cómo romper la medida original en azulejos de igual tamaños. Puede obtener una tiled_extent de hasta un rango de sólo tres. Si tiene dimensiones más que eso, necesita seguir con el modelo simple o refactorizar el código. El número total de subprocesos en un azulejo no puede superar los 1.024.

La forma más sencilla de obtener un objeto tiled_extent es llamando al método sin parámetros azulejo con plantilla de medida, que devuelve un tiled_extent para el objeto de la medida. Para los dos ejemplos anteriores de medida pueden escribir algo como la siguiente para obtener objetos tiled_extent correspondientes:

tiled_extent<6> t_e = e.tile<6>();        // e.rank==t_e.rank
tiled_extent<2,2> t_ee = ee.tile<2, 2>(); // ee.rank==t_ee.rank

Para una representación pictórica vea figura 2, que muestra cómo teselación punto divide los datos en subconjuntos más pequeños, que en C++ AMP son llamados mosaicos.

tiled_extent Is an Extent Partitioned into Tiles
Figura 2 tiled_extent es una medida dividido en azulejos

Los números que desea pasar como argumentos de plantilla deben ser conocidos en tiempo de compilación. Deben dividir equitativamente las dimensiones de alcance global pasadas a la parallel_for_each:

  • e [0] = 12 es divisible por t_e.tile_extent[0]=6
  • EE [0] = 2 es divisible por t_ee.tile_extent[0]=2
  • EE [1] = 6 es divisible por t_ee.tile_extent[1]=2

Por razones de rendimiento, el azulejo de menor tamaño en menos -­dimensión significativa debe ser de 16. Ajuste el tamaño de azulejo puede producir resultados de rendimiento mejor o peor, dependiendo del hardware que se utiliza. Mi Consejo es, no intente hacer eso — en su lugar, elija un número que realiza igualmente bien a través de un amplio conjunto de hardware, empezando por múltiplos de 16 e incluso de la misma.

tiled_index clase

Ustedes saben que en la llamada parallel_for_each que se pasa en una lambda con tu código como segundo argumento. El código recibe un objeto index, y se puede pensar el objeto index como el identificador del subproceso. Por ejemplo:

parallel_for_each(my_extent,[=](index<2> idx) restrict(amp){
  my_array_view[idx] = ...
});

Cuando teja la medida en que se pasa a la parallel_for_each, la firma de la lambda que pasan en acepta un tiled_index. Una tiled_index consta de cuatro objetos de índice. Por ejemplo, usted puede obtener con el objeto de índice que estaban esperando mediante una propiedad del objeto tiled_index, como sigue:

parallel_for_each(my_extent.tile<2,2>(),[=](tiled_index<2,2> t_idx) restrict(amp){
  my_array_view[t_idx.global] = ...
});

Al escribir algoritmos de mosaico, probablemente desea conocer el índice local en el azulejo (no sólo el índice global en el dominio de cómputo global). Puede obtener ese objeto de índice a través de la propiedad local de tiled_index. Para algunos algoritmos es útil conocer el índice de la teja en relación con los otros azulejos en la computación y también el índice global de origen del mosaico. Puede acceder a esos objetos de índice a través de las propiedades de teja y tile_origin de tiled_index.

Usando la medida bidimensional del ejemplo anterior (punto <2> (2,6) .tile <2,2> ()), se puede ver en figura 3 los valores de las propiedades mencionadas tiled_index para la Plaza resaltada.

tiled_index Properties Returning Index Objects
Figura 3 tiled_index propiedades devolver objetos Index

Matriz multiplicación Revisited con mosaico parallel_for_each

En figura 1 vio una implementación de multiplicación de la matriz utilizando el modelo simple de C++ AMP. ¿Cómo cambiaría ese algoritmo por lo que puede ser explícitamente mosaico con lo aprendido hasta ahora (con tiled_extent y tiled_index)?

La solución se muestra en la figura 4, con los cambios de la lista anterior en negrita.

Figura 4 matrices, mosaico, paso 1 sin usar tile_static

3  array_view<const int, 2> a(M, W, vA), b(W, N, vB);
4  array_view<int, 2> c(M, N, vC);
5  c.discard_data();
6  parallel_for_each(c.extent.tile<16,16>(),
     [=](tiled_index<16,16> t_idx) restrict(amp)
7  {
8  int row = t_idx.global[0]; int col = t_idx.global[1];
9  int sum = 0;
10 for(int i = 0; i < b.extent[0]; i++)
11   sum += a(row, i) * b(i, col);
12 c[t_idx.global] = sum;
13 });
14 c.synchronize();

En línea 6 que invoca el método de azulejos sobre la medida, elegir un tamaño de azulejo (16 x 16 en este ejemplo), y he cambiado la lambda para aceptar una tiled_index con argumentos de plantilla de tamaño de azulejo de coincidencia. En el cuerpo de lambda I sustituye todas las apariciones de idx con t_idx.global (líneas 8 y 12). Esta conversión mecanicista es lo que debe hacer en primer lugar para todos los algoritmos de C++ AMP cuando decida les mosaico. Es la primera, pero no la única: paso en el camino desde el modelo simple para el modelo de mosaico.

Una cosa es observar como se analizó anteriormente es que, con este cambio, que necesita para asegurar el tamaño del mosaico elegido uniformemente divide las dimensiones de alcance global. Mi ejemplo asume matrices cuadradas donde cada dimensión es divisible por 16. Además, es una práctica común para izar el tamaño de azulejo fuera en una variable int const estáticos o un argumento de plantilla.

En el ejemplo de multiplicación de matriz simple de figura 1, el sistema de azulejos de la computación en su nombre, detrás de las escenas. Por lo que implícitamente es mosaico, en lugar de explícitamente mosaico, y no tiene que preocuparse acerca de los requisitos de la divisibilidad. What the simple model can’t do for you, and hence you have to do yourself, is the necessary step 2 of tiling: changing the algorithm to use tile_static memory and, typically, the usage of one or more of the other index objects. Antes que ahondar en eso, vamos a tomar un desvío para entender algunos conceptos básicos de hardware.

Breves antecedentes sobre la jerarquía de memoria de la GPU

Hay un montón de contenido en línea que describe las características de hardware GPU. Este contenido varía en función de qué proveedor de hardware se centra en, y aún qué familia de hardware de cada proveedor describe. En esta sección ofrecemos un alto nivel, Cruz-hardware y áspera (cada proveedor de hardware tiene su propia terminología y sus propios matices) imagen, desde la perspectiva de los desarrolladores de C++ AMP.

GPU tienen memoria global en el que residen sus datos de matriz y array_view. También hay registros para cada subproceso, que es donde suelen ir las variables locales (a menos que su controlador de hardware tiene otras ideas: por ejemplo, si utiliza demasiados registros para el código que se ejecuta en hardware, derrame su contenido en la memoria global). El acceso a memoria global es mucho más lento que el acceso a los registros, por ejemplo, toma una GPU ciclo para acceso de registro frente a 1.000 ciclos para un acceso de memoria global.

Además, cerca de cada uno de sus elementos de procesamiento, GPU tienen un espacio de memoria pequeño Bloc de notas (por ejemplo, 16 KB a 48 KB, dependiendo del hardware). Esto es mucho más rápido para acceder a la memoria global; quizás 10 ciclos por acceso, por ejemplo. Esta área de memoria, también llamada el almacén de datos local, es una caché programable. Cachés de la CPU se gestionan de forma automática y transparente para usted, y por lo tanto los beneficios de rendimiento automáticamente le concede a usted. Por el contrario, tienes que administrar esta caché GPU mismo copiando datos dentro y fuera de ella, por lo tanto, tiene que participar en el beneficio de rendimiento. Algunos modelos de programación llaman esta "memoria compartida" otros lo llaman "memoria local" y aún otros llaman "memoria de grupo compartido". En C++ AMP esta caché programable se llama "memoria tile_static" — más sobre este tema más adelante.

A continuación, vamos a asignar un modelo lógico para el modelo de hardware GPU, comenzando con el alcance y la duración de la memoria.

Un pedazo de memoria global es accesible a todos los subprocesos, y su vida supera la de la vida de una parallel_for_each de computación, por lo que la invocación de un parallel_for_each posterior puede operar en los mismos datos. Un valor de registro sólo es accesible por un hilo, y su vida es el de la rosca. Una pieza de tile_static memoria es compartida por un subconjunto de todos los subprocesos, llamado en C++ AMP es un mosaico de subprocesos, y su vida es el de la teja. Now you’re starting to see why you need to tile your computation: Without knowing which tile a thread belongs to, you have no way of using this fast tile_static memory.

Se puede considerar de la memoria de tile_static como "arrendado" por el mosaico de subprocesos hasta la teja completa ejecución, momento en el que se apodera otra baldosa. Por lo tanto, lógicamente hablando, hilos de diferentes azulejos están aislados entre sí con respecto a la memoria de tile_static y aparecen a cada propiedad de la memoria tile_static.

Utilizando el nuevo tile_static de clase de almacenamiento

Acceso a la memoria de tile_static utiliza la clase de almacenamiento de tile_static. Esta es la segunda mejora que C++ AMP hace al lenguaje C++ (el otro se restringen, que tratan en mi artículo anterior).

Sólo se puede utilizar esta nueva clase de almacenamiento de información en funciones de restrict(amp), y sólo si la parallel_for_each es mosaico y sólo para variables declararon dentro de ese bloque de función. Punteros y referencias no se pueden marcar tile_static y cualquier implícitos constructores y destructores de tile_static variables no se llama. Normalmente (aunque no siempre) las variables tile_static son arreglos de discos, y son generalmente proporcionales al tamaño de azulejo, por ejemplo:

tile_static float t[16][16]; // Access t to access tile static memory

La forma más común para tomar ventaja de la memoria tile_static es identificar áreas de memoria global que se puede acceder más de una vez en el algoritmo. A continuación, copia esas zonas en memoria de tile_static (pagando el precio de acceso a memoria global sólo una vez) y luego cambiar su algoritmo a utilizar la copia de tile_static de los datos de acceso (a lo muy rápido, repetidas veces), en lugar de acceder a los datos en la memoria mundial varias veces. La caché programable es pequeña, por lo que no se puede copiar todos los datos de la matriz y array_view variables de tile_static. Un algoritmo de mosaico es normalmente más complejo ya que es preciso evitar copiando sólo un mosaico-tamaño-valor de datos de memoria global.

Antes de mirar un ejemplo del mundo real más que muestra la técnica del párrafo precedente, analicemos un ejemplo simplista, inventado y cochecito, centrado exclusivamente en el uso de tile_static donde intentar sumar todos los elementos de una matriz:

1  static const int TS = 2;
2  array_view<int, 2> av(2, 6, my_vector);
3  parallel_for_each(av.extent.tile<TS,TS>(),
     [=](tiled_index<TS,TS> t_idx) restrict(amp)
4  {       
5    tile_static int t[TS][TS];   
6    t[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global];
7
8    if (t_idx.local == index<2>(0,0)) {
9      t[0][0] = t[0][0] + t[0][1] + t[1][0] + t[1][1];             
10     av[t_idx.tile_origin] = t[0][0];
11   }
12 });
13 int sum = av(0,0) + av(0,2) + av(0,4); // The three tile_origins

The technical term for the preceding code is: horrible. Líneas 9 y 13 toman ventaja del hecho de que el tamaño de azulejo es 2 x 2 = 4 y que el tamaño total es 2 x 6 = 12 (ahí tres fichas), cuando las implementaciones reales deben escribirse de tal manera para que cambiar el tamaño de azulejo o alcance general no significa cambiar cualquier otro código. El rendimiento de este código también es horrible porque tiene un algoritmo de ingenua y sus ramificaciones no es manejable para la paralelización de datos. No hay reutilización de los datos en el algoritmo, por lo que incluso no está justificado el uso de memoria de tile_static. También es un error de corrección que te menciono más adelante. Sin embargo, tan horrible como este código, es bastante simple para alguien nuevo tile_static almacenamiento de información para poder entender el código, que es lo que importa.

En la línea 6 cada subproceso en cada mosaico copia los datos en su memoria tile_static de memoria global. En línea 9, sólo el primer hilo de cada baldosa resumen los resultados de ese mosaico en la primera posición de la memoria tile_static de ese mosaico. En línea de 10 que el mismo subproceso (en cada mosaico) almacenará el resultado vuelva en memoria global. Entonces finaliza el cómputo en el acelerador y, en el lado del host en línea 13, el subproceso de CPU suma las tres sumas de las tres fichas en la variable suma.

¿¿Detectar un error de corrección, específicamente una condición de carrera? A la siguiente sección para obtener información sobre la última parte de la API de mosaico, que nos ayuda a lidiar con ese fallo.

tile_barrier clase

La condición de carrera en el ejemplo anterior es entre las líneas 6 y 9. En línea 9 que el hilo con índice local (0,0) asume que todos los valores ya están almacenados en la variable t tile_static, que sólo es cierto si todos los demás subprocesos en el mosaico ya han realizado la línea 6. Debido a que generalmente ejecutan subprocesos independientemente unos de otros, no es una suposición que puede hacer.

Lo que hay aquí es una forma de codificar la condición de que la línea 9 no debe ejecutarse hasta que todos los subprocesos en la teja han ejecutado línea 6. La forma expresa esa restricción es mediante un tile_barrier y llamando a uno de sus métodos de espera. No se puede crear un objeto de tile_barrier tú mismo, pero puede obtener uno a través de la propiedad de la barrera de la tiled_index pasado a la lambda. Por lo tanto puede arreglar la condición de raza insertando la siguiente línea, después de la línea 6:

7 t_idx.barrier.wait();

Tenga en cuenta que no podía han colocado esta línea justo antes de la línea 9 y dentro del bloque condicional. Una llamada a tile_barrier::wait debe aparecer en un lugar donde todos los subprocesos en un azulejo van alcanzar esa ubicación o todos ellos lo omitirá. Si la barrera le permitió colocarse en esos lugares, entonces si un subproceso en un azulejo no ejecuta la espera, el programa podría colgar, esperando en ese subproceso.

El compilador es capaz de marcar muchas de estas condiciones de carrera y los no se puede, el depurador puede encontrar para usted si en Visual Studio 11 encender excepciones de acceso de memoria GPU bajo el cuadro de diálogo de excepciones de la depuración | Menú de excepciones.

Example: Tiled Matrix Multiplication

¿Recordar el ejemplo de multiplicación de la matriz de figura 4? Ahora es tiempo para la parte 2 del ejercicio de teselación, donde también voy a tomar ventaja de la memoria tile_static y inevitablemente utilizará indexación local y el objeto tile_barrier.

Antes de mostrar la solución, te recuerdo que en mi equipo portátil la simple multiplicación de matriz de C++ AMP produce una mejora de rendimiento de más de 40 veces en comparación con el código serial de CPU para M = N = W = 1024. La solución con azulejos que vas a ver, con TS = 16 (por lo que 16 x 16 = 256 hilos por baldosas), es un adicional dos veces más rápido que el modelo simple! Esa medida incluye a la transferencia de datos, por lo que si se hubiera transferido los datos ya y realiza una serie de cálculos sobre los datos, entonces el tiempo de ejecución del núcleo sería mucho más que dos veces más rápido que la variante que no utiliza memoria tile_static. Así que ¿qué precio de complejidad pagan para ese tipo de aumento del rendimiento?

Se muestra el código de multiplicación de la matriz plenamente mosaico en figura 5.

Figura 5 matrices, tile_static mosaico Plus con memoria

1  void MatMul(vector<int>& vC, const vector<int>& vA,
     const vector<int>& vB, int M, int N, int W )
2  {
3    array_view<const int,2> a(M, W, vA), b(W, N, vB);
4    array_view<int,2> c(M, N, vC);  
5    c.discard_data();
6
7    parallel_for_each(c.extent.tile<TS,TS>(),
       [=](tiled_index<TS,TS> t_idx) restrict(amp) 
8    {
9      int row = t_idx.local[0]; int col = t_idx.local[1];
10     tile_static int locA[TS][TS], locB[TS][TS];
11     int sum = 0;
12     for (int i = 0; i < a.extent[1]; i += TS) {
13       locA[row][col] = a(t_idx.global[0], col + i);
14       locB[row][col] = b(row + i, t_idx.global[1]);
15       t_idx.barrier.wait();
16
17       for (int k = 0; k < TS; k++)
18         sum += locA[row][k] * locB[k][col];           
19       t_idx.barrier.wait();
20     }
21     c[t_idx.global] = sum;
22   });
23   c.synchronize();
24 }

Mientras que el código de figura 5mosaico de obras para cualquier tamaño y cualquier tamaño de extensión total (conforme a las reglas descritas anteriormente), la forma más fácil de comprender lo que hace es utilizar pequeñas cantidades.Pequeñas cantidades no realizan bien en tiempo de ejecución, pero nos ayude a obtener un agarre en lo que va de.Así que vamos a asumir azulejos que son 2 por 2 (TS = 2) y M = 2, N = 6 y W = 4.Esta configuración es fotografiada en figura 6.

Matrix Multiplication Example with 12 Threads in 2-by-2 TilesFigura 6 ejemplo de multiplicación de matriz con 12 temas en azulejos de 2 por 2

TPara entender el código, siga un solo azulejo, la segunda placa (de los tres) y sólo un hilo específico: el hilo que calcula el resultado de 160 (esto se destaca en la Figura 6-el resaltado incluye la fila de A y la columna de B que este tema necesita tener acceso a fin de calcular los resultados en la célula de C).

Vamos a caminar a través del código de figura 5 manteniendo un ojo sobre la imagen útil de figura 6.

Cuando i = 0, la ventana de inspección paralelo en figura 7 muestra los valores de las variables pertinentes hasta el bucle interior on line 16.

When i=0, the Values of Other Variables Are Shown in Each Column, Where Each Row Is a ThreadFigura 7 cuando me = 0, los valores de otras Variables se muestran en cada columna, donde cada fila es un subproceso

Como puede ver, los cuatro subprocesos del azulejo cooperaron para copiar los datos en los dos tile_static matrices locA y locB de matrices a y B.Entonces, después de que todos ellos se reunieron en la barrera en línea 15, entraron en el bucle interior en línea 17.Ver figura 8 para los valores de variables pertinentes para ambos k = 0 y k = 1.

Still i=0, the Values of Other Variables Are Shown for k=0 and k=1Figura 8 todavía i = 0, se muestran los valores de otras Variables para k = 0 y k = 1

En este punto el subproceso está siguiendo calcula una suma parcial de 1 x 4 y en la segunda iteración de la variable k agrega a 2 x 10, haciendo un total de 24 (véase figura 8).Luego, cumple con los otros subprocesos en la barrera en línea 19.Ahora está listos para entrar en una segunda iteración del bucle externo.Observe que la variable tendrá el valor de 2, y figura 9 muestra los nuevos valores correspondientes de las variables hasta la barrera.

Figura 9 cuando me = 2, en esta tabla se muestran los valores de otras Variables

Una vez más, los cuatro hilos del azulejo cooperaron para copiar los datos en los dos tile_static matrices locA y locB de matrices a y B, moviéndose a la derecha en la matriz a y hacia abajo para la matriz B.Entonces, después que cumplen en la barrera en línea 15, vuelva a introducir el bucle interior en línea 17; ver figura 10 para los valores de variables pertinentes para ambos k = 0 y k = 1.

En este punto, según figura 10, el subproceso está siguiendo agrega a 24 el producto de 3 x 16 y en el k segundo iteración más 4 x 22, haciendo un total de la gran suma de 160.Luego, cumple con los otros subprocesos en la barrera en línea 19.Los hilos ya están listos para entrar en una tercera iteración del bucle externo, pero se dan cuenta que ya no se cumple la condición de bucle para variable que, por lo que saltar a la línea 21.El subproceso que está siguiendo utiliza su índice global para actualizar la memoria global en C.

Still i=2, the Values of Other Variables Are Shown for k=0 and k=1Figura 10 todavía i = 2, se muestran los valores de otras Variables para k = 0 y k = 1

Puede ver ahora cómo cada elemento de la matriz fue copiado una vez de memoria global por cada subproceso y luego reutilizado por cada uno de los otros subprocesos en el mosaico.Como dije antes, el aumento de rendimiento viene de copiar un fragmento de datos una vez de memoria global en memoria de tile_static y luego reutilizar ese pedazo de datos varias veces en el código.

Para ayudarle a comprender el código, elegí un tamaño de azulejo de 4 (TS = 2) más pequeños alcances, pero en el código real ambos sería mayores.Los números de rendimiento compartí anteriormente dependía de 16 x 16 = 256 hilos por baldosas, así que fui reutilización de una pieza de datos veces 256 de memoria rápida en lugar de acceder a cada momento de la memoria global.La aplicación de la multiplicación de matriz mosaico además beneficia de mejores patrones de acceso a la memoria en la matriz A (mientras matrices b y c se acceden eficientemente en todas las implementaciones), pero la coalescencia de memoria es más allá del alcance de este artículo.

Esa es la conclusión de la multiplicación de matriz mosaico que se aprovecha de la memoria tile_static.Si desea más práctica, elaboración de algoritmos de mosaico, vea los ejemplos en el blog del equipo "Paralelo de programación en código nativo" en bit.ly/xZt05R.

La desventaja

En este artículo le introduje a las baldosas, la técnica de optimización más común para optimizar el código C++ AMP.

Ha aprendido a usar tres nuevas clases de C++ AMP (tiled_extent, tiled_index y tile_barrier), además de la clase de almacenamiento de tile_static.Usted sabe que puede comenzar con una implementación simple (no explícitamente mosaico).A continuación, puede modificar la aplicación llamando a la función de mosaico en el objeto de medida (elegir un tamaño de azulejo) y modificar tu firma lambda para aceptar una tiled_index (con los mismos argumentos de plantilla de tamaño mosaico).

Completó el paso mecanicista 1.Para el segundo y último paso, se reescribió su algoritmo para utilizar memoria tile_static con uso apropiado de la sincronización mediante un tile_barrier.Es el paso creativo, donde cada algoritmo tienes que llegar a una solución de flamante.Las implementaciones simples y mosaico de matrices demuestran la complejidad introducida con teselación, y también por qué su uso puede producir un rendimiento gana.La desventaja es tuyo optar, o no.

Cabe mencionar que este tipo de programación consciente de caché puede causar aumento de la velocidad de su código de CPU, demasiado, porque sería estar ayudando a las automáticas transparente sistema de gestión de la caché de hacer un mejor trabajo.También, el código mosaico se vuelve más manejable para Vectorización de compiladores inteligentes.

Más allá de mis dos artículos, es una riqueza de contenido de AMP de C++ (documentación, consejos, patrones, enlaces a videos y muestras) en el blog del equipo de programación paralela (bit.ly/bWfC5Y) que recomendamos leer.

Daniel Moth es administrador principal de programas en la División de desarrolladores de Microsoft. Llegó a través de danielmoth.com/Blog.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Steve Deitz, Yossi Levanoni, Robin Reynolds-Haertle, Stephen Toub y Weirong Zhu