cuda – optimización de aplicaciones...–modelo de ejecución cuda: ejecución de threads: warps...

58
CUDA – Optimización de aplicaciones

Upload: others

Post on 26-Jul-2020

12 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

CUDA – Optimización de aplicaciones

Page 2: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

– Modelo de ejecución CUDA: ● Ejecución de threads: warps

– Optimización● Warps, branches, patrones de acceso a memoria (shared y

global)

– Herramientas de profiling: nvprof, nvvp.

Page 3: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

– Vamos a utilizar los códigos de ejemplo:

cp -a /share/apps/codigos/ alumnos_icnpg2016/clase_14/Codigos/ .

● Copiar los 4 ejemplos

Page 4: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

¿Cómo se ejecutan los kernels en la GPU?

__global__ void kernel (int *input, int *output, int size){ int tid = threadIdx.x; if (tid < size) { output[tid] = input[tid] * input[tid]; }}

int main(){ // ... some stuff

kernel<<<100000>>>(dev_in, dev_out);

//... some other stuff}

¿Por qué no así?

Primera aproximación GPU: muchos threads corriendo en paralelo.

Page 5: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Multiprocesador (SM)

kernel<<<BLOCK_DIM, GRID_DIM>>>(...);

Los bloques se ejecutan en cualquier orden -----> Escalabilidad

Una NVIDIA GPU consiste de un número de Multiprocesadores (SMs). Cada SM contiene un número de CUDA cores. Cada SM recibe un bloque y lo ejecuta. Cada core ejecuta

instrucciones de los threads en un modo SIMD.

Page 6: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)
Page 7: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)
Page 8: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Threads inactivos(consumen recursos)

Page 9: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Warps● CUDA implementa un modelo de ejecución llamado SIMT (Single Instruction

Multiple Threads).

0 0 0 0 ... 0 0 0 0 0

1 1 1 1 ... 0 0 0 0 0

1 1 1 1 ... 2 2 2 2 2

__global__ void kernel (float *out){ int tid = threadIdx.x;

if(tid < 16)

{ out[tid] = 1;

} else

{ out[tid] = 2;

}}

int main(){ ... kernel<<<1,32>>>(devOut);

...}

- Cada SM recibe 1 o más bloques.- Cuando un bloque se asigna a un SM, debe ser completamente ejecutado por el SM. - Cada bloque de threads activos es dividido en grupos de 32 threads llamados warps, los cuales son ejecutados en el SM de forma SIMD.- Esto significa que todos lo threads dentro del warp deben ejecutar la misma instrucción cada vez. - En presencia de saltos que causen caminos diferentes a seguir por los threads dentro de un warp (ramas divergentes) se serializa la ejecución de cada rama, deshabilitando threads que no sigan la rama activa. - Una vez que las ramas divergentes se completan, los threads convergen nuevamente.

Page 10: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

else clause

Page 11: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Para casos sencillos el compilador optimiza estas instrucciones:

Warps

__global__ void kernel (float *out){ int tid = threadIdx.x;

if(tid < 16)

{ out[tid] = 1;

} else

{ out[tid] = 2;

}}

int main(){ ... kernel<<<1,32>>>(devOut);

...}

Page 12: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Para casos sencillos el compilador optimiza estas instrucciones:

Warps

__global__ void kernel (float *out){

int tid = threadIdx.x;

out[tid] = (tid < 16) * 1 + (tid >= 16) * 2;}

int main(){ ... kernel<<<1,32>>>(devOut);

...

}

__global__ void kernel (float *out){ int tid = threadIdx.x;

if(tid < 16)

{ out[tid] = 1;

} else

{ out[tid] = 2;

}}

int main(){ ... kernel<<<1,32>>>(devOut);

...} - El compilador CUDA puede transformar pequeños branch y

evitar divergencias.

- Esto se usa para ramas o branch simples, cortos ya que la la instrucción es ejecutada siempre.

Page 13: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Entonces:

Warps

__global__ void kernel (float *out){

int tid = threadIdx.x;

out[tid] = (tid < 16) * 1 + (tid >= 16) * 2;}

int main(){ ... kernel<<<1,32>>>(devOut);

...

}

__global__ void kernel (float *out){ int tid = threadIdx.x;

if(tid < 16)

{ out[tid] = 1;

} else

{ out[tid] = 2;

}}

int main(){ ... kernel<<<1,32>>>(devOut);

...}

Carpeta ejemplos/ejemplo_divergencia/simpleDivergence:

nvcc -O3 -arch=sm_21 simpleDivergence.cu -o simpleOptnvprof –metrics branch_efficiency ./simpleOpt

nvcc -g -G -arch=sm_21 simpleDivergence.cu -o simplenvprof –metrics branch_efficiency ./simple

Fuerza a que el compilador no optimice (branch predication)

Page 14: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Se define como la taza de ramas no divergentes y la cantidad total de ramas:

nvprof –metrics branch_efficiency

Carpeta ejemplos/simpleDivergence:

nvcc -O3 -arch=sm_21 simpleDivergence.cu -o simpleOptnvprof –metrics branch_efficiency ./simpleOpt

nvcc -g -G -arch=sm_21 simpleDivergence.cu -o simplenvprof –metrics branch_efficiency ./simple

Fuerza a que el compilador no optimice (branch predication)

#branchesX

#branches - #divergent branches100Branch efficiency =

Page 15: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Se define como la taza de ramas no divergentes sobre la cantidad total de ramas:

Nvprof –metrics branch_efficiency

Carpeta ejemplos/simpleDivergence:

nvcc -O3 -arch=sm_21 simpleDivergence.cu -o simpleOptnvprof –metrics branch_efficiency ./simpleOpt

nvcc -g -G -arch=sm_21 simpleDivergence.cu -o simplenvprof –metrics branch_efficiency ./simple

Fuerza a que el compilador no optimice (branch predication)

#branchesX

#branches - #divergent branches100Branch efficiency =

Más métricasnvprof –metrics branch_efficiency y –events branch,divergent_branch ./simplenvprof –metrics branch_efficiency y –events branch,divergent_branch ./simpleOpt

Page 16: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Warps● Los threads NO son independientes, pero los warps SI.

__global__ void kernel (float *out){ int tid = threadIdx.x + blockDim.x * blockIdx.x;

if (tid < 32)

{ .... } else if (tid >= 32 && tid <= 64)

{ .... } else

{ .... }}

● GPU: SMs que ejecutan warps de threads. Los warps son independientes entre sí. Los threads de cada bloque comparten la memoria.

Page 17: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Warps● Los threads NO son independientes, pero los warps SI.

● GPU: SMs que ejecutan warps de threads. Los warps son independientes entre sí. Los threads de cada bloque comparten la memoria.

Es importante notar que la divergenciasolo aplica a threads de un mismowarp. Diferentes warps son ejecutados independientemente.

__global__ void kernel (float *out){ int tid = threadIdx.x + blockDim.x * blockIdx.x;

if (tid < 32)

{ .... } else if (tid >= 32 && tid <= 64)

{ .... } else

{ .... }}

Page 18: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Warps & branchs

● Cada grupo de threads en un warp se ejecutan juntos (SIMD), esto significa que, en un caso ideal, se realiza una sola búsqueda de la instrucción en memoria y se hace el broadcast de la instrucción a cada thread (no branchis).

● Tamaño actual de warp: 32 threads. ● Por qué nos interesa saber el tamaño de warp? Varias razones:

– Branching: un warp es una unidad de ejecución, entonces, cualquier instrucción de branching (if, else, for, while, do, switch) causa una divergencia en el flujo de ejecución.

La GPU ejecuta un camino y luego el otro, serializa la ejecución. Los threads que ejecutan el camino se ejecutan y los otros son marcados como inactivos. Una vez

que el camino elegido es ejecutado, el otro camino del salto es elegido y ejecutado.

Tratar de ajustar granularidad para que sea multipo de 32 (tamaño de warp) para evitar divergencias

Page 19: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)
Page 20: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Memoria Global – Patrón de acceso

Page 21: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Memoria Global – Patrón de acceso

● La memoria global es la que se accede para w/r desde CPU y desde GPU. Es la que más hay que entender para utilizarla logrando el mayor ancho de banda.

● Las aplicaciones en GPU tienden a ser memory bound. Hay que maximizar el uso del ancho de banda de la memoria global, ya que es desde donde se empieza casi siempre toda aplicación CUDA.

● Una vez que los datos están en memoria global, la pregunta es cómo accederlos de forma eficiente? (existe mucha diferencia de capacidad computacional de una GPU actual con el ancho de banda de la memoria).

● Los threads se ejecutan en warps y cada vez que un thread (un warp) ejecuta una instrucción de acceso a memoria cada thread del warp genera una dirección. Dependiendo del patrón de las direcciones formado por el warp se tiene distinto rendimiento en dicho acceso patrones de acceso!!→

Page 22: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Todos los accesos a memoria global pasan por cache L2 y en esta memorialas transacciones son de 32 bytes.

Algunas transacciones pasan por L1 (dependiendo de la arquitectura de la GPUy del tipo de acceso). En este caso las transacciones se hacen de a 128 bytes.

La L1 se puede deshabilitar.

Page 23: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Patrones de acceso a memoria global

Las operaciones de memoria también son ejecutadaspor warp ( ½ warp para CC < 2.0).

Es importante considerar: - Patrón de acceso a memoria - Número de accesos concurrentes

Los accesos se hacen de a segmentos de:

* 32 bytes * 128 bytes

Idealmente, el patrón de acceso a memoria global tiene que ser:

- Alineado: la primer dirección de la transacción de acceso a memoria está alineada (o es múltiplo) de los segmentos de memoria (32 o 128 bytes). - Coalescido: cuando los 32 threads de un warp acceden a direcciones contiguas de memoria.

Page 24: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Patrones de acceso CORRECTOS a memoria global

Caso 1: Cada warp accede a 32 posiciones de memoria alineadas y contiguas de 4 bytes:

Utilización del Bus: 100%Bytes necesarios: 128 Bytes leídos: 128

Caso 2: Cada warp accede a 32 posiciones de memoria contiguas pero no secuenciales dentro del rango de 128 bytes:

Utilización del Bus: 100%Bytes necesarios: 128Bytes leídos: 128

(cada thread accede a datos de 4 bytes, y se supone acceso con segmentos de 32 bytes no se usa L1→ )

Page 25: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Patrones de acceso INCORRECTOS a memoria global

Caso 3: Cada thread accede a 32 posiciones de memoria contiguas desalineadas de 4 bytes:

Utilización del Bus: >80%Bytes necesarios: 128Bytes leídos: 160 (se leen 32 bytes más)

(cada thread accede a datos de 4 bytes, y se supone acceso con segmentos de 32 bytes no se usa L1→ )

Page 26: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Caso 4: Todos los threads acceden al mismo dato de 4 bytes:

Utilización del Bus: 12%Bytes necesarios: 4Bytes leídos: 32

Caso 5: Un warp pide 32 datos de 4 bytes repartidos arbitrariamente:

Utilización del Bus: 128/(N*32)Bytes necesarios: 128Bytes leídos: N*32(los 128 bytes caen en N segmentos de 32 bytes)

Patrones de acceso INCORRECTOS a memoria global

Page 27: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Memoria Global – Patrón de acceso

● Alineados: que la primer dirección de acceso esté alineado con la granularidad de la memoria a la que se accede (la memoria que preste el servicio: global o alguna cache intermedia).

● Coalescidos: que los 32 threads de un warp accedan a un bloque contiguo de memoria.

● Alineado y coalescido: es lo ideal: un warp accediendo a direcciones contiguas en memoria, empezando por una dirección alineada

● Las direcciones de acceso de los threads contiguos se combinan y se realiza un solo acceso a memoria (transacción).

Para maximizar el rendimiento de la memoria global es importante organizar los accesos a dicha memoria para que sean ambos,

coalescidos y alineados.

Page 28: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Acceso a memoria global

● Problema:– Tengo un conjunto de N puntos en el eje cartesiano, cada uno con las coordenadas (x,y).

● Puedo elegir: – almacenar cada punto como una estructura, y los N puntos en un arreglo.

struct innerStruct { float x; float y;};

struct innerStruct myAoS[N];

struct innerArray { float x[N]; float y[n];};

AoS approach

SoA approach

– O bien, tener una estructura con dos arreglos (componentes x e y) de tamaño N.

Page 29: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Acceso a memoria global: AoS vs SoA

__global__ void testInnerStruct(innerStruct *data, innerStruct * result, const int n){ unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;

if (i < n) { innerStruct tmp = data[i]; tmp.x += 10.f; tmp.y += 20.f; result[i] = tmp; }}

AoS approach

SoA approach

__global__ void testInnerArray(InnerArray *data, InnerArray * result, const int n){ unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;

if (i < n) { float tmpx = data->x[i]; float tmpy = data->y[i];

tmpx += 10.f; tmpy += 20.f; result->x[i] = tmpx; result->y[i] = tmpy; }}

Page 30: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

nvprof

Eventos y métricas de operaciones de memoria: para medir la eficiencia de un kernel operando con los diferentes tipos de memoria.

1) Accesos a memoria global:– Lo óptimo son accesos coalescidos y alineados. Cualquier otro tipo de acceso se traduce en 2

accesos (replay memory requests)

– Métricas para chequear eficiencia de lecturas y escrituras en memoria global:

● gld_effiency nvprof –metrics gld_efficiency ./app→● gst_efficiency nvprof –metrics gst_efficiency ./app→

muestran la relación entre los pedidos de accesos a memoria con los accesos requeridos realmente (el primero no incluye repeticiones y el segundo si).

Page 31: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Probando el código

1) Entrar a la carpeta ejemplos_memoria_global/ y compilar ambos códigos:

[monica@denham ejemplos_memoria_global]$

nvcc simpleMathSoA.cu -o simpleMathSoAnvcc simpleMathAoS.cu -o simpleMathAoS

2) Ejecutar utilizando nvprof:

[monica@denham ejemplos_memoria_global]$

nvprof --metrics gld_efficiency,gst_efficiency /simpleMathAoSnvprof --metrics gld_efficiency,gst_efficiency /simpleMathSoA

Page 32: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Probando el código

● Ejecutar con nvprof –metrics gld_efficiency, gst_efficiency /simpleMathAoS ● Ver el rendiento de escrituras y lecturas en memoria global. Explicar lo que está pasando.

¿Por qué en AoS se consigue el 50% de eficiencia?

¿Por qué en SoA se consigue el 100% de eficiencia?

¿Que pasa con la proximidad espacial y temporal en CPU?

¿ Que pasa con la proximidad espacial y temporal en GPU?

Page 33: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Probando el código

● Ejecutar con nvprof –metrics gld_efficiency, gst_efficiency /simpleMathAoS ● Ver el rendiento de escrituras y lecturas en memoria global. Explicar lo que está pasando.

● Por qué en AoS se consigue el 50% de eficiencia? esto quiere decir que cada lectura y →escritura se está repitiendo. En cada transacción, x e y son contiguos, accedo a ambos y uso exactamente la mitad de los datos a los que accedo. 50% de los datos escritos o leidos son útiles. Cada acceso se resuelve con 2 transacciones.

● En SoA se consigue el 100% porque cada acceso se resuelve con 1 unica transacción.

● ¿Que pasa con la proximidad espacial y temporal en CPU? Caches ayudan a proximidad espacial y temporal (predicen instrucciones que se accederán en un futuro proximo)

● Que pasa con la proximidad espacial y espacial en GPU? L1 en gpu va por la proximidad espacial y no temporal. Accesos cercanos, coalescidos.

Page 34: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Probando el código

● Más métricas para memoria global– g**_transactions_per_request: número promedio de accesos que se realizan por cada pedido de

acceso. (** : ld o st)

– g**_transactions: número total de operaciones en memoria global (ld o st) por lanzamiento de kernel.

– g**_throughput: productividad lograda que se puede comparar con los picos teóricoss para ver cómo es la eficiencia en la aplicación.

Page 35: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory

Page 36: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory

● Físicamente cada SM tiene una pequeña memoria de baja latencia compartida por todos los threads que ejecutan concurrentemente en un bloque en un SM.

● Shared memory permite cooperar a los threads de un mismo bloque, facilita reuso de datos (onchip) y puede reducir el ancho de banda de memoria global.

● Como el contenido de la memoria global es exclusivamente manejado desde la aplicación, se la usa como una memoria cache programable.

En arquitecturas actuales, la memoria compartida tiene una latencia 20 o 30 vecesmenor que la memoria global y el ancho de banda es cerca de 10 veces mayor.

Page 37: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Memorias Global y Shared

● La memoria global es una memoria grande, on-board, y tiene alta latencia.

● Memoria compartida: más chica, baja latencia y es on-chip y mayor ancho de banda que la memoria global.

● Usos comunes:– Medio de comunicación entre threads de un mismo bloque.

– Cache manejada por el usuario.

– Se usa para transformar patrón de los datos y mejorar el patrón de acceso a memoria global.

Page 38: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory

● El tiempo de vida del contenido de la memoria compartida es el mismo que el del bloque que la declara.

● Se accede por warp. Idealmente un acceso a memoria compartida por un warp se sirve en una sola transacción. En el peor de los casos, cada pedido se ejecuta en 32 transacciones únicos secuenciales.

● Si todos los threads de un bloque acceden a la misma palabra, un solo thread accede a la memoria compartida, busca la palabra y la manda al resto de threads con un multicast.

La memoria compartida es repartida entre todos los bloques residentes en un SM, → es un recurso critico que limita el paralelismo en la GPU. A más memoria compartida

usada por un kernel, menos bloques concurrentes activos.

Page 39: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Uso de memoria compartida:– Alocación: se declara con el siguiente cuantificador:

se pueden declarar arreglos 1D, 2D y 3D de memoria compartida.

declara un arreglo 2D de flotantes.

– Alcance:

● Si se declara dentro de una función kernel, el alcance es el local al kernel. Si se declara fuera de un kernel en un archivo, el alcance de la variable es global a todos los kernels.

– Tamaño dinamico vs estático: si en tiempo de compilación el tamaño no es conocido, se puede declarar un arreglo adimensional con la palabra clave extern. Por ejemplo:

Se aloca dinámicamente en tiempo de ejecución en cada invocación del kernel, especificando el tamaño en el lanzamiento del kernel, como sigue:

● Notar que solo se pueden declarar arreglos dinámicos 1D.

Shared memory

Kernel<<<grid, block, size*sizeof(int)>>>(...);

__shared__

__shared__ float tile [size_y][size_x];

extern __shared__ int tile[];

Page 40: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Memoria compartida

● Bancos y Acceso a memoria: la memoria compartida puede ser usada para ocultar latencia de memoria global y mejorar ancho de banda, pero para esto se necesita saber cómo es la organización de esta memoria:

– La memoria compartida está dividida en 32 módulos del mismo tamaño, llamados bancos, los cuales pueden ser accedidos simultáneamente.

– Hay 32 bancos porque hay 32 threads por warp. Memoria compartida: arreglo 1D.

– Si una operación en memoria comparitda (r/w) ejecutada por el warp no accede a más de una localidad por banco, la operación puede ser llevada a cabo con una sola transacción. → acceso concurrente a los distintos bancos.

– Si se acceden a localidades del mismo banco, la operación es servida por múltiples transacciones, disminuyendo el ancho de banda.

– Cuando hay conflicto de bancos el hardware divide la operación en transacciones libres de conflicto, disminuyendo la utilización del ancho de banda.

Shared memory

Page 41: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

No hay conflicto de bancos, cada transacción accede a bancos distintos.Todos los accesos se hacen de forma simultánea.

Page 42: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Acceso al mismo banco. 2 posibilidades:

- Broadcast libre de confilcto si los threads acceden a la misma dirección dentro del banco. - Acceso con conflicto de bancos si los threads acceden a distintas direcciones dentro del mismo banco. Si los 32 threads acceden a 32 posiciones distintas en el mismo banco, se realizan 32 transacciones para resolver el acceso.

Page 43: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Una forma de evitar conflicto de bancos en sm es haciendo padding:

Todos los threads acceden al banco 0. Conflicto de bancos!!

Si se reacomodan los datos en memoria compartida el acceso se hace libre de confilctos...

Decrece un poco la cantidad de memoria disponible y se deben reorganizar los indicesde acceso a la memoria. Pero se evita conflicto de bancos!

Shared memory

Page 44: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: optimizando su uso

¿Cómo mapear los datos en los bancos de memoria compartida? ¿Cómo mapear los índices de los threads a la disposición de los

datos en memoria?

Claves para diseñar kernels que eviten conflictos de bancos y aproveche al máximo los beneficios

de la memoria compartida.

Page 45: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: ejemplo● Se puede usar la memoria compartida para cachear, por ejemplo, memoria global

2D cómo accedo a estos datos desde los threads (asumimos bloques 2D de →threads)

Definiciones en main()

Page 46: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: ejemplo● Se puede usar la memoria compartida para cachear, por ejemplo, memoria global

2D cómo accedo a estos datos desde los threads (asumimos bloques 2D de →threads)

Definiciones en main()

Definiciones en kernels:

Page 47: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: ejemplo● Se puede usar la memoria compartida para cachear, por ejemplo, memoria global

2D cómo accedo a estos datos desde los threads (asumimos bloques 2D de →threads)

Definiciones en main()

Acceso por filasAcceso por columnas

Definiciones en kernels:

Page 48: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: ejemplo● Se puede usar la memoria compartida para cachear, por ejemplo, memoria global

2D cómo accedo a estos datos desde los threads (asumimos bloques 2D de →threads)

Definiciones en main()

Acceso por filasAcceso por columnas

Kernel: 3 accesos a memoria:- una escritura en memoria compartida- una lectura de memoria compartida- una escritura en memoria global

Definiciones en kernels:

Page 49: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: ejemplo● Se puede usar la memoria compartida para cachear, por ejemplo, memoria global

2D cómo accedo a estos datos desde los threads (asumimos bloques 2D de →threads)

Acceso por filasAcceso por columnas

Kernels: SetRowReadRow()SetColReadCol()SetRowReadCol()SetRowReadColDyn()SetRowReadColPad()SetRowReadColDynPad()

Set operation (on sh_mem)Read operation (on sh_mem)

Page 50: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Probando el código

1) Entrar a la carpeta ejemplo_shared_memory/ y compilar ambos códigos:

[monica@denham ejemplos_memoria_global]$

nvcc smemSquare.cu -o smem

2) Ejecutar utilizando nvprof:

[monica@denham ejemplos_memoria_global]$

nvprof ./smem

Y más adelante se dan métricas para evaluar utilización de la memoria compartida.

Page 51: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: ejemplo● Se puede usar la memoria compartida para cachear, por ejemplo, memoria global

2D cómo accedo a estos datos desde los threads (asumimos bloques 2D de →threads)

Acceso por filasAcceso por columnas

Kernels: SetRowReadRow()SetColReadCol()SetRowReadCol()SetRowReadColDyn()SetRowReadColPad()SetRowReadColDynPad()

Set operation (on sh_mem)Read operation (on sh_mem)

Page 52: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Shared memory: ejemplo● Se puede usar la memoria compartida para cachear, por ejemplo, memoria global

2D cómo accedo a estos datos desde los threads (asumimos bloques 2D de →threads)

Acceso por filasAcceso por columnas

Kernels: SetRowReadRow()SetColReadCol()SetRowReadCol()SetRowReadColDyn()SetRowReadColPad()SetRowReadColDynPad()

Set operation (on sh_mem)Read operation (on sh_mem)

Evaluar:nvprof ./smemnvprof –metrics shared_load_transactions_per_request,shared_store... ./smem

Page 53: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

nvprof

● Ver diferencias entre acceso por fila y por columna a memoria compartida conflictos →de bancos!!

● Memoria compartida:– Los conflictos de banco es el problema más importante cuando se usa memoria compartida:

– shared_load_transactions_per_request > 1 si hay conflictos de banco y un pedido se resuelve en más de →1 transacción.

– shared_store_transactions_per_request > 1 si hay conflictos de banco y un pedido se resuelve en más →de 1 transacción.

– l1_shared_bank_conflict: (event) reporta la cantidad de conflictos de bancos debido a 2 o más accesos al mismo banco de memoria.

– shared_load | shared_store (events): reporta la cantidad de instrucciones de st o ld sin incluir repeticiones.

– shared_efficiency: relación entre pedidos de acceso a memoria con las transacciones totales. Estos últimos incluyen repeticiones, entonces una shared_efficiency baja implica más conflicto de bancos.

Page 54: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Constant memory

Page 55: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Constant memory● Tiene dos características especiales:

– Está cacheada

– Soporta broadcasting de un valor a todos los threads de un warp.

● Es una memoria usada para datos que son accedidos de forma uniforme por todos los threads de un warp.

● Memoria de solo lectura por threads y W/R desde la CPU. ● Físicamente en DRAM (como la memoria global) y tiene una

cache on-chip dedicada (leer de esta cache tiene mucho menos latencia que leer de la memoria de constantes).

● Patrón de acceso óptimo: todos los threads de un warp acceden a la misma localidad.

● Accesos a direcciones diferentes dentro de un warp son serializados.

● Restringida a 64K.● Si la constante es un valor literal, es mejor definir dicho

valor con un #define. Esto no ocupa memoria y no hay que acceder a ella.

Page 56: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

● Uso de memoria de constantes:– Se usa el calificador:

y como esta memoria es de solo lectura para los threads, debe ser inicializada desde el host:

– Alcance:

Accesible desde todos los threads de un grid. – Tiempo de vida

Toda la aplicación

__constant__ float tile[N];

cudaMemcpyToSymbol(tipo *destino, tipo *origen, size (en bytes), cudaMemcpyHostToDevice);// ultimo argumento por defecto, puede no ponerse

Constant memory

Ver ejemplo: ejemplos_constant_memory/convolucion.cu y comparar las distintas soluciones.

Page 57: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Más ejemplos

● Ver con sobel.cu y sobel_opt1.cu donde:– Sobel.cu utiliza imagenes de 775x499 (no son multiplos de 32)

– sobel_opt1.cu utiliza imagenes de 768x512 que son multiplos de 32

Bloques de 16x16 threads. Con bloques de 32x16 threads mejora

Page 58: CUDA – Optimización de aplicaciones...–Modelo de ejecución CUDA: Ejecución de threads: warps – Optimización Warps, branches, patrones de acceso a memoria (shared y global)

Más ejemplos

● Al modificar 32x16 threads por bloque, – Sobel.cu logra: NO CAMBIA NADA!!!

– Sobel_opt1.cu: sube mucho!!!