• No se han encontrado resultados

Figura 4.16 Acceso a la memoria global por los threads del bloque 0,0

In document Computación de alto desempeño en GPU (página 123-129)

Una mejora podría ser hecha organizando los accesos de los threads a la memoria global de manera que exista la menor cantidad de superposiciones y en consecuencia reducirlos.

acceso a la memoria global y reducir significativamente los accesos a ella, siendo esta reducción proporcional a la dimensión de la matriz. La idea es que los threads en forma colaborativa lean los elementos de A y B y los copien a la memoria compartida antes de usarlos en el producto punto. A fin de no sobrepasar el tamaño de la memoria compartida, se divide a las matrices A y B en tiles o sectores cuyo tamaño se ajuste a ella. En la figura 4.17 se muestra como se divide en sectores las matrices A y B para la colaboración de los threads. Los

tiles son matrices de (3*3) indicadas con líneas más gruesas.

Figura 4.17. División en sectores de A y B 

 

El código de la función kernel para la multiplicación de matrices es detallado en la figura 4.18. Las variables en la memoria compartida tienen el tamaño del sector y en cada fase se utilizan para cargar los valores desde las matrices en la memoria global. En la figura 4.19 se muestran las lecturas realizadas en la memoria global en la primer y segunda iteración para el ejemplo de la figura 4.17. En ella se especifica también la posición de la memoria compartida donde se guardan los valores leídos. 

 

1. __global__ void MatMul_Shared(Matrix A, Matrix B, Matrix C)

2. {

3. __shared__ float As[Dim_sector][Dim_sector];

4. __shared__ float Bs[Dim_sector][Dim_sector];

5.

6. int bx = blockIdx.y;

7. int by = blockIdx.x;

8. int fila = by * Dim_sector+threadIdx.y; 9. int col = bx * Dim_sector + threadIdx.x; 10. float Cvalor = 0;

11.

12. for (int m = 0; m < (N/ Dim_sector); m++) { 13.

14. As[threadIdx.y][threadIdx.x] = A[fila*N+m*Dim_sector+threadIdx.x]; 15. Bs[threadIdx.y][threadIdx.x] = B[(m*Dim_sector+threadIdx.y)*N + col]; 16.

17. __syncthreads();

18.

19. for (int k = 0; k < Dim_sector; k++) 20. Cvalor += As[fila][k] * Bs[k][col]; 21. 22. __syncthreads(); 23. } 24. 25. C[fila*N+col]= Cvalor; 26. }  

Figura 4.18. Función kernel de la multiplicación de matrices usando memoria

compartida 

 

Thread

 

Iteración 0

 

Iteración 1

 

T0,0

 

A0,0As0,0

 

B0,0Bs0,0

 

A0,3As0,0

 

B0,0Bs0,0

 

T0,1

 

A0,1As0,1

 

B0,1Bs0,1

 

A0,4As0,1

 

B0,1Bs0,1

 

T0,2

 

A0,2As0,2

 

B0,2Bs0,2

 

A0,5As0,2

 

B0,2Bs0,2

 

T1,0

 

A1,0As1,0

 

B1,0Bs1,0

 

A1,3As1,0

 

B1,0Bs1,0

 

T1,1

 

A1,1As1,1

 

B1,1Bs1,1

 

A1,4As1,1

 

B1,1Bs1,1

 

T1,2

 

A1,2As1,2

 

B1,2Bs1,2

 

A1,5As1,2

 

B1,2Bs1,2

 

T2,0

 

A2,0As2,0

 

B2,0Bs2,0

 

A2,3As2,0

 

B2,0Bs2,0

 

T2,1

 

A2,1As2,1

 

B2,1Bs2,1

 

A2,4As2,1

 

B2,1Bs2,1

 

T2,2

 

A2,2As2,2

 

B2,2Bs2,2

 

A2,5As2,2

 

B2,2Bs2,2

 

 

 

Observe que dos sincronizaciones son necesarias, la primera asegura que todo el sector de ambas matrices se ha cargado en la memoria compartida y el segundo es para asegurar que la suma parcial se complete antes de pasar a la siguiente fase o finalizar el producto punto.  

La solución de la multiplicación de matrices utilizando la memoria compartida presenta muchas ventajas, una de ellas es la reducción del acceso a la memoria global en función de la dimensión del sector. Además acelera la solución, los accesos a los mismos elementos son hechos en la memoria compartida, más rápida que la global, y de lectura. Se deberá realizar un análisis cuidadoso para determinar la no existencia de conflictos de accesos.  

El análisis de la memoria como factor determinante del paralelismo tiene las mismas características que el hecho para el ejemplo anterior. Para tiles de 16*16, cada bloque requiere 1KB para almacenar cada una de las matrices en la memoria compartida, esto significa que cada

bloque necesita 2KB de memoria compartida. De la misma forma se

puede determinar la cantidad de registros.

4.10. Resumen 

Un thread en CUDA puede acceder a los distintos tipos de memoria

de la jerarquía. Dependiendo de a cuál acceda será la velocidad de acceso. Se distinguen dos grandes tipos de memoria, las memorias on- chip: Memoria Compartida y de Registros, y las off-chip: Memoria Global, Local, de Constantes y de Texturas. Respecto a las velocidades de acceso, las memorias del primer grupo tienen accesos más rápidos que las del segundo a excepción de la de Constante y de Textura, las cuales al tener caché en el chip, tienen velocidades de acceso similares a la memoria de registros y compartida. Estás dos son memorias de sólo lectura para los threads de la aplicación, la escritura la realiza el host

Otra de las diferencias entre las distintas memorias es su tamaño, la memoria global es la más grande y el resto, además de ser mucho más pequeñas, está limitado según la arquitectura. Los programadores deben tener en cuenta los límites, su exceso implica incorporar un factor limitante para el número de threads o de bloques en un SM, en el apéndice C se describen los máximos para cada una de las arquitecturas y sus capacidades. 

Se presentaron distintas técnicas para reducir la latencia de acceso a la memoria global y evitar los conflictos de bancos de la memoria

compartida. En ambos casos se debe tener en cuenta la organización y planificación de los accesos. 

Tener en cuenta los distintos tipos de memoria, le permiten al programador rediseñar sus aplicaciones de manera de minimizar los accesos a la memoria global y utilizar alguna de las memorias más rápidas. Un ejemplo de ello es la multiplicación de matrices desarrollada.  

En este capítulo se presentaron los conceptos básicos y las características de cada memoria, el aprovechamiento de las propiedades más avanzadas de cada una implicará un estudio más profundo del presentado en este texto. 

 

4.11. Ejercicios 

 

4.1.

Desarrolle la multiplicación de matrices explicada en la sección 4.8, donde se utiliza la memoria compartida.

 

 

4.2. Analice el programa del histograma desarrollado en el ejercicio 3.8 del capítulo anterior y proponga una reducción de los accesos a memoria global utilizando otras memorias de la jerarquía.  

 

4.3. Proponga un programa en CUDA para resolver el histograma sobre 256 valores utilizando memoria compartida. 

 

4.4. Resuelva la multiplicación por reducción de los elementos de un vector reduciendo los accesos a memoria global. 

 

4.5. Para la solución propuesta en el ejercicio 3.10 analice:  a. Los accesos a memoria global y a la memoria compartida.  b. Cantidad de: 

1. Memoria de registro.  2. Memoria Global.  3. Memoria Compartida. 

  4.6. Dado el siguiente código:

En el main 

const BANDAS=256; const N= 16384; //16KB int bands[BANDAS+1];

int dato [N];

__constant__ int band_GPU[BANDAS+1]; …

bands[0]=0;

for (int i = 1; i < BANDAS; i++)

bands[i]=bands[i-1] +(rand()%(N-bands[i-1])); bands[i]=N; cudaMemcpyToSymbol(band_GPU,bands,(sizeof (int) * (BANDAS+1))); … kernel<<<1,BANDAS>>>(dato,d_r);

 

En el kernel

__shared__ int sum[blockDim.x]; int i; sum[threadIdx.x]=0; size=(band_GPU[threadIdx.x]- band_GPU[threadIdx.x-1]); for(i=band_GPU[threadIdx.x- 1];i<band_GPU[threadIdx.x]+1;i++) sum[threadIdx.x]+=dato[i]; __syncthreads(); if (threadIdx.x==0){

for (i=0, d_r=0;i<blockDim.x;i++) d_r+=sum[threadIdx.x]; }

Analice:

 

a. ¿Qué hace la parte del programa enunciado? b. Tipos de memoria que utiliza.

c. Tamaño solicitado para cada una de las memorias.

d. Complete el código de programa con las sentencias faltantes.

 

C

APÍTULO

5

In document Computación de alto desempeño en GPU (página 123-129)