Buscar este blog

viernes, 23 de marzo de 2012

Un ejemplo básico, parte II: Implementación del Kernel

Como os prometí en el anterior post, hoy veremos la implementación del kernel para la reducción de vectores:

Nuestro kernel básicamente consiste en realizar la suma de una serie de elementos y guardarla en el primero de ellos. Cada ejecución se encargará de una posición de memoria diferente, con lo que no es necesario preocuparnos de implementar ningún mecanismo de sincronización. El número de elementos a sumar viene dado por el factor de desenrollado (UF). 

Los parámetros de entrada del Algoritmo son: Datos: puntero a la estructura que contiene los datos a sumar, Elementos: número de elementos que se suman, Impar: indica si el número de elementos es impar (para evitar la realización del cálculo del módulo, dentro del kernel, ya que es computacionalmente costoso), y finalmente UF, que se utilizará para indicar el número de elementos a sumar dentro de cada hilo.

Algoritmo 3: kernel de reducción de vectores
1
tid ← threadIdx.x + blockIdx.x * blockDim.x
2
t (ene/UF)
3
si tid < t hacer
4
   para i1 hasta UF hacer
5
            a[tid] a[tid]  + a[(tid+(t*i))]
6
Si tid = 0 y impar > 0 hacer
7
  para i1 hasta impar+1 hacer
8

a[tid] ← a[tid]  + a[(ene-i)]


Algoritmo 1 Kernel de reducción de vectores

1.-La primera línea del algoritmo, “tid ← threadIdx.x + blockIdx.x * blockDim.x”, es la típica asignación para el cálculo de la posición global de ejecución del kernel. Así se identifica exactamente al hilo que se está referenciando en cada uno de los kernels y es la forma que tenemos para organizar el proceso en paralelo. Como en nuestro caso estamos trabajando únicamente con una dimensión, sólo se referencia al valor del campo “x” de cada variable (threadIdx, blockIdx y blockDim), pero podrían ser hasta tridimensionales, para ello se utilizan los campos “y” y “z” para referirnos a la segunda y tercera dimensión respectivamente.

2.- Dado que el número real de cálculos que tenemos que ejecutar no siempre coincide con el total de hilos ejecutados en los Kernels, debemos controlar esta situación para que el dispositivo no haga trabajo inútil (o redundante). Por este motivo la segunda línea del algoritmo calcula el número total de threads a emplear y en la tercera sólo se permite la ejecución del proceso de reducción a los hilos que están dentro del rango necesario.

4.-El siguiente paso define al bucle que empleamos para la suma de elementos del vector, que viene definido por el factor de desenrollado que empleamos en la llamada.

5.-La siguiente línea realiza la suma de elementos y la acumula en el primer elemento.

6.-Finalmente, tenemos una condición especial: sólo si se trata del primer hilo y el número de elementos a procesar es impar, adicionamos esos elementos impares también al primero.

Las operaciones realizadas en el siguiente ejemplo son: (3+6=9), (8+5=13), (4+2=6), y en la siguiente iteración (9+13+6=28).
En la siguiente Figura se puede observar gráficamente el proceso, para un factor UF de 2:


Figura 1. Reducción con UF = 2

Las propiedades fundamentales de este método de reducción que acabamos de proponer son:

·         No hay relación entre bloques ni hilos del mismo stream, con lo que no es necesario establecer ningún tipo de mecanismo para sincronizar el algoritmo en ningún punto. Es un algoritmo orientado al hilo y no al bloque.
·         Adecúa la petición de recursos del sistema en función al estado de su ejecución.
·         Sólo se realiza un desplazamiento de datos de memoria central a memoria de dispositivo por el total de elementos a sumar, y además solo se necesita desplazar el primer elemento a memoria principal para obtener el resultado de la reducción.
·         Con el control de UF se reduce el efecto de la latencia de memoria global del dispositivo, no siendo por tanto necesario realizar las lecturas alineadas con el orden de los hilos.


Ya hemos presentado un algoritmo para la reducción de vectores, que como sabréis, es una de las operaciones más típicas en multitud de escenarios, tanto en análisis de datos, como en optimización. El próximo post presentará los datos obtenidos en las pruebas. 


No hay comentarios:

Publicar un comentario