tesinapablotintor final

34
1 UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO FACULTAD DE CIENCIAS TÍTULO DE TESINA “Propagación de ondas en medios heterogéneos: simulaciones y aplicaciones usando tarjetas gráficas para acelerar el cómputo” T E S I N A QUE PARA OBTENER EL TÍTULO DE: LICENCIADO EN CIENCIAS DE LA COMPUTACIÓN PRESENTA: TINTOR JIMÉNEZ PABLO ALBERTO DIRECTORA DE TESINA: DRA. ÚRSULA XIOMARA ITURRARÁN VIVEROS 2012

Upload: joaquin-casanova

Post on 24-Oct-2015

37 views

Category:

Documents


0 download

DESCRIPTION

Programacin en paralelo Cuda

TRANSCRIPT

Page 1: TesinaPabloTintor FINAL

1

UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO

FACULTAD DE CIENCIAS

TÍTULO DE TESINA

“Propagación de ondas en medios heterogéneos:

simulaciones y aplicaciones usando tarjetas gráficas

para acelerar el cómputo”

T E S I N A

QUE PARA OBTENER EL TÍTULO DE:

LICENCIADO EN CIENCIAS DE LA COMPUTACIÓN

PRESENTA:

TINTOR JIMÉNEZ PABLO ALBERTO

DIRECTORA DE TESINA:

DRA. ÚRSULA XIOMARA ITURRARÁN VIVEROS

2012

Page 2: TesinaPabloTintor FINAL

2

Hoja de Datos del Jurado

DATOS DEL ALUMNO

Apellido paterno Tintor

Apellido materno Jiménez

Nombre(s) Pablo Alberto

Teléfono 55 29 05 65 51

Universidad Nacional Autónoma de México Universidad Nacional Autónoma de México

Facultad de Ciencias Facultad de Ciencias

Carrera Ciencias de la Computación

Número de cuenta 305248336

DATOS DEL TUTOR

Grado Dra.

Nombre(s) Úrsula Xiomara

Apellido paterno Iturrarán

Apellido materno Viveros

DATOS DEL SINODAL 1

Grado Dr.

Nombre(s) José David

Apellido paterno Flores

Apellido materno Peñaloza

DATOS DEL SINODAL 2

Grado Dr.

Nombre(s) Pablo

Apellido paterno Barrera

Apellido materno Sánchez

DATOS DEL SINODAL 3

Grado Dr.

Nombre(s) Pedro

Apellido paterno González

Apellido materno Casanova

DATOS DEL SINODAL 4

Grado Dr.

Nombre(s) Francisco José

Apellido paterno Sánchez

Apellido materno Sesma

DATOS DEL TRABAJO ESCRITO

Título Propagación de ondas en medios heterogéneos: simulaciones y aplicaciones usando tarjetas gráficas para acelerar el cómputo.

Número de páginas 34p

Año 2012

Page 3: TesinaPabloTintor FINAL

3

AGRADECIMIENTOS

A mis padres Blanca Jiménez e Ignacio Tintor que me han apoyado

incondicionalmente para alcanzar mis metas y objetivos. Que me educaron y me

orientaron a llevar una vida escolar que ahora alcanza una meta importante.

A la Dra. Úrsula Iturrarán que me permitió trabajar con ella y me orientó en la

realización de este trabajo. Muchas gracias por ayudarme a terminar esta etapa de

mi vida académica.

A toda mi familia, en especial a mis hermanas Jazmín, Azucena y Dalia por haber

sido mis principales cómplices y compañeras de juegos infantiles. Y ahora por ser

mis principales cómplices y compañeras de vida.

A la profesora Sonia Valery por todo su apoyo profesional prestado en estos dos

años de conocerla.

A J.G.B.P y Ruth, son dos personas muy especiales en mi vida.

A todos los profesores con los que tuve el gusto de tomar clase y aprender de

ellos, en especial a Elisa Viso y Aida Byrd que me enseñaron a programar.

A mis cinco sinodales Dra. Úrsula Iturrarán, Dr. David Flores, Dr. Pablo Barrera,

Dr. Pedro González y Dr. Francisco Sánchez.

A todos mis compañeros y amigos de escuela, en especial a Carlos, Daniel,

Leticia, Jessica, Genaro, Guadalupe, Elizabeth con quienes compartimos trabajos,

tareas y momentos muy divertidos.

Page 4: TesinaPabloTintor FINAL

4

ÍNDICE

Introducción………………………………………………………………………………..5

Objetivo..………………………………………………………………………….………..6

Propiedades de las GPU’s………………………………………………………….........7

Memoria de Textura…………………………………………………………........9

Eventos……………………………………………………………………….........9

Solución de la ecuación de onda acústica……………………………………………10

Condiciones iniciales…………………………………………………….………11

Fronteras absorbentes………………………………………………………......11

Condición de Courant……………………………………………………………12

Definición de la malla……………………………………………………………12

Programación de la ecuación de onda acústica……………………………………...13

Animación…………………………………………………………………………15

Diagrama de flujo………………………………………………………………...17

Código fuente…………………………………………………………………….18

Resultados………………………………………………………………………………..26

Conclusiones……………………………………………………………………………..27

Apéndice A, instalación y configuración de CUDA…………………………………29

Page 5: TesinaPabloTintor FINAL

5

INTRODUCCIÓN

Una onda sonora es una onda longitudinal que transmite lo que se asocia con sonido. Si

se propaga en un medio elástico y continuo genera una variación local de presión o

densidad, que se propaga en forma de onda esférica periódica o cuasi periódica. El

modelado numérico de este fenómeno físico se ha desarrollado como un método para

crear modelos realistas y controlables de los instrumentos musicales. Con este propósito

se han desarrollado numerosos modelos matemáticos que describen el comportamiento

de las ondas acústicas. Uno de ellos es el método de diferencias finitas que permite

aproximar y discretizar la solución a la ecuación de onda acústica, este método consiste

en un conjunto de condiciones físicas y un sistema de actualización, los cuales calculan el

desplazamiento de las ondas acústicas basados en los valores de los tiempos previos, ver

[4].

El método de diferencias finitas permite simular con gran precisión, la propagación de

ondas acústicas sobre cualquier espacio definido. Sin embargo hay dos temas

importantes que tratar antes de llevar a cabo esta aproximación. La primera es la

complejidad para obtener dichos sistemas físicos y la derivación matemática del sistema.

El segundo es la complejidad computacional que se deriva de calcular las soluciones, esta

depende del tamaño de la malla que a su vez depende de la precisión que se desee, la

calidad y el tamaño de las imágenes.

Afortunadamente la programación paralela se ha desarrollado mucho durante los últimos

años. En primera instancia se ha incrementado considerablemente la velocidad de

procesamiento de las Unidades de Procesamiento Central por sus siglas en inglés

CPU’s, adicionalmente se ha incrementado la cantidad de ellas en nuestras computadoras

permitiendo que a la fecha se cumpla la llamada ley de Moore [8]. Al mismo tiempo se

comenzó a hacer uso de las Unidades de Procesamiento Gráfico GPU’s para cómputo de

propósito general, las cuales es sabido contienen cientos de unidades de procesamiento

dotadas de un gran poder de cómputo y canales de datos con gran ancho de banda,

como se puede observar en las siguientes gráficas:

Fig. 1. Operaciones de punto flotante por segundo para CPU

y GPU[3].

Fig. 2. Ancho de banda de los canales de datos para CPU y

GPU[3].

Page 6: TesinaPabloTintor FINAL

6

La diferencia en la capacidad de cómputo entre las CPU’s y las GPU’s radica en que una

GPU está diseñada para realizar computo en paralelo, esto es que puede ejecutar el

mismo programa sobre varias unidades de datos al mismo tiempo. El procesamiento de

imágenes es una de las principales aplicaciones para las GPU’s, grandes conjuntos de

pixeles y vértices pueden ser procesados por hilos de ejecución en paralelo.

En Noviembre 2006, NVIDIA introdujo en el mercado CUDATM, una plataforma de

propósito general y un modelo de programación que aprovecha los recursos del motor de

computo en paralelo de las GPU’s de NVIDIA, solucionando cómputos complejos de una

forma más eficiente que una CPU.

CUDA fue lanzado con un ambiente de software que permite a los desarrolladores usar C

como lenguaje de programación de alto nivel, manteniendo baja la curva de aprendizaje

para programadores familiarizados con este lenguaje de programación.

En su núcleo hay tres conceptos claves -- grupos de hilos jerárquicos, memoria

compartida y sincronización -- que pueden ser vistos por el programador como una

mínima extensión del lenguaje de programación. Estos conceptos llevan al programador a

dividir el problema en sub-problemas que puedan ser resueltos de manera independiente

en bloques paralelos de hilos, y cada sub-problema en pequeñas piezas que puedan

resolverse cooperativamente en paralelo por todos los hilos dentro de un bloque.

De esta manera un programa multi-hilos puede ser dividido en bloques independientes

uno de otro, permitiendo aprovechar mejor las capacidades de computo de las diferentes

GPU’s disponibles en el mercado, es decir una GPU con más multiprocesadores puede

automáticamente ejecutar el mismo programa en menor tiempo que una GPU con menos

multiprocesadores.

OBJETIVO

En este trabajo exponemos las propiedades de los procesadores gráficos, haciendo

énfasis en la explotación de éstos mediante la tecnología CUDA. Esta tecnología se

implementa en la solución de la ecuación de onda acústica por el método de diferencias

finitas.

Page 7: TesinaPabloTintor FINAL

7

PROPIEDADES DE LAS GPU´S

Normalmente una CPU contiene solo 2, 4, 6 o hasta 8 núcleos (ALU´s Unidad Lógica

Aritmética) que comparten una memoria cache y una RAM en común. En una PC, es

posible usar memoria distribuida y modelos de comunicación entre procesos como

OpenMPI o MPI (ver sitio oficial openMPI o la documentación de la Interfaz de Paso de

Mensajes [7]), que utilizan la misma o varias memorias en una o varias computadoras.

Cuando nos enfrentamos a la ejecución de grandes y pesadas rutinas, unos cuantos

núcleos por computadora no son suficientes, entonces nos damos a la tarea de unir varias

para construir un clúster de modo que la carga de trabajo y memoria queda distribuida

sobre cada una de ellas.

Las GPU´s trabajan un poco diferente a las CPU’s. Una GPU contiene cientos de

unidades lógico-aritméticas, agrupadas en bloques que comparten una gran cantidad de

memoria global, sin embargo cada uno contiene memoria local que no puede ser

accedida entre bloques. Dependiendo de las necesidades del usuario las unidades lógico-

aritméticas por sus siglas en inglés ALU´s pueden agruparse en 1, 2 o 3 dimensiones, las

imágenes 3 y 4 muestran una agrupación en 1D y 2D.

Antes de programar una tarjeta gráfica es conveniente conocer sus capacidades y

limitaciones físicas, además de determinar si es la única o existen varias de ellas en

nuestro equipo de cómputo. Se puede determinar la cantidad de estos dispositivos

mediante la función cudaGetDeviceCount. El programa completo se muestra al final de

este tema.

int count; HANDLE_ERROR( cudaGetDeviceCount( &count ) );

Fig.3. Arreglo unidimensional Fig.4. Arreglo bidimensional

Page 8: TesinaPabloTintor FINAL

8

Conocida la cantidad dispositivos dentro del sistema de cómputo, podemos iterar a través

de cada uno de ellos y conocer sus propiedades. Esta información se proporciona en una

estructura de tipo cudaDeviceProp, las propiedades que contiene esta estructura se

muestran en la tabla siguiente.

Propiedad de la GPU Descripción

char name [256] Una cadena en código ASCII que identifica al dispositivo (Ej.,”GeForce GTX 590”).

size_t totalGlobalMem Cantidad de memoria global en el dispositivo en bytes.

size_t sharedMemPerBlock Cantidad máxima en bytes de memoria compartida en un solo bloque.

int regsPerBlock Número de registros de 32-bits disponibles por bloque.

Int warpSize Número de procesos por unidad de cómputo.

Int size_t mem_pitch Tamaño en bytes máximo para copiar memoria.

int maxThreadsPerBlock Máximo de hilos de ejecución que un bloque puede contener.

int maxThreadsDim[3] Número máximo de hilos que un bloque puede contener a lo largo de cada una de sus dimensiones.

int maxGridSize[3] Máximo número de bloques a lo largo de cada dimensión de la malla.

size_t totalConstMem Cantidad de memoria constante disponible.

int major Revisión más a fondo de las capacidades de cómputo del dispositivo.

int minor Revisión mínima de las capacidades de cómputo del dispositivo.

size_t textureAlignment Requerimiento del dispositivo para el alineamiento de textura.

int deviceOverlap Valor booleano para indicar que se puede realizar simultáneamente una llamada a la función cudaMemcpy y la ejecución del kernel.

int multiprocessorCount Número de multiprocesadores en el dispositivo.

Int kernelExecTimeoutEnabled

Valor booleano, indica que existe un límite en el tiempo de ejecución de los kernels ejecutados en el dispositivo.

int integrated Valor booleano que indica si el dispositivo está integrado a la GPU.

int canMapHostMemory Valor booleano que indica si el dispositivo puede mapear memoria de la computadora al espacio de direcciones de CUDA.

int computeMode Valor que representa el modo de cómputo del dispositivo. Por defecto, exclusivo o prohibido.

int maxTexture1D Cantidad máxima de memoria de textura 1D soportada.

int maxTexture2D[2] Dimensiones máximas para memoria de textura 2D soportadas.

int maxTexture3D[3] Dimensiones máximas para memoria de textura 3D soportadas.

int maxTexture2DArray[3] Dimensiones máximas soportadas para arreglos de memoria de textura 2D.

int concurrentKernels Valor booleano que indica si en un momento determinado, el dispositivo puede ejecutar múltiples kernels dentro del mismo contexto, simultáneamente.

Page 9: TesinaPabloTintor FINAL

9

Siendo conscientes de esta información podremos programar nuestras GPU’s, haciendo

mejor uso de sus capacidades y sin sobrepasar sus límites. El código para consultar

nuestros dispositivos lucirá más o menos así:

Además de conocer las capacidades de cómputo de nuestros dispositivos, debemos

tomar en cuenta que existe dentro de las GPU’s memoria de textura que puede ayudar a

mejorar el desempeño si se le usa adecuadamente.

Memoria de textura

La memoria de textura es una variante de memoria de solo lectura que se aloja en la

circuitería del dispositivo, esto puede ayudar a su desempeño y reducir el tránsito de

datos desde la memoria global externa. Esta memoria fue diseñada principalmente para

aplicaciones gráficas, NVIDIA diseño las unidades de textura para interactuar con los

canales de OpenGL y DirectX. Específicamente fue diseñada para procesos que

requieren acceder continuamente a grandes espacios de memoria.

En el caso de la simulación que se lleva a cabo es necesario actualizar las imágenes

( ) accediendo continuamente a las regiones de memoria que contienen a las

imágenes actual ( ) y a la anterior ( ). El acceso a dichas memorias parece ser más

eficiente cuando se alojan en la memoria de textura. En el caso de la propagación de

ondas en 2D será necesario acceder a los nodos vecinos de la izquierda, derecha,

superior e inferior, por lo que el uso de memoria de textura 2D optimiza el indexado de los

nodos y por tanto el acceso es más eficiente que con memoria de textura 1D.

Memoria Compartida.

La memoria compartida después de los registros es la más cercana a los procesos, sin

embargo solo es visible durante la ejecución de un kernel y solo por procesos del mismo

bloque, además de ser muy pequeña, a pesar de tener estas desventajas no deja de ser

la más rápida, por ello es conveniente tenerla en cuenta y analizar en qué tipo de

problemas es conveniente usarla.

El uso de memoria compartida puede presentar problemas de sincronización de procesos,

los cuales pueden generar incongruencia en los datos, la función __syncthreads()

#include "../common/book.h" int main( void ) {

cudaDeviceProp prop; int count; HANDLE_ERROR( cudaGetDeviceCount( &count ) ); for (int i=0; i< count; i++) {

HANDLE_ERROR( cudaGetDeviceProperties( &prop, i ) ); printf(“Numero de procesos por unidad de computo: %i”,prop.warpSize); // El resultado es 32 para el caso de una tarjeta GeForce GT 240

} }

Page 10: TesinaPabloTintor FINAL

10

sincroniza los procesos, de tal manera que antes de ejecutar la siguiente instrucción en el

código todos los proceso del bloque han terminado de ejecutar las instrucciones previas.

Eventos

Los eventos en CUDA son esencialmente marcas de tiempo del GPU. En la simulación

haremos uso de ellos para marcar el inicio y el fin del cómputo de las imágenes que se

mostrarán.

Page 11: TesinaPabloTintor FINAL

11

SOLUCIÓN DE LA ECUACIÓN DE ONDA ACÚSTICA

Sea Ω = [0,1] Χ [0,1], f, u0 ∈ C 0(Ω). Hallar la función ∈ C 2(Ω) que satisface la

ecuación de onda.

Podemos discretizar el problema usando un esquema de diferencias finitas para dar

solución al sistema, tomando como la velocidad de propagación de las ondas.

Dividiremos las direcciones , de Ω en puntos con resultando 1024

intervalos en cada dirección. Denotaremos = = =

al delta espacial y =

al delta temporal. De acuerdo con esto y , ver

Laurent Michel 2011[1]. La aproximación numérica queda de la siguiente manera:

Sustituyendo en la primera ecuación y despejando tenemos que

(

)

Fig. 5. Valor del punto i,j

en el tiempo anterior. Fig. 6. Valores del punto i,j y

sus vecinos en el tiempo actual. Fig.7. Valor del punto i,j calculado

en base a los tiempos anteriores.

Page 12: TesinaPabloTintor FINAL

12

Donde

Que para los tiempos t=0.0, t=0.5 y t=1.0 se ve como lo muestran las siguientes figuras.

Condiciones iniciales

Para las condiciones iniciales decidí graficar un aro en el centro usando la función.

(

)

Donde

Fronteras absorbentes.

En el mejor de los casos las ondas incidentes en los bordes deben ser absorbidas en su

totalidad, imitando una continuidad en el espacio de propagación, sin embargo al ser

nuestra malla un espacio finito las ondas se ven reflejadas. La forma de hacer frente a

este problema será implementar una zona de absorción como se muestra en la figura de

abajo.

Las barreras absorbentes presentan la siguiente función , donde es el

índice del nodo a partir del borde más cercano y es el tamaño de las barreras

absorbentes, ver Cêrjan 1986[6].

x

Espacio no

absorbente Banda con celdas absorbentes

Fig. 8. f(x,y,t) en el inicio

de la simulación. t=0.0

Fig. 9.f(x,y,t) en el tiempo medio

de la simulación t=0.5

Fig. 10.f(x,y,t) en el fin

de la simulación t=1.0

Fig. 12. Espacio finito de propagación de las ondas, las bandas absorbentes ayudan a simular una continuidad en el espacio, según Cêrjan 1986 [6].

Fig. 11. Gráfica de la función u0 que establece las condiciones iniciales para la simulación.

x

x y

x

y

y

t t

t

y

t

Page 13: TesinaPabloTintor FINAL

13

Condición de Courant

El método de diferencias finitas tiene la dificultad de los problemas de inestabilidad. Las

soluciones inestables se presentan si es relativamente grande respecto a . La

relación a cumplir entre estos dos parámetros se conoce como la condición de Courant.

√ donde es la velocidad de propagación de las ondas.

En el caso de esta simulación

por tanto

√ cumple la

condición de Courant.

Definición de la malla

Para esta simulación he definido una imagen de tamaño 1024 x 1024, la cual será dividida

en 64 bloques con 16 procesos cada uno, que serán responsables de una región de

memoria representada por los puntos negros de la siguiente imagen.

Fig. 13. Agrupación de los procesos paralelos en los bloques de memoria definidos.

Page 14: TesinaPabloTintor FINAL

14

PROGRAMACIÓN DE LA ECUACIÓN DE ONDA ACÚSTICA

En el código utilizado para el censo de los dispositivos del sistema, podemos observar

que la sintaxis CUDA no es muy diferente a la del lenguaje C. El uso de variables,

constantes, estructuras, sentencias de control de flujo, uso de bibliotecas, entre otras

cosas es muy similar en ambos lenguajes, tal como podremos apreciar a continuación. La

documentación completa de CUDA se puede consultar en la página oficial de NVIDIA [3].

El código hasta aquí mostrado es ejecutado en la CPU y su memoria. Según la

documentación de CUDA, un kernel es código compilado para ser ejecutado

simultáneamente en los microprocesadores de la GPU, aplicar la misma instrucción sobre

secciones de memoria diferentes. Veamos el kernel que calcula .

La firma de este método no es muy diferente a uno escrito en lenguaje C, pero podemos

notar una importante alteración que es el calificador __global__. Este mecanismo alerta

al compilador para que el método sea compilado para ser ejecutado en la GPU.

El paso de parámetros a estas funciones se asemeja a la manera del lenguaje C. Sin

embargo en la invocación de un kernel con la sintaxis <<<m , n>>>, indicamos que

#include "../common/book.h" #include "../common/gpu_anim.h" /*DEFINIMOS EL NÚMERO DE THREADS Y EL NÚMERO DE BLOQUES PARA NUESTROS KERNELS **RESPETANDO: n_blocks = DIM / n_threads */ #define _nthreads 16 #define _nblocks 64 #define DIM 1024 /* ** N = DIM-1: Tamaño de la malla ** h = 1.0/N: Delta espacial ** tf: Tiempo final ** dt = h/2.0 : Delta temporal */ #define N 1023 #define h 1.0f/(float)N #define tf 1.0f #define dt h/2.0f float t=0; //Tiempo

__global__ void evolve(float *n, float t, int step) {

//Código que se ejecuta en GPU

}

//...Código

evolve<<<blocks, threads>>>(data->dev_uNext,t,0);

//...Código

Page 15: TesinaPabloTintor FINAL

15

ejecutaremos el programa dividiendo la sección de memoria original en “m” bloques y que

cada uno contendrá “n” procesos paralelos trabajando cooperativamente, teniendo

cuidado de no sobrepasar los límites físicos de nuestro dispositivo. Ver Fig. 4 “Arreglo

bidimensional”.

La memoria global, es el tipo de memoria de uso más general con la que cuenta una

GPU. Al igual que reservamos la memoria RAM de un CPU para los arreglos de un

programa escrito en C, podemos reservar memoria global para la ejecución de un kernel

con la función cudaMalloc. Veamos un ejemplo de su uso.

En este ejemplo con la función cudaMalloc alojamos dentro de la memoria del

dispositivo, el vector que guardará los valores de a lo largo del programa.

También haremos uso de la memoria de textura para mejorar el desempeño de nuestro

dispositivo. Veamos un ejemplo del uso de esta memoria.

En este ejemplo declaramos texOld, un bloque de memoria de textura 2D. Después

inicializamos este bloque y creamos la conexión con el bloque de memoria global

dev_uOld, mediante la función cudaBindTexture2D. Finalmente leemos datos de ella con

la función tex2D.

He descrito y ejemplificado la sintaxis que usaremos en los cálculos de esta modelación,

entonces podemos describir las funciones y utilidades que CUDA ofrece para realizar los

gráficos y la animación de este trabajo, basado en las API’s del libro “CUDA BY

EXAMPLE” [2]. Aunque existen otras plataformas para visualizar los resultados como

Matlab, gnuplot, dislin, etc. CUDA hace este trabajo más eficiente y dinámico utilizando

los recursos de la GPU, evitando la transferencia de datos entre CPU y GPU.

HANDLE_ERROR( cudaMalloc( (void**)&data.dev_uOld,1024*1024*sizeof(float) ) );

//…Código

texture<float,2> texOld; //Memoria de textura para u^n-1

//…Código

HANDLE_ERROR( cudaBindTexture2D( NULL,

texOld,data.dev_uOld,desc,DIM,DIM,DIM*sizeof(float)) );

//…Código

oidx = tex2D(texOld,i,j);

Page 16: TesinaPabloTintor FINAL

16

Animación

Teniendo en cuenta las dimensiones de nuestra malla, primero definimos un cuadro que

graficará los mapas de bits correspondientes a las imágenes calculadas por cada paso de

tiempo durante la simulación.

La función anim_gpu es llamada por el cuadro de animación cada vez que se muestra una

nueva imagen, anim_exit es llamada al final de la animación para liberar los recursos

utilizados y anim_and_exit llama a estas dos rutinas para realizar la animación.

Los argumentos de esta función son: un apuntador a una estructura DataBlock y un

entero ticks que cuenta las imágenes transcurridas, debido a que DataBlock contiene

los cálculos de , , en sus respectivos arreglos, el parámetro ticks no es

utilizado.

Ya que usamos memoria de textura, no podemos pasar parámetros que requieran

grandes cantidades de memoria, por ello alternamos , , tres veces de manera

que al terminar cada una retome su lugar, indicamos esta alternancia con un numero 0,1,2

al método evolve que lleva a cabo el cómputo de la propagación. Incrementamos la

variable t después de cada ejecución del kernel evolve.

Al llegar a la frontera las ondas rebotan, para evitarlo lanzamos el kernel siguiente:

GPUAnimBitmap bitmap( DIM, DIM, &data );//creamos una imagen de tamaño DIM*DIM

bitmap.anim_and_exit( (void (*)(uchar4*,void*,int))anim_gpu,(void (*)(void*))anim_exit );

void anim_gpu( uchar4* outputBitmap, DataBlock *data, int ticks ) {

//Código de animación

}

if(paso==0){ evolve<<<blocks, threads>>>(data->dev_uNext,t,0,ticks); in=data->dev_uNext; incrementDT(); } if(paso==1){ evolve<<<blocks, threads>>>(data->dev_uOld,t,1,ticks); in=data->dev_uOld; incrementDT(); } if(paso==2){ evolve<<<blocks, threads>>>(data->dev_uCurr,t,2,ticks);

in=data->dev_uCurr;

incrementDT();

}

paso++;

frontera<<<blocks, threads>>>(in,20,20,0.035);

Page 17: TesinaPabloTintor FINAL

17

La función up_Color permite tener colores llamativos sin afectar el cómputo de la

propagación, la función float_to_color mapea valores de punto flotante en pixeles de

color susceptibles de graficar y la función sleepP retarda la simulación.

Elemento de animación Descripción GPUAnimBitmap bitmap Estructura CUDA que define el cuadro de

animación y sus funciones. DataBlock data Estructura definida por el usuario utilizada

por el GPUAnimBitmap para guardar los buffers de lectura y escritura de datos para la animación, además de variables particulares de la animación.

anim_gpu Rutina de animación definida por el usuario es llamada cada vez que se calcula una nueva imagen.

anim_exit Rutina de liberación de recursos, una vez que se termina la animación será necesario liberar los buffers y las variables de animación.

anim_and_exit Función para llamar a las dos rutinas anteriores, anim_gpu y anim_and_exit.

Esta fue la forma de graficar y animar la propagación de ondas acústicas. En las

siguientes páginas se muestran un diagrama de flujo y el código fuente de este programa.

upColor<<<blocks, threads>>>(data->dev_color,in);

float_to_color<<<blocks,threads>>>(outputBitmap,data->dev_color);

sleepP(10000);

Page 18: TesinaPabloTintor FINAL

18

Diagrama de flujo de la operación del programa presentado.

Page 19: TesinaPabloTintor FINAL

19

Código fuente

#include "../common/book.h" #include "../common/gpu_anim.h" #include <stdio.h> #include <stdlib.h> /*DEFINIMOS EL NUMERO DE THREADS Y EL NUMERO DE BLOQUES PARA NUESTROS KERNELS **RESPETANDO: n_blocks = DIM / n_threads */ #define DIM 1024 #define _nthreads 16 #define _nblocks (DIM/_nthreads) /* ** N = DIM-1: Tamaño de la malla ** h = 1.0/N: Delta espacial ** tf: Tiempo final ** dt = h/2.0 : Delta temporal */ #define N (DIM-1) #define h (1.0f/(float)N) #define tf 1.0f #define dt (h/2.0f) texture<float,2> texOld; //Memoria de textura para u^n-1 texture<float,2> texCurr;//Memoria de textura para u^n texture<float,2> texNext;//Memoria de textura para u^n+1 float t=0; //Tiempo int paso=0; /* *Estructura DataBlock usada para *guardar datos para realizar la animación */ struct DataBlock { float *dev_uOld; float *dev_uCurr; float *dev_uNext; float *host_uOld; float *host_uCurr; float *host_uNext; float *dev_color; cudaEvent_t start, stop; float totalTime; float frames; };

Page 20: TesinaPabloTintor FINAL

20

/*Función f(ih,ij,ndt)

** x = i*h

** y = j*h

** t = n*dt

**/

__device__ float f(float *x, float *y, float *t) {

return expf(-*t)*(*x*(*x-1.0f)**y*(*y-1.0f) - 2.0f*(*y*(*y-1.0f)+*x*(*x-1.0f)));

}

/*Función equivalente a f(ih,ij,ndt) pero ejecutada en el host

** x = i*h

** y = j*h

** t = n*dt

**/

float g(float *x, float *y, float *t) {

return expf(-*t)*(*x*(*x-1.0f)**y*(*y-1.0f) - 2.0f*(*y*(*y-1.0f)+*x*(*x-1.0f)));

}

/*Método para convertir un arreglo de tipo float en un arreglo de tipo uchar4

**con las escalas de grises equivalentes, susceptible de graficar.

**uchar4 *in arreglo donde se guarda el mapeo float-escala de grises

**float *ou arreglo original para realizar el mapeo float-escala de grises

*/

__global__ void float_to_grey(uchar4* in,float* ou){

// mapeo de threadIdx/BlockIdx a coordenas x,y de los pixeles

int x = threadIdx.x + blockIdx.x * blockDim.x;

int y = threadIdx.y + blockIdx.y * blockDim.y;

int idx = x + y * blockDim.x * gridDim.x;

if(idx > DIM*DIM) return;

in[idx].x=(1.0f-ou[idx])*255;

in[idx].y=(1.0f-ou[idx])*255;

in[idx].z=(1.0f-ou[idx])*255;

in[idx].w=255;

}

Page 21: TesinaPabloTintor FINAL

21

/*Método para establecer las condiciones iniciales. **Estable la fuerza que inicia la propagación de las ondas. **Es un aro blanco en el centro. **float u arreglo en el que se guardan las condiciones iniciales */ __global__ void init(float *u){ // mapeo de threadIdx/BlockIdx a coordenas x,y de los pixeles int x = threadIdx.x + blockIdx.x * blockDim.x; int y = threadIdx.y + blockIdx.y * blockDim.y; int idx = x + y * blockDim.x * gridDim.x; if(idx > DIM*DIM) return; //movemos el origen de coordenadas al centro de la imagen float fx = x - DIM/2; float fy = y - DIM/2; float d = sqrt(fx * fx + fy * fy); float grey = cos(d/10.0f - 100.0f/7.0f) /(d/10.0f + 1.0f); //grey=fx*(fx-1)+fy*(fy-1); u[idx]=grey; if(u[idx]<0.20f)u[idx]=0.0f;//Eliminamos puntos basura. else u[idx]=1.0f;//Subimos el color de los puntos importantes al máximo. } /*Método para subir el color de las ondas **float *color arreglo modificado **float *in arreglo original */ __global__ void upColor(float *color, float *in){ // mapeo de threadIdx/BlockIdx a coordenas x,y de los pixeles int x = threadIdx.x + blockIdx.x * blockDim.x; int y = threadIdx.y + blockIdx.y * blockDim.y; int idx = x + y * blockDim.x * gridDim.x; if(idx > DIM*DIM) return; float px=2.0f*in[idx];//Subimos el color de los puntos. if(px>1.0f)px=1.0f; color[idx]=px; } /*Método para incrementar el tiempo */ bool incrementDT() { float tmp = t+dt; if(tmp < tf) { t=tmp; return false; } return true; }

Page 22: TesinaPabloTintor FINAL

22

//Método para contener las ondas, al llegar a las mallas //de contención las ondas se debilitan __global__ void frontera(float *in, int nxa,int nya,float a){ // mapeo de threadIdx/BlockIdx a coordenas x,y de los pixeles int x = threadIdx.x + blockIdx.x * blockDim.x; int y = threadIdx.y + blockIdx.y * blockDim.y; int idx = x + y * blockDim.x * gridDim.x; if(idx > DIM*DIM) return; float c=1.0f; if(x<nxa) c=expf(-((a*(nxa-x))*((nxa-x)))); if(y<nya) c=expf(-((a*(nya-y))*((nya-y)))); if(x>DIM-nxa+1) c=expf(-((a*(x-DIM+nxa-1))*((x-DIM+nxa-1)))); if(y>DIM-nya+1) c=expf(-((a*(y-DIM+nya-1))*((y-DIM+nya-1)))); in[idx]=c*in[idx]; } /* *Método para calcular la propagación de onda acústica secuencialmente */ void evolve_s(float *old, float *curr, float *next, float t) { //Inicia uso de apuntadores float g(float *,float *, float*); float ih; float jh; float *pih; float *pjh; float *pt; float oidx,cidx,cleft,cright,cbottom,ctop; //Termina uso de apuntadores for(int i=0;i<(DIM*DIM);i++){ int x = i % DIM; int y = i / DIM; if(x==0||x==(DIM-1)||y==0||y==(DIM-1)){ next[i]=0; } else{ oidx = old[i]; cidx = curr[i]; cleft = curr[i-1]; cright = curr[i+1]; cbottom = curr[i+DIM]; ctop = curr[i-DIM]; //Diferencias finitas pih=&ih; pjh=&jh; pt=&t; ih=h*x; jh=h*y; next[i]= -oidx + 2*cidx + (dt*dt)*(N*N*(cleft + cright + cbottom + ctop- 4 * cidx)+g(pih, pjh, pt)); } } }

Page 23: TesinaPabloTintor FINAL

23

/* *Método para calcular la propagación de onda acústica haciendo uso *de memoria global. */ __global__ void evolve_gm(float *old, float *curr, float *next, float t) { // mapeo de threadIdx/BlockIdx a coordenas x,y de los pixeles int i = threadIdx.x + blockIdx.x*blockDim.x; int j = threadIdx.y + blockIdx.y*blockDim.y; int idx = i + j*blockDim.x*gridDim.x; if(idx > DIM*DIM) return; //Puntos en la frontera if(i==0 || i == DIM-1 || j==0 || j == DIM-1) { next[idx] = 0.0f; return; } //Inicia uso de punteros float f(float *,float *, float*); float ih=i*h; float jh=j*h; float *pih; float *pjh; float *pt; pih=&ih; pjh=&jh; pt=&t; //Termina uso de apuntadores

float oidx,cidx,cleft,cright,cbottom,ctop;

oidx = old[idx];

cidx = curr[idx];

cleft = curr[idx-1];

cright = curr[idx+1];

cbottom = curr[idx+DIM];

ctop = curr[idx-DIM];

//Diferencias finitas

next[idx]= -oidx + 2*cidx + (dt*dt)*(N*N*(cleft + cright + cbottom + ctop- 4 * cidx)+f(pih, pjh, pt));

}

Page 24: TesinaPabloTintor FINAL

24

/*Método principal, aquí se calcula u^n+1 con memoria compartida **float *n arreglo en el que guardaremos el cálculo de u^n+1 **t tiempo actual transcurrido del inicio de propagación n*dt **step u^n-1,u^n,u^n+1 se alternan de acuerdo a este parámetro */ __global__ void evolve_sh(float *n, float t,int step,int ticks) { // mapeo de threadIdx/BlockIdx a coordenas x,y de los pixeles int i = threadIdx.x + blockIdx.x*blockDim.x; int j = threadIdx.y + blockIdx.y*blockDim.y; int idx = i + j*blockDim.x*gridDim.x; if(idx > DIM*DIM) return; __shared__ float sh_old[_nthreads][_nthreads]; __shared__ float sh_curr[_nthreads][_nthreads]; __shared__ float sh_next[_nthreads][_nthreads]; if(threadIdx.x==_nthreads-1){ sh_old[threadIdx.x+2][threadIdx.y]=tex2D(texOld,i+1,j); sh_curr[threadIdx.x+2][threadIdx.y]=tex2D(texCurr,i+1,j); sh_next[threadIdx.x+2][threadIdx.y]=tex2D(texNext,i+1,j); } if(threadIdx.y==_nthreads-1){ sh_old[threadIdx.x][threadIdx.y+2]=tex2D(texOld,i,j+1); sh_curr[threadIdx.x][threadIdx.y+2]=tex2D(texCurr,i,j+1); sh_next[threadIdx.x][threadIdx.y+2]=tex2D(texNext,i,j+1); } if(threadIdx.x==0){ sh_old[threadIdx.x][threadIdx.y]=tex2D(texOld,i-1,j); sh_curr[threadIdx.x][threadIdx.y]=tex2D(texCurr,i-1,j); sh_next[threadIdx.x][threadIdx.y]=tex2D(texNext,i-1,j); } if(threadIdx.y==0){ sh_old[threadIdx.x][threadIdx.y]=tex2D(texOld,i,j-1); sh_curr[threadIdx.x][threadIdx.y]=tex2D(texCurr,i,j-1); sh_next[threadIdx.x][threadIdx.y]=tex2D(texNext,i,j-1); } sh_old[threadIdx.x+1][threadIdx.y+1]=tex2D(texOld,i,j); sh_curr[threadIdx.x+1][threadIdx.y+1]=tex2D(texCurr,i,j); sh_next[threadIdx.x+1][threadIdx.y+1]=tex2D(texNext,i,j); __syncthreads();

//Puntos en la frontera

if(i==0 || i == DIM-1 || j==0 || j == DIM-1) { n[idx] = 0.0f; return; }

Page 25: TesinaPabloTintor FINAL

25

//Obtenemos los datos de la memoria de textura float oidx,cidx,cleft,cright,cbottom,ctop; if(step==0){ oidx = sh_old[threadIdx.x+1][threadIdx.y+1]; cidx = sh_curr[threadIdx.x+1][threadIdx.y+1]; cleft = sh_curr[threadIdx.x][threadIdx.y+1]; cright = sh_curr[threadIdx.x+2][threadIdx.y+1]; cbottom = sh_curr[threadIdx.x+1][threadIdx.y+2]; ctop = sh_curr[threadIdx.x+1][threadIdx.y]; } if(step==1){ oidx = sh_curr[threadIdx.x+1][threadIdx.y+1]; cidx = sh_next[threadIdx.x+1][threadIdx.y+1]; cleft = sh_next[threadIdx.x][threadIdx.y+1]; cright = sh_next[threadIdx.x+2][threadIdx.y+1]; cbottom = sh_next[threadIdx.x+1][threadIdx.y+2]; ctop = sh_next[threadIdx.x+1][threadIdx.y]; } if(step==2){ oidx = sh_next[threadIdx.x+1][threadIdx.y+1]; cidx = sh_old[threadIdx.x+1][threadIdx.y+1]; cleft = sh_old[threadIdx.x][threadIdx.y+1]; cright = sh_old[threadIdx.x+2][threadIdx.y+1]; cbottom = sh_old[threadIdx.x+1][threadIdx.y+2]; ctop = sh_old[threadIdx.x+1][threadIdx.y]; } __syncthreads(); //Inicia uso de punteros float f(float *,float *, float*); float ih=i*h; float jh=j*h; float *pih; float *pjh; float *pt; pih=&ih; pjh=&jh; pt=&t; //Termina uso de punteros //Diferencias finitas n[idx]= -oidx + 2*cidx + (dt*dt)*(N*N*(cleft + cright + cbottom + ctop- 4 * cidx)+f(pih, pjh, pt)); }

Page 26: TesinaPabloTintor FINAL

26

/*Método principal, aquí se calcula u^n+1 **float *n arreglo en el que guardaremos el cálculo de u^n+1 **t tiempo actual transcurrido del inicio de propagación n*dt **step u^n-1,u^n,u^n+1 se alternan de acuerdo a este parámetro */ __global__ void evolve(float *n, float t,int step,int ticks) { // mapeo de threadIdx/BlockIdx a coordenas x,y de los pixeles int i = threadIdx.x + blockIdx.x*blockDim.x; int j = threadIdx.y + blockIdx.y*blockDim.y; int idx = i + j*blockDim.x*gridDim.x; if(idx > DIM*DIM) return; //Puntos en la frontera if(i==0 || i == DIM-1 || j==0 || j == DIM-1) { n[idx] = 0.0f; return; } //Obtenemos los datos de la memoria de textura float oidx,cidx,cleft,cright,cbottom,ctop; if(step==0){ oidx = tex2D(texOld,i,j); cidx = tex2D(texCurr,i,j); cleft = tex2D(texCurr,i-1,j); cright = tex2D(texCurr,i+1,j); cbottom = tex2D(texCurr,i,j-1); ctop = tex2D(texCurr,i,j+1); } if(step==1){ oidx = tex2D(texCurr,i,j); cidx = tex2D(texNext,i,j); cleft = tex2D(texNext,i-1,j); cright = tex2D(texNext,i+1,j); cbottom = tex2D(texNext,i,j-1); ctop = tex2D(texNext,i,j+1); } if(step==2){ oidx = tex2D(texNext,i,j); cidx = tex2D(texOld,i,j); cleft = tex2D(texOld,i-1,j); cright = tex2D(texOld,i+1,j); cbottom = tex2D(texOld,i,j-1); ctop = tex2D(texOld,i,j+1); } //Inicia uso de punteros float f(float *,float *, float*); float ih=i*h; float jh=j*h; float *pih; float *pjh; float *pt; pih=&ih; pjh=&jh; pt=&t; //Termina uso de punteros //Diferencias finitas n[idx]= -oidx + 2*cidx + (dt*dt)*(N*N*(cleft + cright + cbottom + ctop- 4 * cidx)+f(pih, pjh, pt)); }

Page 27: TesinaPabloTintor FINAL

27

//Método para retrasar la simulación void sleepP(unsigned int mseconds) { clock_t goal = mseconds + clock(); while (goal > clock()); } //Rutina de para graficar y animar los cómputos void anim_gpu( uchar4* outputBitmap, DataBlock *data, int ticks ) { //if(t==0.0f)system("pause"); HANDLE_ERROR( cudaEventRecord( data->start, 0 ) ); dim3 blocks(_nblocks, _nblocks); dim3 threads(_nthreads, _nthreads); float *in; paso=paso%3; //lanzamos el kernel evolve para calcular u^n+1 if(paso==0){ evolve<<<blocks, threads>>>(data->dev_uNext,t,0,ticks); //evolve_gm<<<blocks, threads>>>(data->dev_uOld,data->dev_uCurr,data->dev_uNext,t); //evolve_s(data->host_uOld,data->host_uCurr,data->host_uNext,t); //HANDLE_ERROR( cudaMemcpy( data->dev_uNext, data->host_uNext,DIM*DIM*sizeof(float),cudaMemcpyHostToDevice ) ); in=data->dev_uNext; incrementDT(); } if(paso==1){ evolve<<<blocks, threads>>>(data->dev_uOld,t,1,ticks); //evolve_gm<<<blocks, threads>>>(data->dev_uCurr,data->dev_uNext,data->dev_uOld,t); //evolve_s(data->host_uCurr,data->host_uNext,data->host_uOld,t); //HANDLE_ERROR( cudaMemcpy( data->dev_uOld, data->host_uOld,DIM*DIM*sizeof(float),cudaMemcpyHostToDevice ) ); in=data->dev_uOld; incrementDT(); } if(paso==2){ evolve<<<blocks, threads>>>(data->dev_uCurr,t,2,ticks); //evolve_gm<<<blocks, threads>>>(data->dev_uNext,data->dev_uOld,data->dev_uCurr,t); //evolve_s(data->host_uNext,data->host_uOld,data->host_uCurr,t); //HANDLE_ERROR( cudaMemcpy( data->dev_uCurr, data->host_uCurr,DIM*DIM*sizeof(float),cudaMemcpyHostToDevice ) ); in=data->dev_uCurr; incrementDT(); } paso++; //Debilitamos las ondas que estan en la frontera frontera<<<blocks, threads>>>(in,10,10,0.015f); //Subimos el color de las ondas upColor<<<blocks, threads>>>(data->dev_color,in); //Convertimos valores float en pixeles de color float_to_color<<<blocks,threads>>>(outputBitmap,data->dev_color); HANDLE_ERROR(cudaEventRecord( data->stop, 0 ) ); HANDLE_ERROR( cudaEventSynchronize( data->stop ) ); float elapsedTime; HANDLE_ERROR( cudaEventElapsedTime( &elapsedTime,data->start, data->stop ) ); data->totalTime += elapsedTime; ++data->frames; printf( "Average Time per frame: %3.12f ms\n",data->totalTime/data->frames ); //Retrasamos la animación sleepP(10000); }

Page 28: TesinaPabloTintor FINAL

28

//Rutina para liberar recursos void anim_exit( DataBlock *data ) { HANDLE_ERROR( cudaUnbindTexture( texOld ) ); HANDLE_ERROR( cudaUnbindTexture( texCurr ) ); HANDLE_ERROR( cudaUnbindTexture( texNext ) ); HANDLE_ERROR( cudaFree( data->dev_uOld) ); HANDLE_ERROR( cudaFree( data->dev_uCurr ) ); HANDLE_ERROR( cudaFree( data->dev_uNext ) ); HANDLE_ERROR( cudaEventDestroy( data->start ) ); HANDLE_ERROR( cudaEventDestroy( data->stop ) ); } int main (int argc, char *argv[]) { DataBlock data; //Estructura usada por anim_gpu GPUAnimBitmap bitmap( DIM, DIM, &data );//creamos una imagen de tamaño DIM*DIM data.totalTime = 0; data.frames = 0; HANDLE_ERROR( cudaEventCreate( &data.start ) ); HANDLE_ERROR( cudaEventCreate( &data.stop ) ); int imageSize = bitmap.image_size(); //Asignamos memoria global HANDLE_ERROR( cudaMalloc( (void**)&data.dev_uOld,DIM*DIM*sizeof(float) ) ); HANDLE_ERROR( cudaMalloc( (void**)&data.dev_uCurr,DIM*DIM*sizeof(float) ) ); HANDLE_ERROR( cudaMalloc( (void**)&data.dev_uNext,DIM*DIM*sizeof(float) ) ); HANDLE_ERROR( cudaMalloc( (void**)&data.dev_color,DIM*DIM*sizeof(float) ) ); //Ligamos la memoria de textura a la memoria glogal cudaChannelFormatDesc desc = cudaCreateChannelDesc<float>(); HANDLE_ERROR( cudaBindTexture2D( NULL, texOld,data.dev_uOld,desc,DIM,DIM,DIM*sizeof(float)) ); HANDLE_ERROR( cudaBindTexture2D( NULL, texCurr,data.dev_uCurr,desc,DIM,DIM,DIM*sizeof(float) ) ); HANDLE_ERROR( cudaBindTexture2D( NULL, texNext,data.dev_uNext,desc,DIM,DIM,DIM*sizeof(float) ) ); //Asignamos memoria RAM data.host_uOld = (float*)malloc( DIM*DIM*sizeof(float)); data.host_uCurr = (float*)malloc( DIM*DIM*sizeof(float)); data.host_uNext = (float*)malloc( DIM*DIM*sizeof(float)); //Inicializamos// dim3 threads(_nthreads,_nthreads); dim3 blocks(_nblocks,_nblocks); //Establecemos condiciones iniciales init<<<blocks, threads>>>(data.dev_uOld); init<<<blocks, threads>>>(data.dev_uCurr); HANDLE_ERROR( cudaMemcpy( data.host_uCurr, data.dev_uCurr,DIM*DIM*sizeof(float),cudaMemcpyDeviceToHost ) ); HANDLE_ERROR( cudaMemcpy( data.host_uOld, data.dev_uOld,DIM*DIM*sizeof(float),cudaMemcpyDeviceToHost ) ); //Llamamos a la rutina de animación bitmap.anim_and_exit( (void (*)(uchar4*,void*,int))anim_gpu,(void (*)(void*))anim_exit ); return 0; }

Page 29: TesinaPabloTintor FINAL

29

RESULTADOS

El objetivo principal de este trabajo fue hacer uso de las propiedades de una GPU para

acelerar el cómputo de la propagación de ondas acústicas en un sistema 2D. El cómputo

de esta simulación fue realizado en una GPU GeForce GT240 (modificar las constantes

n_threads y n_blocks, si se cuenta con un dispositivo de mayores capacidades). A

continuación se muestran los tiempos de cómputo obtenidos para los tres algoritmos:

evolve hace uso de la GPU y memoria de textura, evolve_gm hace uso de la GPU y

memoria global, evolve_s hace uso de una CPU (AMDFx-4100 Quad-Core Processor) y

su memoria RAM.

Tamaño de imagen

Tiempo de cálculo en milisegundos

evolve GPU + Mem.

Textura

evolve_gm GPU + Mem.

Global

evolve_sh GPU + Mem. Compartida

evolve_s CPU + RAM

64 x 64 px 0.03 0.03 0.04 1.04

128 x 128 px 0.07 0.08 0.10 5.37

256 x 256 px 0.18 0.19 0.29 21.62

512 x 512 px 0.58 0.65 0.99 84.5

1024 x 1204 px 2.17 2.42 3.81 335.75

2048 x 2048 px 8.52 9.62 15.14 1342.81

La siguiente gráfica muestra la superioridad de cómputo de una GPU sobre una CPU para

diferentes tamaños de imagen.

0.01

0.1

1

10

100

1000

10000

64 128 256 512 1024 2048

Comparación de Tiempos de Cálculo

GPU + Mem Textura

GPU + Mem Global

GPU + Mem Compartida

CPU + RAM

Fig. 14. Tiempos de cómputo de los algoritmos para diferentes tamaños de imagen. El tiempo es en milisegundos por imagen.

Fig. 15. Gráfica de comparación de tiempos de cómputo para los algoritmos definidos.

Page 30: TesinaPabloTintor FINAL

30

La superioridad de cómputo en paralelo de una GPU sobre una CPU queda comprobada

en los resultados anteriores, una GPU (GeForce GT240) requiere el 2.88% del tiempo

requerido por una CPU (AMDFx-4100 Quad-Core Processor) para procesar imágenes de

64x64 px, mientras que para imágenes de 2048x2048 px el tiempo requerido por la GPU

es el 0.63% del tiempo requerido por la CPU. A continuación se muestran los resultados

arrojados por la herramienta de gráficos.

t = 0.00978 t = 0.005865 t = 0.011730 t =0.020039

t=0.038612 t = 0.074780 t = 0.112903 t = 0.173020

t = 0.320137 t = 0.478983 t = 0.521994

CONCLUSIONES

La velocidad de cómputo en paralelo de una GPU tiene mayor potencial sobre

implementaciones hechas en Matlab o C. Conforme los ambientes de programación en

CUDA vayan progresando, será más fácil programar código ejecutable en tarjetas gráficas

que utilicen de manera eficiente los recursos que ofrecen. Permitiéndonos realizar

cómputos extensos, en el mejor de los casos sin interactuar con los recursos del sistema

que las alberga, o en un caso menos ideal disminuyendo el uso de los mismos,

liberándolos para la realización de otras tareas, tal como se hizo en esta simulación.

Fig. 15. Imágenes en diferentes momentos de la simulación.

Page 31: TesinaPabloTintor FINAL

31

El uso de memoria de textura facilita el acceso a los datos a las unidades de

procesamiento de las GPU’s, mejorando su desempeño en la realización de los cálculos,

ya que se encuentra integrada en la circuitería del dispositivo, disminuye el tránsito de los

datos a diferencia de la memoria global que es externa. Hacer uso de ella es una tarea

sencilla con la función cudaBindTexture2D para ligarla a la memoria global y tex2D para

leer datos de ella. En el caso de la presente simulación la diferencia no es muy notoria sin

embargo si hubo una mejora al desempeño de la GPU.

El procesamiento en paralelo de las GPU’s es la principal aceleración de los cómputos y

el motivo principal de trabajar con ellas, ya que dividimos el área de trabajo en bloques y

en cada uno de estos ejecutamos cierta cantidad de procesos que trabajan al mismo

tiempo y cooperativamente. Utilizar esta técnica para definir una malla bidimensional

sobre secciones de memoria unidimensionales, conlleva a realizar un mapeo de índices a

coordenadas (x,y), afortunadamente CUDA cuenta con los mecanismos necesarios para

facilitar esta tarea.

El modelado de la propagación de ondas acústicas por el método de diferencias finitas

presenta problemas de discretización y de recursos finitos, por ello fue importante hacer

uso de la condición de Courant e implementar barreras absorbentes en los bordes.

Page 32: TesinaPabloTintor FINAL

32

APENDICE A

INSTALACIÓN Y CONFIGURACIÓN DE CUDA

Este manual fue probado en una PC con sistema operativo Ubuntu 10.10.

1. Verificar la lista de GPU’s compatibles con CUDA.

https://developer.nvidia.com/cuda-gpus

2. Descargar los drivers de nuestra tarjeta gráfica desde:

http://la.nvidia.com/Download/index.aspx

3. Limpiar todo lo referente a NVIDIA y apagar el X server, para no generar

conflictos.

aptitude remove nvidia*

aptitude purge xserver-xorg-video-nouveau

/etc/init.d/lightdm stop

Nota: Si no se tiene instalado aptitude ejecutar.

sudo apt-get install aptitude

4. Dar permisos de ejecución y ejecutar el instalador de drivers.

chmod +x NVIDIA-Linux-x86-304.64.run

./NVIIDIA-Linux-x86-304.64.run

5. Descargar CUDA Toolkit y SDK de:

Page 33: TesinaPabloTintor FINAL

33

https://developer.nvidia.com/cuda-toolkit-archive

6. Dar permisos de ejecución y ejecutar el instalador de CUDA Toolkit.

chmod +x cudatoolkit_4.2.9_linux32_ubuntu11.04.run

./cudatoolkit_4.2.9_linux32_ubuntu11.04.run

7. Terminada la instalación, procedemos a configurar el Path.

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${CUDA}/lib:${CUDA}/lib64

export PATH=PATH:{cuda}/bin

8. Dar permisos de ejecución y ejecutar el instalador de SDK.

chmod +x gpucomputingsdk_4.2.9_linux.run

./ gpucomputingsdk_4.2.9_linux.run

Durante el proceso el instalador preguntará si se desea instalar los

complementos, Aceptar la instalación de ellos.

libgl1-mesa-dev

libglu1-mesa-dev

libxmu-dev

freeglut3-dev

libxi-dev

9. Tan pronto esté listo, moverse a la carpeta donde se instalaron los programas y

ejecutamos los comandos:

cd C

Para entrar a la carpeta que contiene los códigos de ejemplo.

make

Para compilar los ejemplos del SDK.

Nota: Este manual muestra la forma de instalación que se realizó en el año 2010 para la

computadora donde se desarrolló el presente trabajo, la cual cuenta con un Sistema

Operativo Ubuntu 10.10. Para versiones más recientes del instalador, así como los

manuales de instalación para otros sistemas operativos se recomienda la siguiente página

del sitio oficial de NVIDIA.

https://developer.nvidia.com/cuda-downloads

Page 34: TesinaPabloTintor FINAL

34

REFERENCIAS

[1] Laurent Michel. Solving a wave equation with CUDA. Technical report. May 2011.

http://sma.epfl.ch/~lmichel/cuda_report.pdf

[2] Jason Sanders, Edward Kandrot. CUDA by Example, An introduction to general-

purpose GPU programming. Addison-Wesley. July 2010.

[3]NVIDIA Corporation. CUDA C Programming Guide. October 2012.

http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html

[4] Randall J. LeVeque. Finite Difference Method for Ordinary and Partial Differential

Equation. SIAM. July 2007.

[5] Alfredo Ghisays Ruiz, Carlos Alberto Vargas J, Luis Alfredo Montes Vides. Método

híbrido de fronteras no reflectivas en límites de modelos sísmicos. 2006.

[6] Cerjan, C., D. Kosloff, R. Kosloff, and M. Reshef. A non-reflecting bound-

ary condition for direct acoustic and elastic wave equations. Geophysics,

50, 705 – 708. 1985.

[7] http://www.open-mpi.org http://www.mpi-forum.org/docs

[8] http://www.intel.com/cd/corporate/techtrends/emea/spa/209840.htm