Programando para el GPU con CUDA

Publicado en

Las Unidades de Procesamiento Grafico (GPUs) incluidas en las tarjetas de video modernas son procesadores de varios núcleos cuyo principal objetivo es realizar cálculos para el rendering de gráficas 3D en tiempo real. Al desarrollar videojuegos lo más común es utilizar motores que nos proveen frameworks y APIs que facilitan la interacción con la tarjeta de video para el rendering de gráficos, y por lo tanto no es necesario que programemos directamente para el GPU.

Sin embargo, como buen lector de SG, tal vez tengas curiosidad de saber cómo podrías programar directamente hacia el GPU. Esto te permitiría crear por ejemplo un motor de gráficos de muy alto desempeño. Más allá del rendering de gráficos, el procesamiento paralelo con GPU puede aplicarse para resolver todo tipo de problemas de cómputo. A fin de cuentas, al igual que un CPU, un GPU es una unidad de procesamiento que recibe datos e instrucciones y las procesa. La diferencia es que la arquitectura de un GPU se presta mucho más al procesamiento paralelo que la de un CPU tradicional. Mientras que un CPU está formado por algunos cuantos núcleos (cores) con mucha memoria cache que pueden manejar unos cuantos hilos (threads) al mismo tiempo, un GPU típicamente está compuesto por cientos de núcleos de procesamiento que pueden manejar miles de hilos de manera simultánea. Gracias a esto, el procesamiento paralelo de datos usando GPUs llega a ser mucho más rápido, económico y con menos gasto de energía que si usaramos CPUs. Es así que el cómputo por GPU ya se aplica en algunas organizaciones para acelerar cargas de procesamiento en áreas tales como el modelado financiero, investigación científica y exploración de gas y petróleo.

En este artículo veremos a grandes rasgos cómo construir programas que interactúen directamente con el GPU. Para ello utilizaremos CUDA (Compute Unified Device Architecture), que es una tecnología que incorporan las tarjetas de video N-Vidia. CUDA ofrece un API en C, que es el que usaremos. Vale la pena notar que CUDA no es la única opción para programar hacia el GPU, existen otras como OpenCL y DirectCompute.

Funcionamiento de CUDA

En la Figura 1 podemos ver cómo está organizado internamente un proceso en CUDA en base a los elementos: kernel, grid, block y thread.


Figura 1. Arquitectura CUDA

Un kernel es una función que al ejecutarse lo hará en N distintos hilos en lugar de en forma secuencial. La ejecución de un kernel en múltiples threads (hilos) es organizada “espacial y temporalmente” como un grid (malla) de bloques de threads. Esta organización tiene que ver con la estructura en multiprocesadores del array de procesadores. Cada bloque es atendido por un multiprocesador, así que todos los threads de un bloque son ejecutados en el mismo multiprocesador. Gracias a esto, los threads de un mismo bloque pueden sincronizar su ejecución y compartir datos de forma eficiente usando memoria local de baja latencia. En cambio, los threads de bloques distintos no pueden cooperar (o al menos no es trivial lograrlo).

Instalación de las herramientas

Para usar CUDA debes descargar e instalar: los drivers para la tarjeta de video (tu tarjeta debe soportar CUDA), el toolkit de CUDA que contiene los elementos necesarios para poder compilar, y el GPU Computing SDK que contiene proyectos de ejemplo. Todo esto está disponible para plataformas Windows, Linux y Mac, y se puede descargar en http://developer.nvidia.com/cuda-downloads 

Ejemplo de Algoritmo Paralelizado

El listado 1 muestra un código de tipo “Hola Mundo” usando CUDA en C

#include "cuPrintf.cu"
#include <cuda.h>
#include <stdio.h>

// funcion kernel para mostrar thread ID
__global__ void ejemploImprime() {
 int idx = blockDim.x*blockIdx.x + threadIdx.x
 cuPrintf("Hola mundo, %d \n", idx);
}

int main() {
 cudaPrintfInit();
 // invocamos función kernel con 5 grids y 2 bloques.
 ejemploImprime<<<5,2>>>();
 //Mostrar el saludo del kernel
 cudaPrintfDisplay();
 cudaPrintfEnd();
 return 0;
}

Listado 1. Hola Mundo usando CUDA


Al ejecutar el código del listado 1 obtendremos un resultado como el de la figura 2.

Figura 2. Ejecución de Hola Mundo CUDA

 

Conclusión

En este artículo echamos un vistazo a cómo hacer programación para GPU con CUDA. Si te interesa desarrollar soluciones que puedan analizar cantidades masivas de datos en un tiempo razonable y con un costo de hardware relativamente bajo, una excelente opción es hacer un cluster de supercómputo utilizando GPUs de consolas de videojuego.
Debemos tener en cuenta que no todo es paralelizable, por lo que durante el diseño de nuestro software debemos identificar qué partes de nuestro algoritmo vamos a optimizar, así como organizar la memoria de tal forma que los accesos paralelos sean óptimos y con esto evitar cuellos de botella (race condition).

Referencias

  1. NVIDIA Developer Zone. http://developer.nvidia.com
  2. http://forums.nvidia.com/index.php?showtopic=106638
  3. Kirk D.B., Hwu W., ”Programing massively parallel processors”, Elsevier, 2010.
Bio

Héctor Cuesta Arvizu (@hmcuesta) es Lic. en Informática y actualmente cursa la maestría en ciencias de la computación en la UAEM Texcoco. Adicionalmente se desempeña como instructor de TI para Nyce en el área de base de datos e ingeniería de software.

Jorge Esteban Zaragoza Salazar (@TheSuperJez) es Ingeniero en Sistemas Computacionales. Es apasionado del desarrollo de software y se especializa en el desarrollado de aplicaciones Web de alto rendimiento.