• No se han encontrado resultados

Proyecto de grado. Concurrencia y alto desempeño

N/A
N/A
Protected

Academic year: 2021

Share "Proyecto de grado. Concurrencia y alto desempeño"

Copied!
24
0
0

Texto completo

(1)

Proyecto de grado

Concurrencia y alto desempeño

Yoann Lecuyer1, Rafael Gómez2, Andrés González Mancera3

1 Estudiante de pregrado del departamento de ingeniería de sistemas y computación de la Universidad de Los Andes (doble titulación con Ecole Nationale Supérieure des Mines de Saint­Etienne, ICM), [email protected]

2 Profesor asociado de ingeniería de sistemas y computación de la Universidad de Los Andes, [email protected]

(2)

Resumen

En el  departamento de ingeniería mecánica de la Universidad de Los Andes desarrolló se desarrollaron varias simulaciones de flujo basadas sobre el método dicho de Lattice­Boltzmann. Se logró tener prototipos funcionando para diferentes situaciones sin embargo estos prototipos son muy lentos y eso no le permite sacar los resultados que se ecesitan en un tiempo razonable. Por eso, este proyecto intenta mejorar el desempeño de esos prototipos usando la potencia de la GPU a través de la paralelización del problema sobre los datos. Usando el framework CUDA de nVidia, se mejoró el desempeño en términos de velocidad de esos algoritmos de un orden de magnitud de un factor 2.5 en el peor de los casos y de un factor 18 en el mejor de los casos. Ahora, esos resultados permiten sacar más datos de esas simulaciones para los estudios que usan esos prototipos.

Résumé

Dans le département d'ingénierie mécanique de l'Université de Los Andes (Bogotá, Colombie), plusieurs simulations de fluides basées sur la méthode de Lattice­Boltzmann ont été dévelopées. Il est possible de les faire fonctionner dans différentes situations cependant ils sont très lents et ne permettent pas d'obtenir des résultats en un temps raisonnable. L'objectif de ce projet est donc d'améliorer les performances de ces différents prototypes notamment en utilisant la puissance de calcul de la GPU et en parallélisant le problème au niveau des données. Grâce au framework CUDA de nVidia, le gain de performance a été d'un facteur 2.5 dans le pire des cas et d'un facteur 18 dans le meilleur des cas. Maintenant,   il   est   possible   d'utiliser   ces   prototypes   et   d'obtenir   de   bons   résultats   en   un   temps raisonnable.

Agradecimientos

Primero quiero agradecer a  Rafael Gómez  (Profesor asociado de ingeniería de sistemas y computación) por su asesoría a lo largo del proyecto.

También   quiero   agradecer   a  Andrés  González  Mancera  (Profesor   asistente   de  ingeniería mecánica) por proponer los códigos de sus proyectos con los cuales podíamos trabajar y por su ayuda en la evaluación de los resultados.

Finalmente, gracias a Julian Urán (Admonsis) por el préstamo de la máquina de desarrollo y su soporte técnico a lo largo del proyecto.

(3)

Índice de contenido

Resumen...2 Résumé...2 Agradecimientos...2 Introducción...4 Descripción general...4 Objetivos...4 Cronograma...5 Antecedentes...5 Contexto...6 Definición del problema...6 El método Lattice­Boltzmann...6 Calcular las variables físicas...6 Propagación...7 Colisiones...8 La paralelización con la GPU...8 Paralelización...11 Primer prototipo...11 Segundo prototipo...12 Tercer prototipo...13 Resultados...15 Primer prototipo...15 Segundo prototipo...16 Tercer prototipo...17 Validación...18 Conclusiones...18 Trabajo futuro...19 Referencias...19 Apéndices...20 Apéndice A...20 Caracteristicas del CPU...20 Caracteristicas de la memoria...22 Caracteristicas de la GPU...23 Apéndice B...24 Códigos...24 Prototipo 1...24 Prototipo 2...24 Prototipo 3...24

(4)

Introducción

La ley de Moore nos dice que mientras va pasando el tiempo más poderosos van a ser los computadores. Eso permitió tener una potencia de cálculo cada vez mayor a lo largo de los años y así lograr resolver problemas cada vez más complejos. Sin embargo, hoy en día, las leyes de la física están poniendo límites a esta ley así que algunos expertos dicen que desaparecerá dentro de unos años. No obstante, los problemas de hoy también son más complejos en términos de algoritmos y de manejo de datos, por eso, se desarrolló la programación paralela para seguir trabajando con problemas aún más complejos de manera efectiva. Entre las soluciones propuestas, encontramos CUDA un framework de nVidia   que   permite   usar   los   múltiples   procesadores   que   tienen   las   tarjetas   gráficas   para   hacer computación paralela.

En este proyecto, se usa el framework CUDA para mejorar el desempeño de programas que solucionan problemas reales. Los programas que se trabajaron en este proyecto los propuso Andrés González  Mancera   ([email protected])   profesor   asistente   del   departamento   de   ingeniería mecánica de la Universidad de Los Andes. Esos programas son simulaciones de flujos basadas en el método Lattice­Boltzmann y cada uno trata un problema diferente. Sin embargo, al ser secuenciales se demoran un tiempo nototio para sacar los resultados esperados. El algoritmo Lattice­Boltzmann es interesante por lo que está basado en una arquitectura de cuadrícula lo que lo convierte en un buen candidato para ser paralelizado a través del framework CUDA.

A   continuación,   haré   una   descripción   general   del   problema   explicando   los   objetivos   del proyecto y una revisión de los trabajos relacionados. Luego, explicaré el método Lattice­Boltzmann y como se paraleliza con el framework CUDA. Eso me permitirá presentar los resultados obtenidos y sus evaluaciones respectivas. Finalmente concluiré sobre el uso de CUDA para mejorar el desempeño del método Lattice­Boltzmann.

Descripción general

Objetivos

El objetivo principal del proyecto es trabajar con el código de los prototipos de simulaciones de fluidos propuestos por Andrés González Mancera con el fin de mejorar el desempeño. Por lo menos, se desea lograr un mejoramiento del 50% de los tiempos de referencias. Por supuesto, los resultados deben ser iguales o al menos equivalentes a los obtenidos con los prototipos originales. Además de este objetivo principal existen otros objetivos específicos para Andrés González Mancera y para mí. Andrés quiere mostrar que se puede ganar en desempeño con el uso de la GPU y quiere tener ejemplos de códigos para poder seguir implementado la paralelización con CUDA a sus futuros prototipos. En lo que me concierne, mis objetivos fueron aprender a usar el framework CUDA o más generalmente la paralelización de manera eficiente y trabajar con otras ingenierías que no están tan familiarizadas con el área de sistemas y computación.

(5)

Cronograma

El desarrollo de este proyecto ha sido planeado según el cronograma siguiente: Tuve reuniones semanales con mi asesor para revisar el trabajo hecho durante la semana previa y para socializar dudas o problemas técnicos. También se definían las metas para la siguiente semana con respecto al cronograma.

Antecedentes

Desde que CUDA existe muchos estudios se desarrollaron para paralelizar algoritmos existentes y estudiar el comportamiento. Así existe mucha documentación relacionada con este tema y dentro los artículos existentes algunos se centran sobre el algoritmo Lattice­Boltzmann. Por ejemplo,   Rinaldi, Dari, Vénere y Clausse (2012) desarrollaron un nuevo algoritmo basado sobre el método Lattice­ Boltzmann que permite incrementar el número de celdas actualizadas por segundos lo que implica un mejoramiento en el desempeño del algoritmo. De igual manera, Ye y Li (2013) desarrollaron una variante del algoritmo Lattice­Boltzmann y lograron tener un mejoramiento de la velocidad de un factor 3.14 con la implementación usando CUDA con respecto a la implementación basada sobre un solo CPU. Otros estudios como el de Rosales (2011) y el de Li et al. (2012) mostraron que al usar varias GPU se puede ganar aún más en términos de tiempo de ejecución.

También   es   importante   destacar   el   trabajo   previo   de   Andrés  González  Mancera ([email protected])   profesor   asistente   del   departamento   de   ingeniería   mecánica   de   la Universidad de Los Andes. El hizo tres prototipos de simulación de flujos, basados sobre el método Lattice­Boltzmann,   que   nos   entregó   para   desarrollar   este   proyecto.   El   primer   prototipo   es   una simulación   de   flujo   externo   sobre   estructuras   de   gran   tamaño,   esta   escrito   en   C++   de   manera secuencial. El segundo prototipo es la misma simulación de flujo a la cual se le agregó modelos de glóbulos rojos en suspensión. Igual está escrito en C++ de manera secuencial. Finalmente, el último

(6)

prototipo es un simulación de flujos de múltiples componentes. No obstante, este código esta escrito de manera secuencial en python con el uso de la librería numpy. Esos algoritmos han sido desarrollados según la metodología disponible en libro de Sukop y Thorne (2006).

Contexto

Definición del problema

En este proyecto se trabajaron tres prototipos distintos, una simulación de flujo externo sobre estructuras   de   gran   tamaño,   una   simulación   de   glóbulos   rojos   en   suspensión   y   finalmente   una simulación de flujo de múltiples componentes. El primer código y el segundo son relacionados en el sentido que los glóbulos rojos de la segunda simulación están en suspensión en el mismo flujo que el de la primera simulación. Estos códigos están escritos en C++, se manejan los parámetros de la simulación directamente dentro el código y el resultado es un conjunto de archivos VTK que se pueden leer con ParaView (ParaView, n.d) para tener una animación de la simulación. El tercer código al contrario está escrito en python y el resultado es un conjunto de imágenes representando datos de interés de la simulación.

El método Lattice­Boltzmann

El método Lattice­Boltzmann permite hacer una simulación discreta de las ecuaciones de la dinámica de fluidos. Es un buen candidato para ser paralelizado dado que la arquitectura del algoritmo está basada en una cuadrícula y cómo lo vamos a ver con el pseudo­código en seguida, las etapas del algoritmo hacen cálculos sobre cada cuadros que son independientes de sus vecinos y de los demás cuadros. El fluido está representado a través de la cuadrícula en la cual cada cuadro representa unas partículas con respecto a una dirección de movimiento. Como lo explican Sukop y Thorne (2006), el algoritmo básico tiene tres fases claves: cálculo de las variables físicas, propagación y colisión. Esas tres fases hacen cálculos con los valores de cada cuadro y por eso son los que requieren más tiempo de procesamiento. Así, esas tres fases son las que se van a paralelizar con CUDA.

Calcular las variables físicas

Para cada cuadro de la cuadrícula, se calcula independientemente de la posición, la suma de los valores presentes en cada dirección para después calcular las variables físicas:

(7)

for i in [0..Xmax] for j in [0..Ymax] for k in [0..Zmax] rho = 0 u = 0 for l in [0..19] f = cell[i][j][k][l] rho+= f u = f*e[l] end for calcular_velocidad(i,j,k,rho,u) end for end for end for

Propagación

Sigue la parte de propagación que simula el movimiento de los átomos dentro el fluido. Como el fluido se representa a través de una cuadrícula, los átomos se mueven de un cuadro a otro según la dirección del movimiento que tienen. En esta etapa también se aplican las condiciones de bordes como el rebote, la periodicidad, la velocidad... temp_cell = copiar(cell) for i in [0..Xmax] for j in [0..Ymax] for k in [0..Zmax] for l in [0..19] a = i b = j c = k d = l aplicar_condiciones_de_frontera(a,b,c,d,i,j,k,l) temp_cell[i][j][k][l] = cell[a][b][c][d] end for end for end for end for cambiar(temp_cell, cell)

(8)

Colisiones

Luego   del   movimiento   de   los   átomos,   viene   la   gestión   de   colisiones   y   del   equilibrio.   Una   vez completada esta etapa se puede hacer una nueva iteración. for i in [0..Xmax] for j in [0..Ymax] for k in [0..Zmax] for l in [0..19] feq = calcular_equilibrio(i, j, k, l) cell[i][j][k][l] ­= C*(cell[i][j][k][l] – feq) + K end for end for end for end for

La paralelización con la GPU

La GPU (Graphics Processing Unit) es un componente material que se encuentra ubicado sobre las tarjetas gráficas de los computadores. Tiene una arquitectura multi core que se usa generalmente para representar escenas 3D como las de los videojuegos. Sin embargo, se empezó a usarlas para hacer otras   operaciones   más   generales   llamadas   GPGPU   (General­Purpose   Computing   on   Graphics Processing   Units).   Para   facilitar   el   desarrollo   de   GPGPU,   los   constructores   de   GPU   han   creado frameworks, entre ellos está CUDA (Compute Unified Device Architecture) del fabricante nVidia. En este proyecto se usó CUDA con una máquina de desarrollo equipada con una tarjeta gráfica nVidia GeForce GTX 480. Al momento de desarrollar con una GPU es importante tomar en cuenta unos parámetros claves. Para introducirlos, miramos las características de la tarjeta gráfica usada en este proyecto: $ ./deviceQuery  ./deviceQuery Starting...   CUDA Device Query (Runtime API) version (CUDART static linking)  Detected 1 CUDA Capable device(s)  Device 0: "GeForce GTX 480"    CUDA Driver Version / Runtime Version      5.5 / 5.5    CUDA Capability Major/Minor version number:    2.0    Total amount of global memory:       1535 MBytes (1609760768 bytes)    (15) Multiprocessors, ( 32) CUDA Cores/MP:     480 CUDA Cores    GPU Clock rate:      1401 MHz (1.40 GHz)    Memory Clock rate:       1848 Mhz    Memory Bus Width:      384­bit 

(9)

  L2 Cache Size:       786432 bytes    Maximum Texture Dimension Size (x,y,z)         1D=(65536), 2D=(65536, 65535),  3D=(2048, 2048, 2048)    Maximum Layered 1D Texture Size, (num) layers  1D=(16384), 2048 layers    Maximum Layered 2D Texture Size, (num) layers  2D=(16384, 16384), 2048 layers    Total amount of constant memory:       65536 bytes    Total amount of shared memory per block:       49152 bytes    Total number of registers available per block: 32768    Warp size:       32    Maximum number of threads per multiprocessor:  1536    Maximum number of threads per block:       1024    Max dimension size of a thread block (x,y,z): (1024, 1024, 64)    Max dimension size of a grid size    (x,y,z): (65535, 65535, 65535)    Maximum memory pitch:      2147483647 bytes    Texture alignment:       512 bytes    Concurrent copy and kernel execution:      Yes with 1 copy engine(s)    Run time limit on kernels:       No    Integrated GPU sharing Host Memory:      No    Support host page­locked memory mapping:       Yes    Alignment requirement for Surfaces:      Yes    Device has ECC support:      Disabled    Device supports Unified Addressing (UVA):      No    Device PCI Bus ID / PCI location ID:       1 / 0    Compute Mode:       < Default (multiple host threads can use ::cudaSetDevice() with device  simultaneously) >  deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 5.5, CUDA Runtime Version  = 5.5, NumDevs = 1, Device0 = GeForce GTX 480  Result = PASS De la salida de este programa se puede destacar estos parámetros: número de hilos de ejecución por bloques (Threads per blocks) y el tamaño máximo de un bloque (Block).

(10)

Un hielo de ejecución es la parte donde se va a ejecutar el código, con esta tarjeta gráfica, se pueden ejecutar simultáneamente 1024 hielos de ejecución por bloque. Para poder usar más hielos de ejecución, se puede definir varios bloques. Sin embargo, lo bloques no pueden ejecutarse al mismo tiempo y van a ejecutarse de manera secuencial. Entonces, es de vital importancia configurar bien la ejecución de CUDA para que todo el dominio sea tomado en cuenta. Además, la arquitectura de CUDA permite organizar los bloques y los hielos de ejecución en un espacio 1D, 2D o 3D. Está organización es conveniente dado que nos permite cambiar bucles de este estilo: for x in [0..Xmax]: for y in [0..Ymax]: for z in [0..Zmax]: tarea(x,y,z); A un código que se va ejecutar en la GPU de este estilo: CPU: ejecutar_tarea<<dim_grid, dim_bloque>>(); GPU: __global__ void tarea() { int x = blockIdx.x*blockDim.x + threadIdx.x; int y = blockIdx.y*blockDim.y + threadIdx.y; int z = blockIdx.z*blockDim.z + threadIdx.z; } Esta estructura es la que encontramos en el código del algoritmo Lattice­Boltzmann y es por eso que este algoritmo es un buen candidato para ser paralelizado con CUDA. Podemos ver en el pseudo­ código que además de llamar la función para que se ejecute desde la GPU, hay que definir el número de bloques (dim_grid) que van a ser usados y el tamaño de esos bloques (dim_bloque). El manejo de esos parámetros   es   lo   que   se   llama   tuning   y   es   importante   cambiar   esos   parámetros   para   tener   las dimensiones muy parecidas a las de los problemas para no perder en eficiencia. Pero también hay que tener en cuenta las limitaciones de la GPU para quedarse dentro la configuración autorizada. En efecto, vimos que la GPU usada para desarrollar este proyecto puede correr en paralelo 1024 hielos de ejecución, para correr más hielos de ejecución, se debe organizarlos en bloques que se van a ejecutar de manja secuencial. Dado la arquitectura del algoritmo Lattice­Boltzmann, se usan los hielos de ejecución para hacer operaciones sobre un cuadro de la cuadrícula base del modelo. Así, si el dominio es de un tamaño   X ×Y ×Z , para hacer los cálculos sobre cada cuadro se debe tomar conjuntos de cuadros de tal manera que el total de cuadros sea inferior que 1024. Por ejemplo, para un tamaño   21×21×21=9261 se   puede   tomar   subconjuntos   de   tamaño   10×10×10=1000 y   así recoger   toda   la   cuadrícula.   Sin   embargo,   hay   que   prestar   atención   a   las   fronteras.   En   efecto,   es

(11)

importante escribir una condición para asegurarse que los cálculos que se van a hacer no van a salir de la cuadrícula dado que el subconjunto que se usa tiene más puntos que lo que se necesita.

Paralelización

Primer prototipo

El primer prototipo con el cual trabajé es una simulación de flujo externo sobre estructuras de gran tamaño. Dicho de otra manera tenemos un fluido entre dos paredes que van moviéndose hacia direcciones diferentes y se desea estudiar el comportamiento del fluido. Con este prototipo se paralelizó las tres fases descritas en la explicación del método Lattice­ Boltzmann. Para lograrlo se necesitó hacer unos cambios a la arquitectura general del programa. Por ejemplo los valores decimales eran del tipo double pero la GPU usada durante este proyecto sólo podían manejar variables de tipo float. Igualmente, los arreglos eran definidos con apuntadores a apuntadores. Dicho de otra manera, había un arreglo de apuntadores apuntando a las filas que estaban apuntando a las columnas que estaban apuntando a la profundidad... No obstante, es difícil de mandar los datos así a la GPU a través de la función cudaMemCpy de CUDA parecida a la función memcpy de la biblioteca C estándar. En efecto, los datos no están enseguida en la memoria y no se puede copiar el bloque de datos de una vez. Así se hizo un cambio de todos los arreglos de dimensión N a arreglos de dimensión 1 y para acceder a los datos de esos arreglos de una dimensión se crearon funciones de soporte con el fin de facilitar el proceso. Esta etapa permitió mejorar el desempeño de manera significante pero nos dimos cuenta gracias al profiler de CUDA que se podía mejorar aún más el desempeño cambiando el manejo de los datos y de la memoria. En efecto, al principio se mandaban los datos desde la CPU hasta la GPU, se hacían los cálculos y se bajaban los datos de la GPU hasta la CPU para seguir el procesamiento. Esta transferencia de datos

(12)

entre la CPU y la GPU es muy costosa en términos de tiempo de ejecución y para mitigar esto se cambió la manera de guardar los datos en la memoria. Así, para mejorar el desempeño, se mandan los datos de la simulación a la GPU antes de empezar y se guardan dentro la GPU todo el tiempo durante la simulación. Solo se bajan los datos de la GPU cuando la CPU los  necesita para guardarlos dentro los archivos VTK. Finalmente, se hizo la etapa de tuning para tener el mejor desempeño posible.

Como   lo   vimos   anteriormente,   es   importante   armar   bloques   para   cubrir   la   totalidad   de   la cuadricula y deben ser tal que no tengan más que 1024 hielos de ejecución para nuestra GPU. También, vimos que es importante considerar las fronteras dado que se puede tener un bloque con la mayoría de los hielos de ejecución sin actividad. Por eso, la etapa de tunning es importante ya que intenta buscar la mejor forma del subconjunto que nos permite sacar los mejor resultados. Para hacer el tunning de este prototipo, escribé un codigo en java que va calculando todos los bloques posibles   a×b×c y sus ocupaciónes según los ejes tal que  a×b×c≤1024 . Luego se ordenan según el numero de hielos de ejecución que contiene el bloque de tal manera que se pueda encontrar la configuración del bloque que maximisa el numero de hielos de ejecución y la ocupación.

Segundo prototipo

En el segundo prototipo, se usa como base la simulación de flujo sobre estructuras de gran tamaño. A esto, se suma modelos de glóbulos rojos con una posición fija pero con una membrana deformable. A través de métodos de elementos finitos, se calcula la interacción del fluido con respecto a la membrana de cada glóbulo rojo presente. Dado que este código usa la misma base que el primer prototipo se usó el código de este para empezar el proceso de paralelización. La segunda fase del proceso de paralelización se hizo a cerca de la interacción entre los modelos 3D y el flujo. Los modelos 3D son constituidos por nodos y para cada uno de los nodos se debe calcular la interacción con el fluido. Así, se paralelizó el código sobre el recorrido de los nodos de los modelos. También se hizo la etapa de tuning para la parte paralelizada del fluido y la parte paralelizada de los modelos. Esa etapa siguió la misma metodología que para el prototipo anterior dado que son muy parecidos.

(13)

Tercer prototipo

El tercer prototipo es una simulación de flujo de múltiples componentes, está hecho en python usando la librería numpy. Este prototipo es aún más importante para nosotros dado que para científicos sin conocimientos avanzados en programación, python es un lenguaje que permite hacer prototipos de manera sencilla. En efecto, además de tener una sintaxis sencilla, es un lenguaje orientado a objetos interpretado sin ser fuertemente tipado. Con respecto al ambiente de desarrollo, python también cuenta con muchas librerías de uso sencillo para la programación científica como numpy.  En lugar de convertir el código python a C++ para después paralelizar lo sobre CUDA se usó la librería NumbaPro de la distribución de python Anaconda (Anaconda, n.d.). Esta librería permite usar la potencia de CUDA desde python a través de un proceso de tipo JIT (Just In Time). Además, tiene la ventaja de ser compatible con numpy lo que permite usarla con el prototipo existente y los futuros sin tener que hacer muchos cambios al código ni tener que aprender una nueva librería o lenguaje de programación. Dado el uso intensivo de la librería numpy, el código de este prototipo no tiene como tal una arquitectura hecha para ser paralelizada. En efecto, no tiene bucles que van recorriendo todos los cuadros del modelo dado que estos bucles están escondidos dentro de las funciones de numpy. Hice pruebas para ver si era viable escribir sus propias funciones con CUDA en lugar de usarlas definidas dentro numpy. El código usado es lo siguiente:

(14)

Los resultados son los siguientes: Numpy CUDA 0.38302 ±0.00217s 0.92327 ±0.00363s Las funciones de numpy se ejecutan más rápidamente que el equivalente CUDA. Así, en términos prácticos y de desempeño es mejor usar las funciones de la librería numpy que intentar escribir sus propias funciones parecidas con CUDA. Para ganar en desempeño con este prototipo, se paralelizó la inicialización de los datos que sí tiene una arquitectura adecuada para ser paralelizada. También, se hizó un refactoring de unas parte del código que habían sido escritas desde cero aunque existen funciones muy parecidas dentro la librería numpy que tienen un mejor desempeño.

(15)

Resultados

Las medidas de desempeño se midieron sobre una máquina Intel Dual­Core equipada de una tarjeta gráfica nVidia GeForce GTX 480. Para mayores informaciones sobre las características del hardware   ver   Apéndice   A.   Las   mediciones   de   tiempo   se   hicieron   con   el   comando   time   de   unix corriendo el programa varias veces para evitar los resultados aberrantes.

Primer prototipo

El dominio usado para sacar los resultados fue X = 51 Y = 51 Z = 51 Los parametros para llamar a los kernels de CUDA (archivo cuda_settings.h) fueron:

(16)

Se ejecutó la prueba con el flag de optimización (­03) y sin usar el flag de debug (­g) varias veces para evitar resultados aberantes. Secuencial Paralelizado 69.85 ±0.02s 3.56311 ±0.05142s Podemos ver con los resultados que en este caso, se logro una mejora en un factor de 18 en términos de tiempo de ejecución.

Segundo prototipo 

El dominio usado para sacar los resultados fue: X = 51 Y = 51 Z = 51 También estaban dos glóbulos rojos posicionados de tal manera: X = 13 Y = 25 Z = 35 para el primer glóbulo rojo y X = 30 Y = 25 Z = 15 Los parametros para llamar a los kernels de CUDA (archivo cuda_settings.h) fueron:

(17)

Y para las membranas: Se ejecutó la prueba con el flag de optimización (­03) y sin usar el flag de debug (­g) varias veces para evitar resultados aberantes. Secuencial Paralelizado 627.042 ±2.25746s 75.74167 ±0.20683s Podemos ver con los resultados que en este caso, se logro una mejora en un factor de 8 en términos de tiempo de ejecución.

Tercer prototipo 

Este prototipo se usó con los parámetros definidos en el programa original. Sin embargo, como no se podía usar la GPU para hacer cálculos y mostrar imágenes sobre la pantalla al mismo tiempo, se desactivó la posibilidad de mostrar la imagen resultante mientras estaba corriendo el programa. Secuencial Paralelizado 1476.22 ±4.05151s 604.54333 ±1.19396s Aunque no se pudo paralelizar el bucle principal del programa, podemos ver que solo con el refactoring del código que consumaba más tiempo, se pudo lograr un mejoramiento en tiempo de ejecución en un

(18)

factor de 2.5. Luego de ver esos resultados, intenté cambiar las funciones de numpy por mi propio código que permitía paralelizar el bucle principal. Sin embargo, al hacer pruebas sobre mi código, me di cuenta que las funciones de numpy son más eficientes que el mismo código corriendo sobre la GPU. Además, escribir sus propias funciones en lugar de usar las de numpy quita la ventaja que tiene NumbaPro de tener compatibilidad con numpy y quita la simplicidad de python dado que se necesita escribir sus funciones.

Validación

Los resultados descritos en este documento se validaron en dos etapas. Primero al mismo tiempo que se estaba trabajando el prototipo se guardaba una copia de los resultados del programa original para tener los resultados de referencia. Una vez el prototipo paralelizado, se calculaban los resultados y se hacía una comparación con respecto a los resultados de referencia. Para lograr esta comparación, se usó numdiff que permite, entre otros usos, comparar los valores que conforman los archivos VTK. Este programa permite decir si los datos tienen una diferencia absoluta y/o relativa más importante que un umbral dado. Luego de esta primera validación, Andrés González Mancera miró los resultados   del   programa   paralelizado   y   confirmó   la   consistencia   de   los   resultados.   Usando   esta metodología, se validaron los resultados de cada prototipo trabajado durante este proyecto.

Conclusiones

En conclusión, se comprobó a través de este proyecto que se gana en desempeño si se paraleliza con CUDA el algoritmo de Lattice­Boltzmann. Sin embargo, los mejores resultados se lograron con los

Grafico 1: Tiempo de ejecución en segundos

Prototipo 1 Prototipo 2 Prototipo 3 0 200 400 600 800 1000 1200 1400 1600 secuencial paralelizado

(19)

códigos escritos en C++ dado que la arquitectura del código es perfecta para que sea paralelizada. Sin embargo, era obligatorio aplicar cambios sobre las estructuras de datos y los tipos de datos para lograr la paralelización. Eso hace que el proceso de paralelización requiere conocimientos avanzados en programación para lograrlo. Al contrario, el lenguaje python con el framework NumbaPro de la distribución Anaconda (Anaconda, n.d.) permite simplificar este proceso de paralelización con un sistema de compilación JIT y su compatibilidad con la biblioteca numpy. Pero, aunque los científicos estén acostumbrados a escribir prototipos en python con numpy, la estructura de un código usando numpy no es un buen candidato para ser paralelizado. En efecto, con numpy no se hacen bucles embebidos, sino que se usan funciones que manejan los bucles por el usuario. Como vimos con el tercer prototipo, se puede escribir su propio código para hacer que el código se pueda paralelizar. No obstante, así se pierde la calidad y la simplicidad de la librería numpy y en este caso me parece que es mejor escribir el código en C++.

Trabajo futuro 

En mi opinión veo una oportunidad muy buena de poder usar el framework CUDA a través de python.   Esto   permite   a   las   personas   que   no   saben   mucho   de   programación   de   hacer   prototipos paralelizados de manera sencilla. Sin embargo, como lo vimos en este proyecto, la paralelización de códigos usando mucho numpy no es posible pero sería bueno investigar otros tipos de códigos que sí se pueden paralelizar bien desde python, como, por ejemplo, códigos de tratamiento de imágenes, de exploración de grafos y de manipulación de big data.

Referencias  

Anaconda. (n.d.). Python Visualization and Data Exploration. Retrieved November 13, 2013, from https://store.continuum.io/cshop/anaconda/

Kirk,   David,   and   Wen   Hwu.   Programming   massively   parallel   processors:   a   hands­on   approach. Burlington, MA: Morgan Kaufmann Publishers, 2010. Print. Li, Q., Zhong, C., Li, K., Zhang, G., Lu, X., Zhang, Q., Zhao, K., and Chu, X. (2012). Implementation of a lattice boltzmann method for large eddy simulation on multiple gpus. In Proceedings of the 2012 IEEE 14th International Conference on High Performance Computing and Communication & 2012 IEEE 9th International Conference on Embedded Software and Systems, HPCC '12, pages 818­823, Washington, DC, USA. IEEE Computer Society.

ParaView   ­   Open   Source   Scientific   Visualization.   (n.d.). ParaView   ­   Open   Source   Scientific Visualization. Retrieved November 13, 2013, from http://www.paraview.org/

Sanders, Jason, and Edward Kandrot. CUDA by example: an introduction to general­purpose GPU programming. Upper Saddle River, NJ: Addison­Wesley, 2011. Print.

Sukop, M. C., & Thorne, D. T. (2006). Lattice Boltzmann modeling an introduction for geoscientists and engineers. Berlin: Springer.

(20)

Rinaldi, P., Dari, E., Vénere, M., and Clausse, A. (2012). A lattice­boltzmann solver for 3d fluid simulation on gpu. Simulation Modelling Practice and Theory, 25(0):163 – 171.

Rosales, C. (2011). Multiphase lbm distributed over multiple gpus. In Proceedings of the 2011 IEEE

International Conference on Cluster Computing, CLUSTER '11, pages 1­7, Washington, DC, USA.

IEEE Computer Society.

Ye,   Y.   and   Li,   K.   (2013).   Entropic   lattice   boltzmann   method   based   high   reynolds   number   flow simulation using cuda on gpu. Computers & Fluids, 88(0):241 – 249.

Apéndices 

Apéndice A

Caracteristicas del CPU

$ cat /proc/cpuinfo  processor : 0  vendor_id : GenuineIntel  cpu family  : 6  model  : 15  model name  : Intel(R) Core(TM)2 CPU      6300  @ 1.86GHz stepping  : 2  microcode  : 0x5d  cpu Mhz  : 1596.000  cache size  : 2048 KB  physical id  : 0  siblings  : 2  core id  : 0  cpu cores : 2  apicid  : 0  initial apicid : 0  fdiv_bug : no  hlt_bug : no  f00f_bug : no  coma_bug : no  fpu : yes  fpu_exception : yes  cpuid level : 10  wp : yes  flags : fpu vme de pse tsc msr pae mce cx8 apic sep  mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht  tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64

(21)

monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm dtherm  tpr_shadow  bogomips : 3723.99  clflush size : 64  cache_alignment : 64  address sizes : 36 bits physical, 48 bits virtual  power management:  processor : 1  vendor_id : GenuineIntel  cpu family : 6  model : 15  model name : Intel(R) Core(TM)2 CPU      6300  @ 1.86GHz stepping : 2  microcode : 0x5d  cpu MHz : 1596.000  cache size : 2048 KB  physical id : 0  siblings : 2  core id : 1  cpu cores : 2  apicid : 1  initial apicid : 1  fdiv_bug : no  hlt_bug : no  f00f_bug : no  coma_bug : no  fpu : yes  fpu_exception : yes  cpuid level : 10  wp : yes  flags : fpu vme de pse tsc msr pae mce cx8 apic sep  mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht  tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm lahf_lm dtherm  tpr_shadow  bogomips : 3723.99  clflush size : 64  cache_alignment : 64  address sizes : 36 bits physical, 48 bits virtual  power management :

(22)

Caracteristicas de la memoria

$ cat /proc/meminfo  MemTotal:        4071388 kB  MemFree:         3621496 kB  Buffers:       71852 kB  Cached:       317816 kB  SwapCached:      0 kB  Active:      85180 kB  Inactive:         321736 kB  Active(anon):      17516 kB  Inactive(anon):      600 kB  Active(file):      67664 kB  Inactive(file):   321136 kB  Unevictable:       0 kB  Mlocked:       0 kB  HighTotal:       3215052 kB  HighFree:        2866488 kB  LowTotal:         856336 kB  LowFree:      755008 kB  SwapTotal:       4126716 kB  SwapFree:        4126716 kB  Dirty:      44 kB  Writeback:       0 kB  AnonPages:         17152 kB  Mapped:      13992 kB  Shmem:       864 kB  Slab:      19908 kB  SReclaimable:      11244 kB  SUnreclaim:         8664 kB  KernelStack:        1304 kB  PageTables:      900 kB  NFS_Unstable:      0 kB  Bounce:      0 kB  WritebackTmp:      0 kB  CommitLimit:     6162408 kB  Committed_AS:     148976 kB  VmallocTotal:     122880 kB  VmallocUsed:       13740 kB  VmallocChunk:     102460 kB  HardwareCorrupted:     0 kB  AnonHugePages:         0 kB  HugePages_Total:       0 

(23)

HugePages_Free:        0  HugePages_Rsvd:        0  HugePages_Surp:        0  Hugepagesize:       2048 kB  DirectMap4k:       59384 kB  DirectMap2M:      854016 kB 

Caracteristicas de la GPU

$ ./deviceQuery  ./deviceQuery Starting...   CUDA Device Query (Runtime API) version (CUDART static linking)  Detected 1 CUDA Capable device(s)  Device 0: "GeForce GTX 480"    CUDA Driver Version / Runtime Version      5.5 / 5.5    CUDA Capability Major/Minor version number:    2.0    Total amount of global memory:       1535 MBytes  (1609760768 bytes)    (15) Multiprocessors, ( 32) CUDA Cores/MP:     480 CUDA Cores    GPU Clock rate:      1401 MHz (1.40 GHz)    Memory Clock rate:       1848 Mhz    Memory Bus Width:      384­bit    L2 Cache Size:       786432 bytes    Maximum Texture Dimension Size (x,y,z)         1D=(65536),  2D=(65536, 65535), 3D=(2048, 2048, 2048)    Maximum Layered 1D Texture Size, (num) layers  1D=(16384), 2048  layers    Maximum Layered 2D Texture Size, (num) layers  2D=(16384, 16384),  2048 layers    Total amount of constant memory:       65536 bytes    Total amount of shared memory per block:       49152 bytes    Total number of registers available per block: 32768    Warp size:       32    Maximum number of threads per multiprocessor:  1536    Maximum number of threads per block:       1024    Max dimension size of a thread block (x,y,z): (1024, 1024, 64)    Max dimension size of a grid size    (x,y,z): (65535, 65535, 65535)   Maximum memory pitch:      2147483647 bytes    Texture alignment:       512 bytes    Concurrent copy and kernel execution:      Yes with 1 copy 

(24)

engine(s)    Run time limit on kernels:       No    Integrated GPU sharing Host Memory:      No    Support host page­locked memory mapping:       Yes    Alignment requirement for Surfaces:      Yes    Device has ECC support:      Disabled    Device supports Unified Addressing (UVA):      No    Device PCI Bus ID / PCI location ID:       1 / 0    Compute Mode:       < Default (multiple host threads can use ::cudaSetDevice() with  device simultaneously) >  deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 5.5, CUDA  Runtime Version = 5.5, NumDevs = 1, Device0 = GeForce GTX 480  Result = PASS

Apéndice B

Códigos 

Se pueden econtrar los codigos paralelizados en los siguientes repositorios:

Prototipo 1

https://github.com/ylecuyer/lbm­cortante La versión original es la del commit cd24d56a0e

Prototipo 2

https://github.com/ylecuyer/lbm­globulo­rojo La versión original es la del commit cd24d56a0e

Prototipo 3

https://github.com/ylecuyer/lbm­emulsion­2d La versión original es la del commit d01333f50b

Referencias

Documento similar

DECORA SOLO LAS IMÁGENES QUE NECESITES PARA LLEGAR AL NÚMERO CORRESPONDIENTE... CEIP Sansueña/CEIP Juan XXIII Infantil

Las personas solicitantes deberán incluir en la solicitud a un investigador tutor, que deberá formar parte de un grupo de investigación. Se entiende por investigador tutor la

Lo más característico es la aparición de feldespatos alcalinos y alcalino térreos de tamaño centimétrico y cristales alotriomorfos de cuarzo, a menudo en agregados policristalinos,

Las acciones correctivas serán iniciadas por el Director Técnico como consecuencia de no conformidades de todo tipo detectadas previamente, repetidas o no, así

Esta investigación se centra en el diseño y construcción de un prototipo experimental a partir de la identificación de sus características principales como dispositivo didáctico,

El proyecto está centrado en el desarrollo de un prototipo de VANT controlado a través de una aplicación de Smartphone para el sistema operativo Android.. Se empleará una placa

Mostrar el prototipo frente al usuario final (en la clase – última sesión) donde se le explique la solución a su problema por medio del prototipo. El pitch debe ser no mayor a

Este proceso analiza el fonocardiograma adquirido arrojando tres envolventes, la amplitud, energía y frecuencia instantánea, esta información derivada del