IMPLEMENTACIÓN DE UN CLUSTER HETEROGÉNEO DE...
Transcript of IMPLEMENTACIÓN DE UN CLUSTER HETEROGÉNEO DE...
Instituto Tecnológico de La Paz
INSTITUTO TECNOLÓGICO DE LA PAZDIVISIÓN DE ESTUDIOS DE POSGRADO E INVESTIGACIÓN
MAESTRÍA EN SISTEMAS COMPUTACIONALES
IMPLEMENTACIÓN DE UN CLUSTER HETEROGÉNEO DEGPGPU PARA ALGORITMO GENÉTICO
T E S I S
QUE PARA OBTENER EL GRADO DEMAESTRO EN SISTEMAS COMPUTACIONALES
PRESENTA:MAURICIO ROJAS SALAZAR
DIRECTOR DE TESIS:M.C. JESÚS ANTONIO CASTRO
LA PAZ, BAJA CALIFORNIA SUR, MÉXICO, AGOSTO 2018.
Blvd. Forjadores de B.C.S. #4720, Col. 8 de Oct. 1era. Sección C.P. 23080La Paz, B.C.S. Conmutador (612) 121-04-24, Fax: (612) 121-12-95
www.itlp.edu.mx
Dedicatoria
A mis padres, que siempre estuvieron para apoyarme durante mi formacion y en mis estudios,
guiandome y alentandome siempre a superarme cada dıa.
i
Agradecimientos
Agradezco a todas aquellas personas que de manera directa o indirecta ayudaron a la finalizacion
de este proyecto.
ii
Resumen
En la presente tesis se describe la implementacion de un cluster heterogeneo de Unidades de
Procesamiento Grafico de Proposito General (GPGPU, por sus siglas en ingles) en el cual se
ejecuto un algoritmo genetico para pruebas de eficacia y eficiencia.
El cluster se implemento usando OpenMPI y para la programacion en la GPU se utilizo OpenCL.
iii
Abstract
In this paper we describe the implementation of a heterogeneous cluster of General Purpose
Graphical Processing Units (GPGPU) in which a genetic algorithm was run for efficacy and
efficiency tests.
The cluster was implemented using OpenMPI and OpenCL was used for programming on the
GPU.
iv
Indice general
1. Introduccion 1
1.1. INTRODUCCION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.1. Objetivo general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2. Objetivos especıficos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3. Hipotesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2. OpenCL 5
2.1. OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2. Instalacion del Ambiente de Desarrollo OpenCL en Linux . . . . . . . . . . . . . 6
2.2.1. AMD-Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.2. Nvidia-Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3. Compilacion y Ejecucion de Programas en OpenCL . . . . . . . . . . . . . . . . 8
2.4. Primeros Pasos de OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.5. Creacion y Ejecucion de Funciones en el Device . . . . . . . . . . . . . . . . . . 13
v
INDICE GENERAL vi
2.6. Cualificadores en Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.7. Gestion de Memoria y Comunicacion de Datos Entre CPU y GPU . . . . . . . . 21
2.7.1. Comunicacion Host-Device . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.7.2. Comunicacion Device-Host . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3. OpenMPI 27
3.1. OpenMPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1.1. MPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2. Instalacion de OpenMPI en Linux . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.3. Configuracion del Cluster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.4. Funciones de OpenMPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.5. Creacion, Compilacion y Ejecucion de Programas con OpenMPI . . . . . . . . . 36
3.6. Compilacion y Ejecucion de Programas OpenMPI-OpenCL . . . . . . . . . . . . 38
4. Algoritmos Geneticos 41
4.1. Algoritmo Genetico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.2. Operadores Geneticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.3. Algoritmos Geneticos en Paralelo . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.4. Implementacion de Algoritmos Geneticos . . . . . . . . . . . . . . . . . . . . . 48
5. Resultados 51
5.1. RESULTADOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
INDICE GENERAL vii
6. Conclusiones 54
A. Codigo de funciones de aptitud 55
A.1. Archivo “ackley.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
A.2. Archivo “rastrigin.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
B. Codigo del algoritmo genetico 57
B.1. Archivo “parametros.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
B.2. Archivo “maestro.c” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
B.3. Archivo “agHost.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
B.4. Archivo “agKernel.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
B.5. Archivo “OpenCLFramework.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
B.6. Archivo “mt19937.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
B.7. Archivo “mt19937Host.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
B.8. Archivo “mt19937Kernel.h” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Bibliografıa 92
Indice de figuras
2.1. Modelo de memoria de OpenCL. . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.1. Algoritmo Genetico. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2. Distribucion de la carga de procesamiento. . . . . . . . . . . . . . . . . . . . . . 48
4.3. Migracion de los MR mejores individuos del bloque siguiente. . . . . . . . . . . . 49
4.4. Sustitucion de los MR peores individuos del bloque actual con los MR mejores
del bloque siguiente. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.1. Funcion de Rastrigın. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.2. Funcion de Ackley. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
viii
Indice de tablas
2.1. Valores soportados por device type . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2. Valores soportados por cl mem flags . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.1. Especificaciones del equipo 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2. Especificaciones del equipo 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3. Tipos de datos predefinidos en MPI. . . . . . . . . . . . . . . . . . . . . . . . . 36
5.1. Resultados funcion Rastrigin . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.2. Resultados funcion Ackley . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
ix
Capıtulo 1
Introduccion
1.1. INTRODUCCION
Desde la invencion de la computadora el ser humano ha buscado constantemente aumentar la
velocidad de la misma, esto se puede observar con la evolucion del hardware, la mayorıa de los
componentes de una computadora tienen como meta ser mas veloces, los discos duros buscan
tener mas IOPS (del ingles Input/Output Operations Per Second, operaciones de entrada/salida
por segundo), las memorias RAM buscan ser mas rapidas y de mayor capacidad, los modems
buscan tener una mayor tasa de transferencia de datos, etc. Pero el componente de mayor
importancia y relevancia en esta busqueda es el procesador, esto es debido a que es este el que
realiza los calculos y las operaciones. Aunque un problema computacional pueda ser solucionado
en un lapso de tiempo razonable, siempre se busca que la solucion sea inmediata.
Una manera de conseguir mayor poder de procesamiento era aumentando la velocidad del reloj a
la que el procesador operaba, pero se ha llegado a un punto en el cual ya es fısicamente imposible
por cuestiones de espacio, energıa y temperatura poder seguir incrementando la potencia de
un procesador, debido a esto es que los investigadores y los fabricantes han optado por otras
opciones.
Los fabricantes de supercomputadoras lograban grandes saltos en el rendimiento aumentando
1
1.1. INTRODUCCION 2
constantemente la cantidad de procesadores. No es raro que los supercomputadores mas rapi-
dos tengan decenas o cientos de miles de nucleos de procesador trabajando en tandem. En
la busqueda de potencia de procesamiento adicional para computadoras personales, la mejora
en los supercomputadores plantea una muy buena pregunta: En lugar de buscar unicamente
aumentar el rendimiento de un solo nucleo de procesamiento, ¿Por que no poner mas de uno
en una computadora personal? De esta forma, las computadoras personales podrıan continuar
mejorando en rendimiento sin la necesidad de aumentos continuos en la velocidad de reloj del
procesador. En 2005, frente a un mercado cada vez mas competitivo y pocas alternativas, los
principales fabricantes de CPU comenzaron a ofrecer procesadores con dos nucleos informaticos
en lugar de uno. Durante los anos siguientes, siguieron este desarrollo con el lanzamiento de
unidades de procesador central de tres, cuatro, seis y ocho nucleos. A veces denominada re-
volucion multinucleo, esta tendencia ha marcado un gran cambio en la evolucion del mercado
informatico del consumidor.[1]
Para los usuarios, contar con los ultimos avances tecnologicos conlleva un alto costo, ya que
deben pagar grandes sumas de dinero para poder adquirir computadoras que puedan procesar
grandes cantidades de informacion en un tiempo aceptable, haciendolas poco asequibles para
ellos. Una arquitectura de computo paralelo de bajo costo es la de los clusters, la cual consiste en
conectar varias computadoras por medio de la red de tal forma que el conjunto se comporte como
un unico ordenador, mas potente que el de una computadora personal, esta arquitectura nace
de la filosofıa de reutilizar equipos en desuso para darles nueva vida y seguirlos aprovechando.
Una tendencia que se a hecho muy popular es la del uso de las Unidades de Procesamiento
Grafico de Proposito General (GPGPU, por sus siglas en ingles), mientras los procesadores
cuentan con una cantidad muy reducida de ALUs, las tarjetas graficas poseen una cantidad
mucho mayor, permitiendo hacer en una fraccion del tiempo lo que a un CPU le tomarıa
hacer en horas. Inicialmente los investigadores comenzaron utilizando OpenGL o DirectX para
enganar a la GPU y poder hacer calculos en ellas, esto resulto ser muy complicado pues los
investigadores tenıan que resolver graves restricciones de recursos y de programacion, ası como
aprender graficos de computadora y los lenguajes de sombreado antes de intentar aprovechar la
potencia computacional de la GPU.
1.1. INTRODUCCION 3
En noviembre de 2006, NVIDIA presento la primera GPU DirectX 10 de la industria, la GeForce
8800 GTX. La GeForce 8800 GTX tambien fue la primera GPU que se construyo con la ar-
quitectura CUDA de NVIDIA. Esta arquitectura incluıa varios componentes nuevos disenados
estrictamente para computacion GPU y destinados a aliviar muchas de las limitaciones que
impedıan que los procesadores graficos anteriores fueran legıtimamente utiles para el calculo de
proposito general.[1]
Para alcanzar el numero maximo de desarrolladores posibles, NVIDIA tomo el estandar C y
agrego un numero relativamente pequeno de palabras clave con el fin de aprovechar algunas de
las caracterısticas especiales de la arquitectura CUDA. Unos meses despues del lanzamiento de
la GeForce 8800 GTX, NVIDIA hizo publico un compilador para este lenguaje, CUDA C. Y con
eso, CUDA C se convirtio en el primer idioma especıficamente disenado por una companıa de
GPU para facilitar la informatica de proposito general en GPU. Ademas de crear un lenguaje
para escribir codigo para la GPU, NVIDIA tambien proporciona un controlador de hardware
especializado para explotar el enorme poder computacional de la arquitectura CUDA.[1]
Otra practica que se ha ido popularizando es la del uso de clusters de GPUs, la cual combina
las ventajas de ambas arquitecturas, esto se logra siguiendo los principios de ambas soluciones,
se unen varias computadoras por medio de una red y en cada computadora se realiza el procesa-
miento en la GPU, normalmente se utilizan equipos con tarjetas graficas del mismo fabricante,
arquitectura comunmente llamada cluster homogeneo.
En el Instituto Tecnologico de La Paz se han realizado varios proyectos de investigacion con
procesamiento paralelo en GPGPU. Para este trabajo algunos de los mas relevante son:
1. La implementacion de un AG y un PSO en una GPGPU, utilizando CUDA y una tarjeta
Nvidia[2].
2. La implementacion de un AG y un PSO en un cluster homogeneo, utilizando CUDA y tarjetas
graficas Nvidia[3].
Actualmente no existen muchos proyectos sobre clusters heterogeneos, de aquı la iniciativa
propuesta en el presente trabajo.
1.2. OBJETIVOS 4
Para el desarrollo del cluster heterogeneo se implemento OpenMPI para el enlace y la comuni-
cacion de los equipos.
En el caso de la programacion para la GPGPU se empleo OpenCL. Esta herramienta consta de
una interfaz y un lenguaje de programacion, brindando un estandar en el desarrollo del codigo.
Las aplicaciones son desarrolladas en lenguaje C y, al momento de compilacion, se utilizan las
librerıas de cada fabricante, lo que permite usar el mismo codigo para tarjetas de diferentes
marcas.
1.2. Objetivos
1.2.1. Objetivo general
Desarrollar aplicaciones en un cluster heterogeneo de GPGPUs utilizando OpenCL y OpenMPI
para algoritmos de optimizacion basados en poblaciones, concretamente algoritmos geneticos.
1.2.2. Objetivos especıficos
Aprender la interfaz y lenguaje de programacion OpenCL.
Llevar a cabo la instalacion de un cluster con diferentes tipos de GPGPU.
Implementar un algoritmo que pueda ejecutarse en paralelo.
Medir la velocidad del algoritmo en el cluster.
1.3. Hipotesis
Es posible crear aplicaciones que corran de forma paralela en un cluster heterogeneo de GPGPUs.
Capıtulo 2
OpenCL
2.1. OpenCL
Open Computing Language (OpenCL) es un framework (marco de trabajo) de programacion
heterogenea, manejado por el consorcio de tecnologıa sin fines de lucro Khronos, con la participa-
cion de companıas como Intel, ARM, AMD, NVIDIA, QUALCOMM, Apple y muchos otros[4].
Con OpenCL es posible crear aplicaciones que pueden ser ejecutadas en una gran variedad de
devices (dispositivos) de diferentes fabricantes. Es compatible con una amplia gama de niveles
de paralelismo y sistemas homogeneos o heterogeneos, que consten de CPU, GPU y otros tipos
de devices. OpenCL brinda un lenguaje del lado del device y una capa de administracion del
host para los devices en un sistema[5].
Dentro de las arquitecturas admitidas se incluyen: CPU multinucleo, procesadores vectoriales
(tales como la GPU) y devices paralelos de grano fino (como FPGA). Lo mas importante es que
la compatibilidad multiplataforma de OpenCL lo convierte en un excelente modelo de progra-
macion para que los desarrolladores lo aprendan y utilicen, con la confianza de que continuara
estando ampliamente disponible en los proximos anos, con alcance y aplicabilidad cada vez
mayores[5].
La especificacion OpenCL segun [5] se define en cuatro partes, llamadas modelos, que se pueden
5
2.2. INSTALACION DEL AMBIENTE DE DESARROLLO OPENCL EN LINUX 6
resumir como sigue:
1. Modelo de plataforma: especifica que hay un procesador que coordina la ejecucion (el
host) y uno o mas procesadores capaces de ejecutar el codigo C de OpenCL (los devices).
Define un modelo de hardware abstracto que utilizan los programadores al escribir las
funciones C de OpenCL (llamadas kernels) que se ejecutan en los devices.
2. Modelo de ejecucion: define como se configura el entorno OpenCL en el host y como se
ejecutan los kernels en el device. Esto incluye configurar un context (contexto) OpenCL
en el host, proporcionar mecanismos para la interaccion entre el servidor y el device y
definir un modelo de concurrencia utilizado para la ejecucion del nucleo en los devices.
3. Modelo de memoria: define la jerarquıa de memoria abstracta que utilizan los kernels,
independientemente de la arquitectura de memoria subyacente real. El modelo de memoria
se parece mucho a las jerarquıas de memoria GPU actuales, aunque esto no ha limitado
la adoptabilidad de otros aceleradores.
4. Modelo de programacion: define como se mapea el modelo de concurrencia al hardware
fısico.
2.2. Instalacion del Ambiente de Desarrollo OpenCL en
Linux
2.2.1. AMD-Linux
Instalacion de OpenCL, con base a [6] por medio de los siguientes pasos:
1. Instalacion de paquetes prerequisito para los instaladores de AMD y la subsecuente com-
pilacion de c/c++.
sudo apt-get update
2.2. INSTALACION DEL AMBIENTE DE DESARROLLO OPENCL EN LINUX 7
sudo apt-get install build-essential
sudo apt-get install linux-headers-generic
2. Instalacion de de los drivers de AMD 14.4
unzip amd-catalyst-14-4-rev2-linux-x86-x86-64-may6.zip
cd fglrx-14.10.1006.1001
./amd-driver-installer-14.10.1006.1001-x86.x86\_64.run
3. Instalacion de AMD APP SDK 2.9
tar -xvzf AMD-APP-SDK-v2.9-lnx64.tgz
sudo ./Install-AMD.APP.sh
2.2.2. Nvidia-Linux
Instalacion de OpenCL, con base a [7] por medio de los siguientes pasos:
1. Los ejemplos del SDK de OpenCL que se encuentran en el SDK de NVIDIA GPU Com-
puting requieren una GPU con arquitectura CUDA para ejecutarse correctamente. Para
obtener una lista completa de las GPU habilitadas para computacion en arquitectura CU-
DA, consulte la lista en:
http://www.nvidia.com/object/cuda_learn_products.html
2. Las aplicaciones OpenCL en el SDK de NVIDIA GPU Computing requieren la version
258.19 del driver de video NVIDIA o posteriores para correr en Linux de 32 bits o 64 bits.
Este controlador esta disponible para desarrolladores registrados en:
https://nvdeveloper.nvidia.com/login.asp?action=login
Asegurarse de leer el documento de sugerencias de instalacion del controlador antes de
instalarlo:
http://www.nvidia.com/object/driver_installation_hints.html
3. Desinstale todas las versiones anteriores de NVIDIA GPU Computing SDK.
2.3. COMPILACION Y EJECUCION DE PROGRAMAS EN OPENCL 8
4. Instalar el SDK de NVIDIA GPU Computing ejecutando el instalador proporcionado para
su sistema operativo.
La carpeta de instalacion predeterminada para OpenCL SDK en Linux es:
(HOME)/NVIDIA_GPU_Computing_SDK/
2.3. Compilacion y Ejecucion de Programas en OpenCL
Para obtener un ejecutable de OpenCL no es necesario utilizar algun compilador especial, basta
con usar uno que sea capaz de traducir lenguaje C y C++. Para nuestro caso, en el cual
utilizaremos la GPU para hacer procesamiento, la tarea importante consiste en ligar las librerıas
e incluir los archivos correspondientes del fabricante de GPGPU para el cual nos interesa ejecutar
nuestra aplicacion.
La estructura de la lınea de comandos empleada para usar la tarjeta grafica es la siguiente:
gcc <fuente> -I<Directorio de los archivos a incluir del fabricante> -L<Directorio
de las librerıas del fabricante> -lOpenCL -o <ejecutable>
Donde:
<fuente>
Archivo que contiene el codigo fuente normalmente con extension .c
-I
Opcion que indica el directorio donde se encuentran los archivos a incluir del fabricante
<Directorio de las librerıas del fabricante>
Ruta al directorio donde se encuentran los archivos a incluir del fabricante
-L
Opcion que indica el directorio donde se encuentran las librerıas del fabricante
<Directorio de las librerıas del fabricante>
Ruta al directorio donde se encuentran las librerıas del fabricante
2.4. PRIMEROS PASOS DE OPENCL 9
-o
Opcion que indica que el ejecutable estara en un archivo llamado <ejecutable>
<ejecutable>
Archivo Ejecutable
Lo anterior nos generaria un ejecutable, el cual, para ejecutarlo basta con escribir la siguiente
instruccion:
./<ejecutable>
2.4. Primeros Pasos de OpenCL
El framework de OpenCL coloca un encabezado cl.h en una carpeta llamada OpenCL, mientras
que los sistemas Windows y Linux colocan cl.h en una carpeta llamada CL. Esto puede generar
problemas si se tiene la intencion de compilar codigo en multiples sistemas operativos[8].
Antes que nada, lo primero que se debe hacer es importar el encabezado correspondiente de
OpenCL. Para esto, lo importaremos de la siguiente manera:
#ifdef MAC#include <OpenCL/cl.h>#else#include <CL/cl.h>#endif
Con lo anterior garantizamos que se podra acceder al archivo de encabezado cl.h ya sea que
estemos en sistemas Windows, Linux o Mac OS.
Implementando el paso anterior en el archivo donde trabajaremos con las funciones de OpenCL,
ya estamos listos para comenzar a usar las funciones de esta herramienta.
Antes de comenzar a cargar los kernels que seran nuestras funciones que correran en el device
(GPU), debemos hacer una serie de pasos, primero debemos seleccion la plataforma con la cual
trabajaremos y para esto hacemos uso de la siguiente funcion:
2.4. PRIMEROS PASOS DE OPENCL 10
cl_int clGetPlatformIDs(cl_uint num_entries, cl_platform_id *platforms,cl_uint *num_platforms)
Donde:
num entries
El numero de entradas cl platform id que se pueden agregar a las plataformas. Si las
plataformas no son NULL, num entries debe ser mayor que cero.
platforms
Devuelve una lista de las plataformas OpenCL encontradas. Los valores cl platform id
devueltos en las plataformas se pueden usar para identificar una plataforma OpenCL
especıfica. Si el argumento de plataformas es NULL, este argumento se ignora. El numero
de plataformas OpenCL devuelto es el mınimo del valor especificado por num entries o la
cantidad de plataformas OpenCL disponibles.
num platforms
Devuelve la cantidad de plataformas OpenCL disponibles. Si num platforms es NULL,
este argumento se ignora.
Seleccionada nuestra plataforma proseguimos a buscar y encontrar el device que utilizaremos y
en el cual correran nuestros kernels, esto lo podemos lograr con la siguiente funcion:
cl_int clGetDeviceIDs(cl_platform_id platform, cl_device_type device_type,cl_uint num_entries, cl_device_id *devices, cl_uint *num_devices)
Donde:
platform
Se refiere a la ID de la plataforma devuelta por clGetPlatformIDs o puede ser NULL. Si
la plataforma es NULL, el comportamiento esta definido por la implementacion.
device type
Un campo de bits que identifica el tipo de device OpenCL. El device type se puede usar
para consultar devices OpenCL especıficos o todos los devices OpenCL disponibles. Los
valores validos para device type se especifican en la tabla 2.1.
2.4. PRIMEROS PASOS DE OPENCL 11
num entries
El numero de entradas de cl device que se pueden agregar a devices. Si devices no es
NULL, los num entries deben ser mayores que cero.
devices
Una lista de devices OpenCL encontrados. Los valores cl device id devueltos en los devices
se pueden usar para identificar un device OpenCL especıfico. Si el argumento de los devices
es NULL, este argumento se ignora. El numero de devices OpenCL devueltos es el mınimo
del valor especificado por num entries o la cantidad de devices OpenCL cuyo tipo coincide
con device type.
num devices
La cantidad de devices OpenCL disponibles que coinciden con device type. Si num devices
es NULL, este argumento se ignora.
cl device type Descripcion
CL DEVICE TYPE CPU Un device OpenCL que es el procesador host. El proce-
sador host ejecuta las implementaciones OpenCL y es
una CPU de un solo nucleo o multi-nucleo.
CL DEVICE TYPE GPU Un device OpenCL que es una GPU. Esto quire decir
que el device tambien se puede usar para acelerar una
API 3D como OpenGL o DirectX.
CL DEVICE TYPE ACCELERATOR Aceleradores dedicados de OpenCL (por ejemplo, IBM
CELL Blade). Estos devices se comunican con el proce-
sador host utilizando una interconexion periferica como
PCIe.
CL DEVICE TYPE DEFAULT El device predeterminado de OpenCL en el sistema.
CL DEVICE TYPE ALL Todos los devices OpenCL disponibles en el sistema.
Tabla 2.1: Valores soportados por device type
Tabla traducida de la original en [9]
2.4. PRIMEROS PASOS DE OPENCL 12
Por ultimo, es necesario crear el context (contexto). En OpenCL, un context es un contenedor
abstracto que existe en el host. Un context coordina los mecanismos para la interaccion entre
el device y el host, gestiona los objetos de memoria que estan disponibles para los devices y
realiza un seguimiento de los programas y kernels que se crean para cada device[5].
Para crear el context se utiliza la siguiente funcion:
cl_context clCreateContext(cl_context_properties *properties, cl_uintnum_devices, const cl_device_id *devices, void *pfn_notify (const char *errinfo, const void *private_info, size_t cb, void *user_data), void *user_data, cl_int *errcode_ret)
Donde:
properties
Especifica una lista de nombres de propiedades de context y sus valores correspondientes.
Cada nombre de propiedad es seguido inmediatamente por el valor deseado correspondien-
te. La lista termina con 0. las propiedades pueden ser NULL, en cuyo caso la plataforma
que se selecciona esta definida por la implementacion.
num devices
La cantidad de devices especificados en el argumento de devices.
devices
Un apuntador a una lista de devices unicos devueltos por clGetDeviceIDs para una pla-
taforma.
pfn notify
Funcion de devolucion de llamada que puede ser registrada por la aplicacion. Sera utilizada
por la implementacion OpenCL para informar sobre errores que ocurren en este context.
Esta funcion puede ser llamada asincronicamente por la implementacion de OpenCL. Es
responsabilidad de la aplicacion asegurarse que la llamada a esta funcion sea segura para
subprocesos. Si pfn notify es NULL, no se registra ninguna devolucion de llamada. Los
parametros de esta funcion son:
2.5. CREACION Y EJECUCION DE FUNCIONES EN EL DEVICE 13
• errinfo
Es un apuntador a una cadena de error.
• private info y cb
Representan un apuntador a los datos binarios que devuelve la implementacion de
OpenCL que se puede utilizar para registrar informacion adicional util para depurar
el error.
• user data
Es un apuntador a los datos proporcionados por el usuario.
user data
Pasado como el argumento user data cuando se llama a pfn notify.
user data puede ser NULL.
errcode ret
Devuelve un codigo de error apropiado. Si errcode ret es NULL, no se devuelve ningun
codigo de error.
2.5. Creacion y Ejecucion de Funciones en el Device
El framework de OpenCL define un lenguaje para escribir “kernels”. Los kernels son funciones
que pueden ejecutarse en diferentes devices de computo. OpenCL define un lenguaje C extendido
para escribir kernels, y un conjunto de APIs para crear y administrar estos kernels. Los kernels
son codificados por un compilador en tiempo de ejecucion, el cual los compila sobre la marcha
durante la ejecucion de la aplicacion del host para el device de destino. Esto permite que
la aplicacion host aproveche todos los devices del sistema con un unico conjunto de kernels
portatiles.[10]
Los kernels comienzan con la palabra clave kernel y deben ser de tipo void. La lista de argu-
mentos es como para una funcion C, con el requisito adicional de que se debe especificar el tipo
de espacio de memoria de cualquier apuntador.[5]
2.5. CREACION Y EJECUCION DE FUNCIONES EN EL DEVICE 14
Estos kernels suelen estar escritos en una cadena de caracteres o mas convenientemente en un
archivo para luego ser cargado a una cadena.
Una vez que tenemos nuestro codigo escrito y cargado en un buffer, usamos la siguiente funcion:
cl_program clCreateProgramWithSource(cl_context context, cl_uint count, constchar **strings, const size_t *lengths, cl_int *errcode_ret)
Donde:
context
Debe ser un context valido de OpenCL.
count
Contador del numero de apuntadores que tiene el argumento strings.
strings
Mantiene el conteo del numero de apuntadores. La combinacion de todas las cadenas en
este argumento constituye codigo fuente desde el cual se creara el objeto del programa.
lengths
Una matriz con el numero de caracteres en cada cadena del argumento strings. Si un
elemento en su longitud es cero, la cadena que lo acompana tiene terminacion nula. Si las
longitudes son nulas, todas las cadenas en el argumento de strings se consideran terminadas
en nulo.
errcode ret
Devuelve el codigo de error apropiado. Si errcode ret es NULL, no se devuelve ningun
codigo de error.
El objeto del tipo cl program devuelto por la funcion clCreateProgramWithSource nos servira
para ejecutar la siguiente funcion:
cl_int clBuildProgram(cl_program program, cl_uint num_devices, constcl_device_id *device_list, const char *options, void (CL_CALLBACK *pfn_notify) (cl_program program, void *user_data), void *user_data)
2.5. CREACION Y EJECUCION DE FUNCIONES EN EL DEVICE 15
Donde:
program
Un objeto cl program valido (el objeto resultante del metodo clCreateProgramWithSour-
ce).
num devices
La cantidad de devices listados en device list para los cuales construir el objeto del pro-
grama.
device list
Un apuntador a una lista de devices que estan en el programa. Si device list es NULL, el
ejecutable del programa se crea para todos los devices asociados con el programa para el
cual se ha cargado el fuente o un binario. Si device list no es nulo, el programa ejecutable
se creara para los devices especificados en esta lista para los cuales se ha cargado el fuente
o un binario.
options
Un apuntador a una cadena que describe las opciones de compilacion que se utilizaran
para compilar el programa ejecutable.
pfn notify
Un apuntador a la funcion de una rutina de notificacion. La rutina de notificacion es una
funcion de devolucion de llamada que una aplicacion puede registrar y que sera invocada
cuando el programa ejecutable haya sido creado (con exito o sin exito). Si pfn notify no
es NULL, clBuildProgram no necesita esperar a que la compilacion se complete y puede
regresar inmediatamente. Si pfn notify es NULL, clBuildProgram no regresa hasta que
se complete la compilacion. Esta funcion de devolucion de llamada puede ser llamada
asincronicamente por la implementacion de OpenCL. Es responsabilidad de la aplicacion
asegurarse de que la funcion de devolucion de llamada sea segura para subprocesos.
user data
Pasado como un argumento cuando se llama a pfn notify. El parametro user data puede
ser NULL.
2.5. CREACION Y EJECUCION DE FUNCIONES EN EL DEVICE 16
Hasta este punto, nuestro codigo con nuestras funciones ya ha sido creado y compilado. Puede
llamarse la funcion clGetProgramBuildInfo que nos ayuda a extraer un log de la compilacion
del codigo. Esta funcion es util en caso de que el codigo haya tenido errores al compilarse.
Una vez completados los pasos anteriores, ya se pueden crear los kernels. Para esto se utiliza la
siguiente instruccion:
cl_kernel clCreateKernel(cl_program program, const char *kernel_name, cl_int *errcode_ret)
Donde:
program
Es un objeto cl program que haya sido compilado satisfactoriamente.
kernel name
Es el nombre de la funcion kernel para la cual se crea el objeto cl kernel. Este es el nombre
de la funcion que sigue a la palabra clave kernel en el programa fuente.
errcode ret
Devuelve el codigo de error apropiado. Si errcode ret es NULL, no se devuelve ningun
codigo de error.
Creados nuestros kernels, ya solo falta ejecutarlos. Para esto necesitamos hacer otra serie de pa-
sos, primero les pasaremos los parametros declarados en nuestro kernel con la siguiente funcion:
cl_int clSetKernelArg(cl_kernel kernel, cl_uint arg_index, size_t *arg_size,const void *arg_value)
Donde:
kernel
Es un objeto cl kernel valido (nuestro kernel creado en los pasos anteriores)
arg index
Es el ındice del argumento. Los argumentos pasados al kernel son refierenciados por ındices
2.5. CREACION Y EJECUCION DE FUNCIONES EN EL DEVICE 17
que van desde 0 para el argumento de la izquierda, hasta n - 1 donde n es la cantidad
total de argumentos declarados por un kernel.
arg size
Especifica el tamano del valor del argumento. Si el argumento es un objeto de memoria, el
tamano es el tamano del bufer o tipo de objeto de imagen. Para los argumentos declarados
con el cualificador local, el tamano especificado sera el tamano en bytes del bufer que
debe asignarse para el argumento local. Si el argumento es de tipo sampler t, el valor de
arg size debe ser igual a sizeof (cl sampler). Para todos los demas argumentos, el tamano
sera el tamano del tipo del argumento.
arg value
Un apuntador al argumento que se pasara a la funcion kernel. Este argumento tambien
dependera de la forma en que se declare el argumento en el kernel.
La funcion clSetKernelArg se ejecutara tantas veces como argumentos tengamos en nuestro
kernel. Falta crear un command queue (cola de comandos).
Cada command queue esta asociado con un device. El kernel se pondra en cola para su ejecucion
en este device. El objeto command queue se crea utilizando la funcion clCreateCommandQueue[10].
cl_command_queue clCreateCommandQueueWithProperties(cl_context context,cl_device_id device, const cl_queue_properties *properties, cl_int *errcode_ret)
Donde:
context
Debe ser un context valido de OpenCL.
device
Debe ser un device asociado con el context. Puede estar en la lista de devices especificados
cuando se crea el context utilizando clCreateContext o tiene el mismo tipo de device que el
tipo de device especificado cuando se crea el context utilizando clCreateContextFromType.
properties
Especifica una lista de propiedades para la cola de comandos y sus valores correspondien-
2.5. CREACION Y EJECUCION DE FUNCIONES EN EL DEVICE 18
tes. Cada nombre de propiedad es seguido inmediatamente por el correspondiente valor
deseado. La lista finaliza con 0.
errcode ret
Devuelve el codigo de error apropiado. Si errcode ret es NULL, no se devuelve ningun
codigo de error.
Finalmente podemos ejecutar nuestro kernel o lo que es lo mismo lo ponemos en la cola de
comandos para ser ejecutado por el device correspondiente. Para realizar esta tarea es necesario
ejecutar la siguiente funcion:
cl_int clEnqueueNDRangeKernel (cl_command_queue command_queue, cl_kernelkernel, cl_uint work_dim, const size_t *global_work_offset, const size_t *global_work_size, const size_t *local_work_size, cl_uintnum_events_in_wait_list, const cl_event *event_wait_list, cl_event *event)
Donde:
command queue
Un command queue valido. El kernel se pondra en cola para su ejecucion en el device
asociado con command queue.
kernel
Un objeto kernel valido. El context asociado con el kernel y el command queue debe ser
el mismo.
work dim
La cantidad de dimensiones utilizadas para especificar global work-items y work-items en
work-groups. El parametro work dim debe ser mayor que cero y menor o igual que tres.
global work offset
Hasta la version 1.2 de OpenCL debe ser NULL. En una futura revision de OpenCL, glo-
bal work offset se puede usar para especificar una matriz de valores no asignados work dim
que describen el desplazamiento utilizado para calcular el ID global de un elemento de
trabajo en lugar de tener los ID globales siempre en offset (0, 0 ,. .. 0).
2.5. CREACION Y EJECUCION DE FUNCIONES EN EL DEVICE 19
global work size
Apunta a una matriz de valores sin signo work dim que describen el numero de work-items
globales en las dimensiones de work dim que ejecutaran la funcion kernel. El numero total
de work-items globales se calcula como global work size[0] * ... * global work size[work dim
- 1].
local work size
Apunta a una matriz de valores no asignados work dim que describen el numero de work-
items que componen un work-group (tambien conocido como el tamano del work-group)
que ejecutara el kernel especificado por kernel. El numero total de work-items en un work-
group se calcula como local work size[0] * ... * local work size[work dim - 1].
num events in wait list y event wait list
Especifica los eventos que deben completarse antes de que se pueda ejecutar este comando
en particular. Si event wait list es NULL, este comando en particular no espera a que
se complete ningun evento. Si event wait list es NULL, num events in wait list debe ser
0. Si event wait list no es NULL, la lista de eventos apuntada por event wait list debe
ser valida y num events in wait list debe ser mayor que 0. Los eventos especificados en
event wait list actuan como puntos de sincronizacion. El context asociado con los eventos
en event wait list y command queue debe ser el mismo.
event
Devuelve un objeto de evento que identifica esta instancia en particular de la ejecucion
del kernel. Los objetos de evento son unicos y pueden usarse para identificar una instancia
en particular de ejecucion del kernel mas adelante. Si event es NULL, ningun evento sera
creado para esta instancia de ejecucion del kernel y, por lo tanto, no sera posible que la
aplicacion consulte o espere una cola para esta instancia particular de ejecucion del kernel.
Al finalizar la ejecucion de un kernel podemos leer los resultados del device (GPGPU) y pasarlos
al host (CPU), tanto la lectura de los buffers del device al host como la escritura de los buffers
del host al device son explicados mas a fondo en la seccion 2.7.
2.6. CUALIFICADORES EN VARIABLES 20
2.6. Cualificadores en Variables
El modelo de memoria OpenCL define cinco regiones de memoria distintas:
Memoria de host: esta region de memoria solo es visible para el host. Al igual que con
la mayorıa de los detalles sobre el host, OpenCL solo define como interactua la memoria
del host con objetos y construcciones OpenCL.
Memoria global: esta region de memoria permite el acceso de lectura/escritura a todos
los elementos de trabajo en todos los grupos de trabajo. Los elementos de trabajo pueden
leer o escribir en cualquier elemento de un objeto de memoria en la memoria global. Las
lecturas y escrituras en la memoria global pueden almacenarse en cache dependiendo de
las capacidades del device.
Memoria constante: esta region de la memoria global permanece constante durante
la ejecucion de un kernel. El host asigna e inicializa objetos de memoria colocados en la
memoria constante. Los elementos de trabajo tienen acceso de solo lectura a estos objetos.
Memoria local: esta region de memoria es local para un grupo de trabajo y se puede
usar para asignar variables que comparten todos los elementos de trabajo en ese grupo
de trabajo. La memoria local se puede implementar como regiones de memoria dedicadas.
Otra alternativa es que, la region de memoria local puede mapearse en secciones de la
memoria global.
Memoria privada: esta region de memoria es privada para un elemento de trabajo. Las
variables definidas en la memoria privada de un elemento de trabajo no son visibles para
otros elementos de trabajo.
[11]
El modelo de memoria se ilustra en la Figura 2.1.
2.7. GESTION DE MEMORIA Y COMUNICACION DE DATOS ENTRE CPU Y GPU 21
Figura 2.1: Modelo de memoria de OpenCL.
Imagen traducida de la original en [12]
Una vez explicado el modelo de memoria de OpenCL, tenemos los 4 cualificadores posibles en
variables, los cuales son: global, constant, local, y private.
2.7. Gestion de Memoria y Comunicacion de Datos En-
tre CPU y GPU
Las aplicaciones OpenCL a menudo funcionan con matrices grandes o matrices multidimensiona-
les. Esta informacion debe estar fısicamente presente en un device antes de que pueda comenzar
la ejecucion. Para que los datos se transfieran a un device, primero se debe encapsular como un
objeto de memoria. OpenCL define dos tipos de objetos de memoria: buffers e imagenes. Los
2.7. GESTION DE MEMORIA Y COMUNICACION DE DATOS ENTRE CPU Y GPU 22
buffers son equivalentes a los arreglos en C, creadas con malloc (), donde los elementos de datos
se almacenan contiguamente en la memoria. Las imagenes, por otro lado, estan disenadas como
objetos opacos, lo que permite el relleno de datos y otras optimizaciones que pueden mejorar el
rendimiento en los devices. Siempre que se crea un objeto de memoria, es valido solo dentro de
un context unico. El movimiento hacia y desde devices especıficos es gestionado en tiempo de
ejecucion de OpenCL, segun sea necesario para satisfacer las dependencias de datos.[5]
Teniendo el host (CPU) por un lado y el device (GPU) por el otro como dos entidades diferentes,
necesitamos poder hacer que se comuniquen entre sı.
2.7.1. Comunicacion Host-Device
Para transferir datos del host al device, se crea el buffer que es el encapsulamiento de nuestros
datos como un objeto de memoria (explicado anteriormente en el inicio de esta seccion). Para
esto, se hace uso de la siguiente funcion:
cl_mem clCreateBuffer(cl_context context, cl_mem_flags flags, size_t size,void * host_ptr, cl_int *errcode_ref)
Donde:
context
Debe ser un context valido de OpenCL.
flags
Un campo de bit utilizado para especificar asignaciones e informacion de uso para la
creacion del buffer. El conjunto de valores validos para flags, definidos por la enumeracion
cl mem flags, se describe en la Tabla 2.2.
size
El tamano en bytes del objeto de memoria intermedia a asignar.
host ptr
Un apuntador a los datos del buffer que la aplicacion ya puede asignar. El tamano del
2.7. GESTION DE MEMORIA Y COMUNICACION DE DATOS ENTRE CPU Y GPU 23
buffer al que apunta host ptr debe ser mayor o igual que el tamano de los bytes.
errcode ret
Devuelve el codigo de error apropiado. Si errcode ret es NULL, no se devuelve ningun
codigo de error.
2.7. GESTION DE MEMORIA Y COMUNICACION DE DATOS ENTRE CPU Y GPU 24
cl mem flags Descripcion
CL MEM READ WRITE Esta bandera especifica que el objeto de memoria sera leıdo y
escrito por un kernel. Este es el valor por default.
CL MEM WRITE ONLY Esta bandera especifica que el objeto de memoria sera escrito
pero no leıdo por un kernel.
CL MEM READ ONLY Este indicador especifica que el objeto de memoria es un ob-
jeto de memoria de solo lectura cuando se usa dentro de un
kernel.
CL MEM USE HOST PTR Este indicador es valido solo si host ptr no es NULL. Si se es-
pecifica, indica que la aplicacion quiere que la implementacion
OpenCL use memoria a la que hace referencia host ptr como
los bits de almacenamiento para el objeto de memoria. Las
implementaciones de OpenCL estan habilitadas para almace-
nar en cache los contenidos del buffer apuntados por host ptr
en la memoria del device. Esta copia en cache se puede usar
cuando los kernels se ejecutan en un device.
CL MEM ALLOC HOST PTR Este indicador especifica que la aplicacion requiere que
la implementacion OpenCL asigne memoria desde la me-
moria accesible del host. CL MEM ALLOC HOST PTR y
CL MEM USE HOST PTR son mutuamente excluyentes.
CL MEM COPY HOST PTR Este indicador es valido solo si host ptr no es NULL. Si se
especifica, indica que la aplicacion quiere que la implemen-
tacion OpenCL asigne memoria para el objeto de memoria y
copie los datos de la memoria a la que hace referencia host ptr.
CL MEM COPY HOST PTR y CL MEM USE HOST PTR
son mutuamente excluyentes. CL MEM COPY HOST PTR
se puede usar con CL MEM ALLOC HOST PTR para ini-
cializar el contenido del objeto cl mem asignado utilizando la
memoria accesible al host.
Tabla 2.2: Valores soportados por cl mem flags
Tabla traducida de la original en [9]
2.7. GESTION DE MEMORIA Y COMUNICACION DE DATOS ENTRE CPU Y GPU 25
Despues de esta funcion, los datos se encontaran en la memoria intermedia que, como se puede
ver en la Figura 2.1 que serıa en el espacio de memoria global/constant. Ahora con nuestros
datos en el device, podemos usarlos en un kernel como parametro con la funcion clSetKernelArg
explicada anteriormente en la seccion 2.5.
2.7.2. Comunicacion Device-Host
Una vez que el kernel ha sido ejecutado, se pueden recuperar o leer los valores que fueron
almacenados/modificados en algun buffer que se haya pasado al kernel como parametro, para
posteriormente imprimirlos, analizarlos o realizar cualquier otro tipo de calculos. Para este caso,
tenemos la siguiente funcion:
cl_int clEnqueueReadBuffer(cl_command_queue command_queue, cl_mem buffer,cl_bool blocking_read, size_t offset, size_t cb, void * ptr, cl_uintnum_events_in_wait_list, const cl_event * event_wait_list, cl_event *event)
Donde:
command queue
Se refiere a la cola de comandos en la que se pondra en cola el comando de lectura.
command queue y el buffer deben crearse con el mismo context OpenCL.
buffer
Se refiere a un objeto de bufer valido.
blocking read
Indica si las operaciones de lectura son de bloqueo o no bloqueo. Si blocking read es
CL TRUE, entonces, el comando de lectura esta en modo bloqueo, clEnqueueReadBuffer
no regresa hasta que los datos del buffer hayan sido leıdos y copiados en la memoria
apuntada por ptr.
Si blocking read es CL FALSE, es decir, el comando de lectura no es de bloqueo, clEn-
queueReadBuffer pone en cola un comando de lectura de no bloqueo y lo devuelve. El
contenido del buffer al que apunta ptr no se puede utilizar hasta que se haya completado
2.7. GESTION DE MEMORIA Y COMUNICACION DE DATOS ENTRE CPU Y GPU 26
el comando de lectura. El argumento de event devuelve un objeto de evento que se puede
usar para consultar el estado de ejecucion del comando de lectura. Cuando se completa el
comando de lectura, la aplicacion puede usar los contenidos del buffer al que apunta ptr.
offset
El desplazamiento en bytes en el objeto del bufer para leer.
cb
El tamano en bytes de los datos que se leen.
ptr
El apuntador para almacenar en la memoria del host donde se deben almacenar los datos.
num events in wait list y event wait list
event wait list y num events in wait list especifican los eventos que deben completarse
antes de que se pueda ejecutar este comando en particular. Si event wait list es NULL,
este comando en particular no espera a que se complete ningun evento. Si event wait list es
NULL, num events in wait list debe ser 0. Si event wait list no es NULL, la lista de eventos
apuntada por event wait list debe ser valida y num events in wait list debe ser mayor que
0. Los eventos especificados en event wait list actuan como puntos de sincronizacion. El
context asociado con los eventos en event wait list y command queue debe ser el mismo.
event
Devuelve un objeto de tipo evento que identifica este comando de lectura y se puede
usar para consultar o poner en cola una espera para que se complete este comando. El
aparametro event puede ser NULL, en cuyo caso la aplicacion no podra consultar el estado
de este comando ni esperar a que este se complete.
Capıtulo 3
OpenMPI
3.1. OpenMPI
OpenMPI es una implementacion de la interfaz de paso de mensajes de codigo abierto, desa-
rrollada y mantenida por un consorcio de socios academicos, de investigacion y de la industria.
Por lo tanto, OpenMPI puede combinar la experiencia, las tecnologıas y los recursos de toda
la comunidad informatica de alto rendimiento, para crear la mejor biblioteca de MPI disponi-
ble. OpenMPI ofrece ventajas para los proveedores de sistemas y software, desarrolladores de
aplicaciones e investigadores de informatica[13].
3.1.1. MPI
MPI (Message-Passing Interface) es una especificacion de la interfaz de biblioteca para el paso
de mensajes. Todas las partes de esta definicion son significativas. MPI aborda principalmente
el modelo de programacion paralela de transmision de mensajes, en el que los datos se trasla-
dan del espacio de direcciones de un proceso a otro mediante operaciones cooperativas en cada
proceso. Las extensiones del modelo clasico de paso de mensajes se proporcionan en operacio-
nes colectivas, operaciones de acceso a memoria remota, creacion de procesos dinamicos y E/S
paralelas. MPI es una especificacion, no una implementacion. Esta especificacion es para una
27
3.2. INSTALACION DE OPENMPI EN LINUX 28
interfaz de biblioteca; MPI no es un lenguaje, y todas las operaciones MPI se expresan como
funciones, subrutinas o metodos. El estandar ha sido definido a traves de un proceso abier-
to por una comunidad de vendedores de computo paralelo, informaticos y desarrolladores de
aplicaciones[14].
3.2. Instalacion de OpenMPI en Linux
Para instalar OpenMPI en Linux es necesario completar los siguientes pasos:
1. Descargar OpenMPI
Ir a www.open-mpi.org en el apartado download, para descargar el archivo:
openmpi-2.0.2.tar.gz
y mover de descargas a carpeta personal
2. Instalar
En la carpeta personal, ejecutar:
tar xvzf openmpi-2.0.2.tar.gz
cd openmpi-2.0.2
./configure --prefix /home/<usuario>/openmpi
make all install
cd /home/<usuario>
gedit .bashrc
Agregar:
PATH=$PATH:/home/<usuario>/openmpi/bin
LD_LIBRARY_PATH=/home/<usuario>/openmpi/lib
export PATH LD_LIBRARY_PATH
3. Reiniciar el equipo
3.3. CONFIGURACION DEL CLUSTER 29
3.3. Configuracion del Cluster
El cluster consta de 2 equipos, cuyas caracterısticas se describen en las tablas 3.1 y 3.2:
Equipo 1
GPU AMD Radeon R9 270X con 4GB de memoria global, 1280
nucleos y una frecuencia de reloj de 1050 MHz
Procesador AMD FX(tm)-8320 Eight-Core Processor @ 3.50GHz x 4
RAM 8 GB a 1600 MHz
Librerıa OpenCL 1.2
SO Linux Mint 17.3 Cinnamon 64-bit
Tabla 3.1: Especificaciones del equipo 1
Equipo 2
GPU GeForce GT 730 con 2GB de memoria global, 96 nucleos y
una frecuencia de reloj de 700 MHz
Procesador Intel core i7-4790 CPU @ 3.60 GHz x 4
RAM 8 GB a 1600 MHz
Librerıa OpenCL 1.1
SO Linux Mint 17.3 Cinnamon 64-bit
Tabla 3.2: Especificaciones del equipo 2
3.3. CONFIGURACION DEL CLUSTER 30
A continuacion se presentan los pasos que se siguieron para la creacion del cluster:
1. Agregar una conexion de red en cada computadora
Hacer clic en el icono de conexion de red (/) del panel inferior del escritorio.
Hacer clic en Conexiones de red.
En la ventana Conexiones de red, hacer clic en el boton Anadir.
En la ventana Elegir el tipo de conexion, hacer clic en el boton Crear.
En la ventana Editando conexion cableada, escribir cableada en el cuadro de
texto Nombre de la conexion.
Hacer clic en la pestana General y quitar la seleccion a la casilla: Conectarse
automaticamente a esta red cuando este disponible.
Hacer clic en la pestana Ajustes Ipv4.
en lugar de Automatico (DHCP), elegir el metodo Manual.
En Direccion, hacer clic sobre el boton Anadir, para que se habilite la edicion.
Debajo de Direccion escribir: 10.1.1.N (N es el numero de la maquina y debera
estar en el rango de 1 a 2)
Debajo de Mascara de red escribir: 24
Debajo de la puerta de enlace escribir: 10.1.1.1
Hacer clic en el boton Guardar.
Hacer clic en el boton Cerrar.
2. Reiniciar la computadora
Al reiniciar la computadora ya no aparecera la red original: Conexion cableada 1.
Para restaurarla, hay que: Anadir, Guardar y Cerrar.
3. Hacer clic en el icono de red (/) del panel inferior del escritorio
Elegir la red MPI.
Cambiarse al modo superusuario.
3.4. FUNCIONES DE OPENMPI 31
su -l
cd /etc
gedit hosts
Del archivo hosts, comentar la lınea que contiene: 127.0.1.1
Agregar las lıneas:
• 10.1.1.1 MA01
• 10.1.1.2 MA02
En la maquina MA01 (como usuario normal) escribir la orden:
ping MA02
(Para comprobar que hay comunicacion entre estas dos maquinas)
4. Acceder a una maquina desde otra
El objetivo de agrupar las computadoras en un cluster es ejecutar computo distribui-
do, por lo que cada maquina debera tener acceso a los recursos de todas las demas.
Para acceder a la maquina MA02, estando en la maquina MA01 (sin tener que teclear
una contrasena) se utilizo secure shell, con el siguiente comando:
ssh MA02
La primera vez aparecera un mensaje que comienza con:
The autenticity......
Teclear yes (en las subsecuentes ocasiones no volvera a mostrarse dicho mensaje)
3.4. Funciones de OpenMPI
El archivo de encabezado mpi.h contiene los prototipos de funciones MPI, definiciones de ma-
cros, definiciones de tipos, etc. Contiene todas las definiciones y declaraciones necesarias para
compilar un programa MPI. Conviene observar que todos los identificadores definidos por MPI
comienzan con la cadena MPI. p Para los nombres de funciones y los tipos definidos por MPI,
la primera letra que sigue al guion bajo se escribe en mayuscula. Todas las letras en macros y
3.4. FUNCIONES DE OPENMPI 32
constantes definidas por MPI estan en mayuscula, por lo que no hay dudas sobre que define
MPI y que define el programa de usuario[15].
A continuacion se muestran las funciones utilizadas para este proyecto, la explicacion de su
funcionamiento y la descripcion de los argumentos.
MPI Init
Esta funcion le dice a MPI que realice toda la configuracion necesaria. Por ejemplo, podrıa
asignar almacenamiento para los buferes de mensajes, y podrıa decidir que proceso obtiene
que rango. Como regla general, no se deben invocar a otras funciones de MPI antes de que el
programa invoque a MPI Init[15].
Los argumentos argc p y argv p son apuntadores a los argumentos de main: argc y argv. Sin
embargo, cuando nuestro programa no utiliza estos argumentos, podemos pasar NULL para
ambos. Al igual que la mayorıa de las funciones de OpenMPI, MPI Init devuelve un codigo de
error de tipo entero, y en la mayorıa de los casos ignoraremos estos codigos de error[15].
Declaracion:
int MPI_Init(int *argc, char ***argv)
Argumentos:
• argc
Apuntador al numero de argumentos.
• argv
Vector de argumentos.
MPI Finalize
Le dice al sistema MPI que se ha terminado de usar MPI, y que cualquier recurso asignado
para MPI puede ser liberado. En general, no se debe invocar a ninguna funcion MPI despues
de finalizar la llamada a MPI[15].
Declaracion:
int MPI_Finalize()
3.4. FUNCIONES DE OPENMPI 33
MPI Comm size
Esta funcion indica la cantidad de procesos involucrados en un comunicador. Para MPI COMM WORLD,
indica la cantidad total de procesos disponibles.
Declaracion:
int MPI_Comm_size(MPI_Comm comm, int *size)
Argumentos:
• comm
Comunicador.
• size
Numero de procesos en el grupo del comunicador.
MPI Comm rank
Esta funcion proporciona el identificador del proceso en un comunicador de grupo en particular.
Muchos programas utilizan el modelo maestro-esclavo, donde un proceso (como el proceso de
rango cero) hace el papel de supervisor, y los otros procesos serviran como nodos esclavos que
realizaran los calculos.
Declaracion:
int MPI_Comm_rank(MPI_Comm comm, int *rank)
Argumentos:
• comm
Comunicador.
• rank
Identificador del proceso en el grupo del comunicador.
MPI Send
La forma mas basica de intercambio de datos entre los procesos es la comunicacion punto a
punto. Participan dos procesos en esta operacion de comunicacion: Un proceso de envıo ejecuta
3.4. FUNCIONES DE OPENMPI 34
la operacion de envıo y un proceso de recepcion ejecuta una operacion de recepcion correspon-
diente. La operacion de envıo es de bloqueo[16].
Declaracion:
int MPI_Send(const void *buf, int count, MPI_Datatype datatype, intdest, int tag, MPI_Comm comm)
Argumentos:
• buf
Direccion inicial del buffer de envıo.
• count
Numero de elementos enviados.
• datatype
Tipo de dato de cada elemento del buffer de envıo.
• dest
Identificador del proceso de destino.
• tag
Etiqueta de mensaje.
• comm
Comunicador.
MPI Recv
Operacion de recepcion con bloqueo. Solo regresa despues de que el bufer de recepcion contiene
el mensaje recien recibido.
Declaracion:
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source,int tag, MPI_Comm comm, MPI_Status *status)
Argumentos:
• buf
Direccion inicial del buffer de recepcion.
3.4. FUNCIONES DE OPENMPI 35
• count
Numero maximo de elementos a recibir.
• datatype
Tipo de dato de cada elemento del buffer de recepcion.
• source
Identificador del proceso fuente.
• tag
Etiqueta de mensaje.
• comm
Comunicador.
• status
Estado
En la Tabla 3.3 se pueden ver los tipos de datos predefinidos por MPI.
3.5. CREACION, COMPILACION Y EJECUCION DE PROGRAMAS CON OPENMPI 36
Tipos de Datos
MPI C
MPI CHAR signed char
MPI SHORT signed short int
MPI INT signed int
MPI LONG signed long int
MPI LONG LONG INT long long int
MPI UNSIGNED CHAR unsigned char
MPI UNSIGNED SHORT unsigned short int
MPI UNSIGNED unsigned int
MPI UNSIGNED LONG unsigned long int
MPI UNSIGNED LONG LONG unsigned long long int
MPI FLOAT float
MPI DOUBLE double
MPI LONG DOUBLE long double
MPI WCHAR wide char
MPI PACKED special data type for packing
MPI BYTE single byte value
Tabla 3.3: Tipos de datos predefinidos en MPI.
Tabla tomada de [13]
3.5. Creacion, Compilacion y Ejecucion de Programas
con OpenMPI
Normalmente, mpicc es un script que es un contenedor para el compilador de C. Un script de
contenedor es un script cuyo principal proposito es ejecutar algun programa. En este caso, el
programa es el compilador de C. Sin embargo, el contenedor simplifica la ejecucion del compila-
dor, diciendole donde encontrar los archivos de encabezado necesarios y que bibliotecas vincular
3.5. CREACION, COMPILACION Y EJECUCION DE PROGRAMAS CON OPENMPI 37
con el archivo objeto[15].
Para compilar basta con escribir en la linea de comandos la siguiente instruccion:
mpicc <fuente> -o <ejecutable>
Donde:
<fuente>
Archivo que contiene el codigo fuente, normalmente con extension .c
-o
Opcion que indica que el ejecutable estara en un archivo llamado <ejecutable>.
<ejecutable>
Archivo de salida que sera nuestro ejecutable.
Para ejecutar un programa basta con escribir, en la lınea de comandos, la siguiente instruccion:
mpirun -np <numero de procesos> ./<ejecutable>
Donde:
-np
Opcion que sirve para indicar el numero de procesos que ejecutaran el programa.
<numero de procesos>
Entero positivo que indica el numero de procesos que ejecutaran el programa.
<ejecutable>
Archivo ejecutable.
La opcion utilizada para este proyecto es la siguiente:
mpirun -np <numero de procesos> -hostfile <Archivo con los host> ./<ejecutable>
Donde:
3.6. COMPILACION Y EJECUCION DE PROGRAMAS OPENMPI-OPENCL 38
-np
Opcion que sirve para indicar el numero de procesos que ejecutaran el programa.
<numero de procesos>
Entero positivo que indica el numero de procesos que ejecutaran el programa.
-hostfile
Opcion que sirve para indicar el nombre del archivo donde estan los hosts.
<Archivo con los host>
Nombre del archivo con los hosts.
<ejecutable>
Archivo ejecutable
3.6. Compilacion y Ejecucion de Programas OpenMPI-
OpenCL
En OpenCL no se necesita un compilador externo. Basta con utilizar el compilador mpicc. La
parte importante radica en ligar las librerıas correspondientes del fabricante de la tarjeta grafica
en la cual queremos que nuestra aplicacion se ejecute.
Compilacion:
mpicc <fuente> -I<Directorio de los archivos a incluir del fabricante> -L<Directorio
de las librerıas del fabricante> -lOpenCL -o <ejecutable>
Donde:
<fuente>
Archivo que contiene el codigo fuente normalmente con extension .c
-I
Opcion que indica el directorio donde se encuentran los archivos del fabricante a incluir.
3.6. COMPILACION Y EJECUCION DE PROGRAMAS OPENMPI-OPENCL 39
<Directorio de las librerıas del fabricante>
Ruta al directorio donde se encuentran los archivos a incluir del fabricante.
-L
Opcion que indica el directorio donde se encuentran las librerıas del fabricante.
<Directorio de las librerıas del fabricante>
Ruta al directorio donde se encuentran las librerıas del fabricante.
-o
Opcion que indica que el ejecutable estara en un archivo llamado <ejecutable>.
<ejecutable>
Archivo Ejecutable.
A continuacion se muestran las lineas de compilacion utilizadas para este proyecto:
Compilacion para Linux-AMD
mpicc maestro.c -I$(AMDAPPSDKROOT)/include -L$(AMDAPPSDKROOT)/lib/x86_64/sdk -lOpenCL
-lm -lgsl -lgslcblas -o maestro
Compilacion para Linux-Nvidia
mpicc maestro.c -I/usr/local/cuda-7.5/include/ -L/usr/local/cuda-7.5/lib64 -lOpenCL
-lm -lgsl -lgslcblas -o maestro
3.6. COMPILACION Y EJECUCION DE PROGRAMAS OPENMPI-OPENCL 40
Una vez terminada la compilacion del codigo con sus respectivas librerıas, obtendremos el eje-
cutable, que correremos en el cluster aplicando, los pasos descritos en la seccion 3.5.
A continuacion se muestra la linea utilizada para este proyecto:
mpirun -np 3 -hostfile maquinas ./maestro
Capıtulo 4
Algoritmos Geneticos
4.1. Algoritmo Genetico
Los algoritmos geneticos (AG) fueron introducidos por Holland (1975) e imitan los principios
basicos de la naturaleza formulados por Darwin (1859) y Mendel (1866). Dichos principios
basicos segun [17] son:
1. Existe una poblacion de soluciones. Las propiedades de una solucion se evaluan segun el
fenotipo y los operadores de variacion se aplican al genotipo. Algunas de las soluciones se
eliminan de la poblacion si el tamano de la poblacion supera un lımite superior.
2. Los operadores de variacion crean nuevas soluciones con propiedades similares a las existentes.
El operador principal de busqueda es la recombinacion, y la mutacion sirve como operador
de fondo.
3. Los individuos de mejor calidad se seleccionan con mayor frecuencia para la reproduccion,
mediante un proceso de seleccion.
Holland fue probablemente el primero en utilizar el cruce y la recombinacion, la mutacion y la
seleccion en el estudio de sistemas adaptativos y artificiales. Estos operadores geneticos son la
parte esencial del algoritmo genetico como una estrategia de resolucion de problemas. Desde
41
4.2. OPERADORES GENETICOS 42
entonces, muchas variantes de algoritmos geneticos se han desarrollado y aplicado a una amplia
gama de problemas de optimizacion, desde coloracion grafica hasta reconocimiento de patrones,
desde sistemas discretos (como el problema del vendedor ambulante) hasta sistemas continuos
(por ejemplo, el diseno eficiente de perfil aerodinamico en ingenierıa aeroespacial), y de los
mercados financieros a la optimizacion de ingenierıa multiobjetivo. Hay muchas ventajas de los
algoritmos geneticos sobre los algoritmos de optimizacion tradicionales. Dos de los mas notables
son, la capacidad de tratar problemas complejos y el paralelismo. Los algoritmos geneticos
pueden tratar varios tipos de optimizacion, ya sea que la funcion objetivo sea estacionaria
o no estacionaria (cambia con el tiempo), lineal o no lineal, continua o discontinua, o con
ruido aleatorio. Debido a que multiples descendientes en una poblacion actuan como agentes
independientes, la poblacion (o cualquier subgrupo) puede explorar el espacio de busqueda en
muchas direcciones simultaneamente. Esta caracterıstica lo hace ideal para paralelizar[18].
4.2. Operadores Geneticos
Al observar directamente las operaciones de un algoritmo, es muy util ver como trabajan. To-
memos los algoritmos geneticos como ejemplo. Los algoritmos geneticos (GA) son una clase
de algoritmos basados en la abstraccion de la evolucion darwiniana de la biologıa de sistemas,
iniciados por J. Holland y sus colaboradores en las decadas de 1960 y 1970. Los algoritmos gene-
ticos usan operadores geneticos tales como cruce y recombinacion, mutacion y seleccion. Se ha
demostrado que los algoritmos geneticos tienen muchas ventajas sobre algoritmos tradicionales.
Tres ventajas son: sin gradiente, altamente exploratorio, y paralelismo. No se necesita informa-
cion de gradiente/derivada en GA, y por lo tanto GA puede lidiar con problemas complejos y
discontinuos. La naturaleza estocastica del cruce y la mutacion hace que los GAs exploren el
espacio de busqueda de manera mas efectiva y que el optimo global es mas probable que sea
alcanzado. Ademas, los algoritmos geneticos estan basados en poblacion con cromosomas multi-
ples y, por lo tanto, es posible implementarlos de forma paralela. Los tres operadores evolutivos
clave en algoritmos geneticos, segun [18], se pueden resumir de la siguiente forma:
Seleccion. La supervivencia de los mas aptos, lo que significa que los cromosomas y las
4.2. OPERADORES GENETICOS 43
caracterısticas de la mas alta calidad permaneceran dentro de la poblacion. Esto a menudo
toma alguna forma de elitismo, y la forma mas simple es dejar que los mejores genes pasen
a las siguientes generaciones en la poblacion.
El proceso de seleccion realizado en los enfoques de busqueda basados en poblaciones
es equivalente a la busqueda local de individuos, ya que distingue las soluciones de alta
calidad de las de baja calidad y selecciona soluciones prometedoras. Los esquemas de
seleccion populares son la seleccion proporcional [19] y la seleccion por torneos [20]. Para
una seleccion proporcional, el numero esperado de copias que tiene una solucion en la
siguiente poblacion es proporcional a su aptitud. La probabilidad de que una solucion xi
se seleccione para la cruza se calcula como:
f (xi)∑Nj=1 f (x
j)(4.1)
Con la aptitud creciente, un individuo se elige mas a menudo para la cruza. Al utilizar
la seleccion por torneo, se celebra un torneo entre individuos diferentes elegidos al azar
y el que tiene la mejor aptitud se agrega al grupo de cruza M . Despues de N torneos
de tamano s, se completa el grupo de emparejamiento. Se tiene que distinguir entre la
seleccion por torneo con y sin reemplazo.
La seleccion por torneo con reemplazo elige por cada torneo s individuos de la poblacion
P . Entonces, M se llena despues de N torneos.
El torneo sin reemplazo realiza s rondas. En cada ronda tenemos N/s torneos y elegimos
las soluciones para un torneo de aquellos individuos que aun no han participado en el
torneo de la ronda actual. Despues de que todas las soluciones hayan realizado un torneo
en una ronda (despues de N/s torneos), la ronda ha terminado y todos los x ∈ P se
consideran nuevamente para la siguiente ronda. Por lo tanto, para llenar completamente
el grupo de cruza, son necesarias s rondas.
El grupo de acoplamiento M consiste en todas las soluciones que se eligen para la cruza.
Al usar la seleccion por torneo no hay copias de la peor solucion, ni tampoco un prome-
dio de copias de s (con reemplazo), o exactamente s copias (sin reemplazo) de la mejor
4.2. OPERADORES GENETICOS 44
solucion[17].
Cruza. La recombinacion de dos cromosomas padre (soluciones) intercambiando parte de
un cromosoma con una parte correspondiente de otro para producir descendencia (nuevas
soluciones).
En [21] se definen 3 tipos de cruza:
• Cruza en un punto
Se elije aleatoriamente ` ∈ 1, ..., n− 1, y se asigna:
p′ = (p1, ..., p`, q`+1, ..., qn)
q′ = (q1, ..., q`, p`+1, ..., pn)
• Cruza en dos puntos
Se elije aleatoriamente `, r ∈ 1, ..., n− 1, ` < r, y se asigna:
p′ = (p1, ..., p`, q`+1, ..., qr, pr+1, ..., pn)
q′ = (q1, ..., q`, p`+1, ..., pr, qr+1, ..., qn)
• Cruza uniforme
Se genera aleatoriamente un mapa de bits b = (b1, ..., bn) y se asigna:
p′ = (b ∧ p) ∨ (¬b ∧ q)
q′ = (b ∧ q) ∨ (¬b ∧ p)
Mutacion. Es el cambio de una parte de un cromosoma (un bit o varios bits) para generar
nuevas caracterısticas geneticas. En la codificacion binaria, la mutacion se puede lograr
simplemente intercambiando entre 0 y 1. La mutacion puede ocurrir en un solo sitio o
multiples sitios simultaneamente.
Este operador, que cambia ligeramente el genotipo de una solucion x ∈ P ′, es importante
para la busqueda local. Si algunos alelos se pierden durante una ejecucion GA, la mutacion
puede reanimar las propiedades de la solucion que se han perdido previamente.
La probabilidad de mutacion pm debe seleccionarse en un nivel bajo porque de lo con-
trario la mutacion cambiarıa aleatoriamente demasiados alelos y las nuevas soluciones no
4.3. ALGORITMOS GENETICOS EN PARALELO 45
tendrıan nada en comun con sus padres. La descendencia se generarıa casi al azar y la
busqueda genetica se degradarıa a la busqueda aleatoria[17].
4.3. Algoritmos Geneticos en Paralelo
Las regiones que tienen un mejor desempeno que sus vecinas pero que en el espacio global no son
las optimas se les conoce como regiones sub-optimas. Cuando un algoritmo genetico es ejecutado
en una sola computadora cabe la posibilidad de que caiga en regiones sub-optimas y, por ende,
que converjan prematuramente.
Los algoritmos geneticos en paralelo, al buscar de manera simultanea en las distintas regiones
del espacio de busqueda, tienen menor probabilidad de quedar atrapados en una region sub-
optima y converger de manera prematura. Por otro lado, al procesarse de manera simultanea, el
calculo de la aptitud y la aplicacion de los operadores geneticos en varios individuos de distintas
poblaciones, disminuye considerablemente el tiempo de ejecucion del algoritmo [2].
Nowostawski y Poli [22] propusieron una clasificacion de algoritmos paralelos considerando los
siguientes aspectos:
La forma de evaluar la aptitud y aplicar la mutacion.
Si se implementa en una sola poblacion o varias sub-poblaciones.
En el caso de que se implemente en varias sub-poblaciones, la forma en la que se inter-
cambian individuos entre ellas.
Si se hace seleccion global o local.
Las categorıas propuestas son:
Maestro - Esclavo.
• Sıncrono.
4.3. ALGORITMOS GENETICOS EN PARALELO 46
• Asıncrono.
Sub-poblaciones estaticas con migracion.
Sub-poblaciones estaticas sobrepuestas.
Algoritmos geneticos masivamente paralelos.
Sub-poblaciones dinamicas.
Algoritmos geneticos de estado estable.
Algoritmos geneticos desordenados.
Metodos hıbridos.
Cada categorıa puede presentar mejores rendimientos, dependiendo del tipo de problema a tratar
y de la arquitectura de la maquina paralela donde se implemente la solucion.
En la Figura 4.1 se muestra el algoritmo genetico general.
4.3. ALGORITMOS GENETICOS EN PARALELO 47
Figura 4.1: Algoritmo Genetico.
Imagen tomada de [2]
4.4. IMPLEMENTACION DE ALGORITMOS GENETICOS 48
4.4. Implementacion de Algoritmos Geneticos
En la implementacion del Algoritmo Genetico se utilizo el lenguaje de programacion C, la
herramienta OpenCL versiones 1.1 y 1.2 y las librerias de OpenMPI 2.0.2 para el paso de
mensajes entre los nodos del cluster, sobre el sistema operativo Linux Mint 17.3 Cinnamon
64-bit.
El cluster cuenta con dos nodos, descritos en las tablas 3.1 y 3.2.
Se utilizo la siguiente estrategia para la paralelizacion del problema:
Se definio una poblacion global, la cual esta distribuida en los nodos del cluster.
En cada nodo se definieron 14 subpoblaciones de 64 individuos. Cada subpoblacion es
procesada por un bloque, como se ilustra en la Figura 4.2.
Por cada individuo de cada bloque se ejecuta un hilo que actualiza sus parametros, como
se ilustra en la Figura 4.2.
Figura 4.2: Distribucion de la carga de procesamiento.
Imagen tomada de [2]
Cada nodo tiene 896 individuos, por lo tanto, nuestra poblacion global tiene 1792 indivi-
4.4. IMPLEMENTACION DE ALGORITMOS GENETICOS 49
duos que son procesados en paralelo.
Se definio un periodo migratorio entre bloques del 10 % del total de las generaciones,
un periodo migratorio entre nodos del 25 % del total de las generaciones y una tasa de
migracion de aproximadamente 3 %. Como resultado, la migracion entre poblaciones tuvo
una frecuencia de 10 veces y la migracion entre nodos tuvo una frecuencia de 4 veces del
total de las generaciones.
La migracion entre bloques se realiza de forma circular. Primero se ordena la poblacion
para recibir la informacion de los MR mejores individuos del bloque siguiente en la posicion
de los MR peores del bloque actual, como se ilustra en las figuras 4.3 y 4.4.
Figura 4.3: Migracion de los MR mejores individuos del bloque siguiente.
Imagen tomada de [2]
Figura 4.4: Sustitucion de los MR peores individuos del bloque actual con los MR mejores del
bloque siguiente.
Imagen tomada de [2]
La migracion entre nodos tambien es de manera circular. El nodo actual recibe la informa-
cion del mejor individuo del nodo siguiente. Para esto, es necesario copiar desde el device
4.4. IMPLEMENTACION DE ALGORITMOS GENETICOS 50
hacia el host al individuo que se va a migrar. Una vez en el host, el individuo se envıa
por mensaje hacia el nodo siguiente y realiza la operacion inversa. Cuando se recibe el
individuo se copia desde el host hacia el device y se coloca en el lugar correspondiente,
para seguir iterando.
Se define un nodo maestro, el cual tiene la funcion de enviar la instruccion a los nodos
esclavos para que inicien la ejecucion del algoritmo con sus individuos (incluso el mismo).
Al final, el nodo maestro recibe los mejores individuos de cada esclavo y determina al
mejor de la poblacion global.
Capıtulo 5
Resultados
5.1. RESULTADOS
Las funciones de prueba que se utilizaron en la implementacion del AG son las propuestas por
Suganthan et al [23] para la “Competencia de Computacion Evolutiva 2005”.
De la baterıa de 5 funciones unimodales y 7 funciones basicas propuestas, se seleccionaron las si-
guientes:
Rastrigin:
f(x) =D∑i=1
(x2i − 10 cos(2πxi) + 10) (5.1)
D: dimensiones
Propiedades:
• Multimodal, Desplazable, Separable, Escalable.
• Gran cantidad de optimos locales.
• X ∈ [−5, 5]D
51
5.1. RESULTADOS 52
Figura 5.1: Funcion de Rastrigın.
Imagen tomada de [23]
Ackley:
f(x) = −20 exp(−0.2
√√√√ 1
D
D∑i=1
x2i )
− exp(1
D
D∑i=1
cos(2πxi)) + 20 + e
(5.2)
D: dimensiones
Propiedades:
• Multimodal, Rotable, Desplazable, No-Separable, Escalable.
• Optimo global en frontera.
• Si se inicializa la poblacion cerca de la frontera, el problema se resuelve facilmente.
• X ∈ [−32, 32]D
5.1. RESULTADOS 53
Figura 5.2: Funcion de Ackley.
Imagen tomada de [23]
En la optimizacion de las funciones propuestas, para espacios de busqueda de 30 dimensiones,
se utilizaron 14 bloques con 64 individuos, un periodo migratorio del 10 % del total de genera-
ciones, probabilidad de cruce de 0.16, probabilidad de mutacion de 0.7, tamano del torneo de 2
y se obtuvieron los siguientes resultados para 100 muestras:
Generaciones Emax Aciertos Tiempo(s)
5000 1.0e−6 100 23.806168
Tabla 5.1: Resultados funcion Rastrigin
Generaciones Emax Aciertos Tiempo(s)
5000 1.0e−6 100 23.804678
Tabla 5.2: Resultados funcion Ackley
Capıtulo 6
Conclusiones
Es posible implementar aplicaciones de computo paralelo por medio de un cluster heterogeneo
integrado por tarjetas de graficos de diferentes fabricantes, utilizando OpenCL como lenguaje
de programacion y OpenMPI como herramienta de comunicacion entre los nodos del cluster. El
punto clave reside en que el software propietario se enlaza en tiempo de ejecucion, por medio de
las librerıas proporcionadas por el fabricante de cada tarjeta de graficos (GPU). Estos resultados
reviven la idea que dio origen a los clusters de computadoras de bajo costo: utilizar equipos en
desuso, pero ahora incluyendo en el proceso las tarjetas de graficos que estos equipos contienen.
54
Apendice A
Codigo de funciones de aptitud
A.1. Archivo “ackley.h”
//Funcion 8, Pag 11 CEC05
#define PI 3.1415926535897932384626433832795029#define E 2.7182818284590452353602874713526625#define XMAX 32.0#define BIAS 0
float aptitud(float x[DIMENSIONES]){
int i;float r;float sum1, sum2, res, D;
sum1 = 0.0;sum2 = 0.0;
D = (float)(DIMENSIONES * 1.0);
for (i=0; i<DIMENSIONES; i++){
sum1 += (x[i]+BIAS) * (x[i]+BIAS);sum2 += cos(2.0 * PI * (x[i]+BIAS));
}
sum1 = -0.2 * sqrt(sum1/D);sum2 /= D;res = 20.0 + E - 20.0 * exp(sum1) - exp(sum2);
return fabs(res);}
55
A.2. ARCHIVO “RASTRIGIN.H” 56
A.2. Archivo “rastrigin.h”
//Funcion 9, Pag 12 CEC05
#define PI 3.1415926535897932384626433832795029#define E 2.7182818284590452353602874713526625#define XMAX 5.0#define BIAS 0
float aptitud(float x[DIMENSIONES]){
int i;float res;res = 0.0;
for (i=0; i<DIMENSIONES; i++){
res += ((x[i]+BIAS) * (x[i]+BIAS) - 10.0 * cos(2.0 * PI * (x[i]+BIAS)) +10.0);
}
return fabs(res);}
Apendice B
Codigo del algoritmo genetico
B.1. Archivo “parametros.h”
#define DIMENSIONES 30 // Numero de cromosomas que tiene cada individuo#define TMUESTRA 10 //Numero de veces a ejecutar el AG#define MU 64 //Numero de individuos por poblacion#define POBS 14 // Numero de poblaciones#define GMAX 5000 //Generaciones#define PC 0.7 //Probabilidad de cruce#define PM 0.16 //Probabilidad de mutacion#define MPB 50 //Periodo migratorio entre bloques#define MPN 100 //Periodo migratorio entre nodos#define MR 2 //Tasa de migracion#define Z 2 //Tama~no de torneo#define EMAX 0.01 //Error maximo permitido#define BLK POBS // Numero de bloques igual al numero de
poblaciones#define THR MU // Numero de hilos igual al numero de individuos
por poblacion#define TOTAL POBS*MU //Numero total de hilos igual al numero de
bloques (numero de poblacion) por el numero de hilos (numero de individuospor poblacion)
//#define FUNCION ackley //Funcion que se optimizara#define FUNCION rastrigin //Funcion que se optimizara
#define STRINGIZE_NX(A) #A
#define STRINGIZE(A) STRINGIZE_NX(A)
/* Estructura individuo */typedef struct{
float x[DIMENSIONES]; //Cromosoma.
57
B.2. ARCHIVO “MAESTRO.C” 58
float ap; //Aptitud.} individuo;
B.2. Archivo “maestro.c”
#include <stdlib.h>#include <stdio.h>#include <string.h>#include <unistd.h>#include <mpi.h>
#include "parametros.h"#include "ag/agHost.h"
/* Funcion main en el cual se distribuye el trabajo entre nodo maestro y nodosesclavos y
controla el numero de veces en el que se ejecutara el AG */int main(int argc, char *argv[]){
/* Inicializacion de variables que se usaran a nivel global en main */int size, rank, muestra;OpenCL*frame;
/* Inicializacion de framework de OpenCL */initOpenCL(&frame,PROGRAM_FILE);
/* Inicializacion de MPI */MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);
/* Condicion principal con la cual separa lo que hara el maestro y lo queharan los esclavos,la variable rank contiene el Id del hilo */if (rank == 0) // Se toma el hilo con Id 0 como el maestro{
/* Inicializacion de variables para el hilo maestro */MPI_Status rec_stat;int prematuros,ceros;int i,j,destinoE;float error,eprom;FILE *informe;FILE *tiempos;char salida[256];double tini,tfin;float ttotal,tpromedio;
float*rema = malloc(sizeof(float) * DIMENSIONES+1);float*dimej = malloc(sizeof(float) * DIMENSIONES+1);
prematuros=0;ceros=0;
B.2. ARCHIVO “MAESTRO.C” 59
error=0.0;eprom=0.0;tpromedio=0.0;
/* Se escriben comentarios por pantalla del inicio del experimento */printf("\n \n Empezando prueba en cluster... %d Muestras\n\n",TMUESTRA);printf("\n---Cluster trabajando... NO APAGAR EQUIPOS ---\n\n");
/* Se crea el archivo de salida con informacion de los datos */sprintf(salida,"AGgpu %d- %s- %02d- %03d- %03d- %06d- %06d.txt",size-1,
STRINGIZE(FUNCION),BLK,THR,DIMENSIONES,GMAX,TMUESTRA);informe = fopen(salida,"w");
/* Se crea el archivo de salida con informacion de los tiempos */sprintf(salida,"t-AGgpu %d- %s- %02d- %03d- %03d- %06d- %06d.txt",size-1,
STRINGIZE(FUNCION),BLK,THR,DIMENSIONES,GMAX,TMUESTRA);tiempos = fopen(salida,"w");
/* Ciclo que se ejecuta el mismo numero de veces como muestras seespecificaron
y extrae optimo de todo el experimento */for (muestra=0;muestra<TMUESTRA;muestra++){
tini=tfin=0;tini = MPI_Wtime();
/* Ciclo que se encarga de recolectar el mejor resultado de cadaesclavo y de mantener en dimej el mejor resultado obtenido de los esclavosy de cada muestra,
en la primera iteracion se obtiene el mejor resultado de cada esclavoy se guarda en dimej el mejor de los resultados,
en las siguientes iteraciones se obtiene el mejor resultado de cadaesclavo y se guarda en dimej solo si este resultado es mejor que el queconserva dimej*/
for (i=1;i<size;i++){
/* Recibe el resultado de un esclavo y lo guarda en rema */MPI_Recv(rema,DIMENSIONES+1,MPI_FLOAT,i,4,MPI_COMM_WORLD,&rec_stat
);
/* Guarda en dimej el resultado del esclavo si es la primeraiteracion o si la aptitud de rema es mejor que la aptitud que conservadimej */
if (i==1||rema[DIMENSIONES] < dimej[DIMENSIONES]){
/* Copia la aptitud del esclavo el cual esta en la ultimaposicion */
dimej[DIMENSIONES]=rema[DIMENSIONES];
/* Copian los cromosomas del individuo */for(j=0;j<DIMENSIONES;j++){
dimej[j]=rema[j];}
B.2. ARCHIVO “MAESTRO.C” 60
}}
/* Condicional que cuenta a aquellos individuos obtenidos en cadamuestra con una aptitud mayor al EMAX (error maximo permitido)
caso contrario suma la aptitud a un error total */if(dimej[DIMENSIONES]>EMAX){
prematuros++;}else{
error += dimej[DIMENSIONES];}
tfin= MPI_Wtime();ttotal=tfin-tini;// Contabilizacion del tiempo que tardo en ser
procesada la muestratpromedio+=ttotal;// Se va sumando el tiempo que tarda cada muestra
en ser procesada
/* Contabilizacion si el mejor individuo obtenido en cada muestratuvo una aptitud de 0 */
if(dimej[DIMENSIONES]<0.000001){ceros++;}
/* Escritura de datos por cada muestra en el archivo de tiempo y dedatos */
fprintf(tiempos,"Tiempo en muestra %d: %.06f\t\n", muestra, ttotal);fprintf(informe,"Mejor aptitud en muestra %d: %.07f\n", muestra,
dimej[DIMENSIONES]);
/* Contabilizacion del error promedio */eprom=error/(TMUESTRA-prematuros);
}
tpromedio/=TMUESTRA;// se saca un promedio de lo que tarda una muestraen ser procesada
/* Escritura en los archivos de salida de tiempo y de datos de informacion a nivel general del experimento */
fprintf(tiempos,"Tpromedio: %.06f\t\n",tpromedio);fprintf(informe,"CEROS...: %d \n", ceros);fprintf(informe,"PREMATUROS ...: %d \n", prematuros);fprintf(informe,"ERROR PROMEDIO ...: %f \n", eprom);
/* Cierre de los archivos de salida tanto de tiempos como de datos */fclose(informe);fclose(tiempos);
/* Escritura por pantalla de la finalizacion del experimento */printf("\n\n---PRUEBA FINALIZADA...\n");printf("\n\n CEROS: %d\t TIEMPO PROMEDIO: %.06f\t\n",ceros,tpromedio);
B.2. ARCHIVO “MAESTRO.C” 61
/* Se libera memoria dinamica */free(rema);free(dimej);
}else // Cualquier hilo que no tenga el Id 0 se considera como esclavo{
/* Inicializacion de variables para los hilos esclavos */int vecino;float*resultado = malloc(sizeof(float) * DIMENSIONES+1);FILE *aptitud;aptitud=fopen("aptitud.txt", "w");
/* Creacion de los kernels que se utilizaran a lo largo del AG */addKernel(&frame, "genera_poblacion");addKernel(&frame, "ordena");addKernel(&frame, "ag_esc");addKernel(&frame, "migra");addKernel(&frame, "mejorPorBloque");addKernel(&frame, "dispersaIndividuo");
vecino = rank+1;
if(vecino >= size){
vecino = 1;}
/* Ciclo que ejecuta el AG tantas veces como muestras se especificaron*/
for (muestra=0;muestra<TMUESTRA;muestra++){
printf("Muestra: %d \n", muestra+1);h_ag(frame, vecino, resultado, aptitud);// Metodo que contiene toda
la logica del AGMPI_Send(resultado,DIMENSIONES+1,MPI_FLOAT,0,4,MPI_COMM_WORLD);// Env
ıa el mejor resultado obtenido en este esclavo y lo envıa al maestro}
/* Cierre de archivo aptitud.txt */fclose(aptitud);
/* Se libera memoria dinamica */free(resultado);freeOpenCl(frame);
}
/* Finalizacion del MPI */MPI_Finalize();
return 0;}
B.3. ARCHIVO “AGHOST.H” 62
B.3. Archivo “agHost.h”
#define PROGRAM_FILE "ag/agKernel.h"
#include <stdio.h>#include <stdlib.h>#include <sys/time.h>#include <sys/types.h>#include <math.h>#include <string.h>
#include "../opencl/OpenCLFramework.h"#include "../rng/mt19937Host.h"
/* Funcion que se encarga de compartir el individuo con la mejor aptitud a losdemas nodos */
void envianodo(int neighbor, float *indi){
MPI_Send(indi, DIMENSIONES+1, MPI_FLOAT, neighbor, 1, MPI_COMM_WORLD);}
/* Funcion que recibe al individuo con mejor aptitud de cualquier nodo,lo compara con el individuo con la mejor aptitud obtenido localmente yconserva al que tenga la mejor aptitud */void recibenodo(OpenCL**frame, float *indiOr, FILE *aptitud){
/* Declaracion de variables */int d;individuo itemp;float indiRec [DIMENSIONES+1];
/* Se recibe al mejor individuo que se obtuvo en ALGUN nodo */MPI_Recv(indiRec, DIMENSIONES+1, MPI_FLOAT, MPI_ANY_SOURCE, 1,MPI_COMM_WORLD, MPI_STATUS_IGNORE);
/* Escritura en el archivo de salida de aptitudes la aptitud del mejorindividuo local seguido dela aptitud del mejor individuo de otro nodo*/fprintf(aptitud, "AP: %.06f\tAPRec: %.06f\n", indiOr[DIMENSIONES], indiRec[DIMENSIONES]);
/* Validacion para saber si el individuo recibido tiene mejor aptitud queel obtenido localmente,en caso de ser verdadero el individuo se manda a la GPU para ser el mejoren cada bloque */if (indiRec[DIMENSIONES] < indiOr[DIMENSIONES]){
itemp.ap = indiRec[DIMENSIONES]; // Se copia de la aptitud
for (d = 0 ; d < DIMENSIONES ; d++) // Se copia los cromosomas a elindividuo;
{itemp.x[d] = indiRec[d];
}
B.3. ARCHIVO “AGHOST.H” 63
/* Escritura del mejor individuo a la GPU */addBuffer(&(*frame), "individuo", &itemp, sizeof(individuo));
/* Ejecucion del kernel dispersaIndividuo el cual coloca al individuoobtenido en la ultima
pocicion del primer bloque */runKernel((*frame), "dispersaIndividuo", 1, 1, 1, "individuo");
}}
/* Funcion que recibe al mejor individuo de cada bloque y determina cual es elmejor
individuo de todos para mandarlo como resultado */void mejorEntreBloques(individuo *mejores, float *resultado){
/* Declaracion e inicializacion de variables */int r, imr, d;float mejorAptitud = 0.0;
/* Recorrido para analizar al mejor de cada bloque */for (r=0; r<BLK; r++){
/* Condicion para obtener la mejor aptitud, se guarda al individuo si esel primero o si la aptitudes mas proxima a 0 del que se tiene almacenado */if(r==0 || mejores[r].ap < mejorAptitud){
mejorAptitud = mejores[r].ap; //actualiza mejor resultado globalimr = r; //renglon del mejor resultado global
}}
/* Copia de los cromosomas del mejor individuo entre los bloques */for(d=0;d<DIMENSIONES;d++){
resultado[d] = mejores[imr].x[d];}
/* Se copia la mejor aptitud en el ultimo elemento del arreglo resultado */resultado[DIMENSIONES] = mejorAptitud;
}
/* Funcion del AG */int h_ag(OpenCL*frame, int neighbor, float*resultado, FILE*aptitud){
/* Declaracion e inicializacion de variables */gsl_rng*semillas = (gsl_rng*)malloc(sizeof(gsl_rng)*TOTAL);individuo pob[TOTAL];individuo mejores[BLK];int i,j,k;
/* se crean los generadores de numeros aleatorios uno por cada hilo */
B.3. ARCHIVO “AGHOST.H” 64
for(i=0;i<TOTAL;i++){
semillas[i] = getGslRng();}
/* Creacion de los buffers que se utilizaran a lo largo del AG */addBuffer(&frame, "poblacion", pob, sizeof(individuo)*TOTAL);addBuffer(&frame, "mejores", mejores, sizeof(individuo)*BLK);addBuffer(&frame, "semillas", semillas, sizeof(gsl_rng)*TOTAL);
/* Ejecucion del kernel que se encarga de generar la poblacion inicial */runKernel(frame, "genera_poblacion", BLK, THR, 2, "poblacion", "semillas");
/* Ejecucion del kernel que se encarga de ordenar la poblacion */runKernel(frame, "ordena", BLK, THR, 2, "poblacion", "mejores");
int migracionNodos = GMAX/MPN; //para ciclo externo migracion entre nodosint migracionBloques = MPN/MPB; // ciclo interno para migracion entrebloques
/* Ciclo de migracion entre nodos */for(i=0; i<migracionNodos; i++){
/* Ciclo de migracion entre bloques */for(j=0; j<migracionBloques; j++){
/* Ejecucion del kernel que contiene la logica del algoritmo genetico*/
runKernel(frame, "ag_esc", BLK, THR, 3, "poblacion", "mejores", "semillas");
/* Ejecucion del kernel que se encarga de ordenar la poblacion */runKernel(frame, "ordena", BLK, THR, 2, "poblacion", "mejores");
/* Ejecucion del kernel que se encarga de migrar los mejoresindividuos del siguiente bloque
en los peores individuos del bloque actual */runKernel(frame, "migra", BLK, THR, 1, "poblacion");
}
/* Ejecucion del kernel que encuentra al mejor de cada bloque porreduccion binaria */
runKernel(frame, "mejorPorBloque", BLK, THR, 2, "poblacion", "mejores");
/* Lectura del buffer mejores en la GPU y lo guarda en la variablemejores en el host */
readBuffer(frame, "mejores", mejores);
/* Se busca al individuo con mejor aptitud de entre el mejor individuode cada bloque */
mejorEntreBloques(mejores,resultado);
/* Ciclo para recorrer los cromosomas del individuo con mejor aptitud */
B.4. ARCHIVO “AGKERNEL.H” 65
for(k=0; k<DIMENSIONES; k++){
fprintf(aptitud,"cromosoma: %.06f\n",resultado[k]);}
/* Comparte el mejor resultado obtenido con los demas nodos */envianodo(neighbor,resultado);
/* Recibe el mejor resultado obtenido en otro nodo y valida si es mejorque el mejor resultado local */
recibenodo(&frame, resultado,aptitud);}
/* Ejecucion del kernel que encuentra al mejor de cada bloque por reduccionbinaria */
runKernel(frame, "mejorPorBloque", BLK, THR, 2, "poblacion", "mejores");
/* Lectura del buffer mejores en la GPU y lo guarda en la variable mejoresen el host */readBuffer(frame, "mejores", mejores);
/* Se busca al individuo con mejor aptitud de entre el mejor individuo decada bloque */mejorEntreBloques(mejores,resultado);
/* Se libera memoria */free(semillas);
return 0;}
B.4. Archivo “agKernel.h”
#include "./rng/mt19937Kernel.h"#include "./parametros.h"
#include STRINGIZE(funciones/FUNCION.h)
/* Funcion que genera la poblacion inicial */__kernel void genera_poblacion(__global individuo *pob, __global gsl_rng *rng){
/* Declaracion de variables */int d, tid;float x;float poblacion[DIMENSIONES];
tid = get_global_id(0); //Se almacena en tid el ındice del hilo en la GPU
/* Ciclo que asigna un numero aleatorio a cada cromosoma */for(d = 0 ; d < DIMENSIONES ; d++){
B.4. ARCHIVO “AGKERNEL.H” 66
poblacion[d] = NNI(&rng[tid], XMAX); // Se almacena el numero aleatorioen un arreglo auxiliar
pob[tid].x[d] = poblacion[d]; // Se almacena el numero aleatorio en elindividuo correspondiente}
/* Calculo de la aptitud */pob[tid].ap = aptitud(poblacion);
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_GLOBAL_MEM_FENCE);
}
/* Funcion que ordena de mejor a peor aptitud (de menor mayor) a losindividuos de cada bloque */
__kernel void ordena(__global individuo *pob, __global individuo *d_mejores){
/* Declaracion de variables */__local individuo l_pob[MU];individuo aux;int d, i, im, j, it, ib, tid;
ib = get_group_id(0); //Se almacena en ib el ındice del bloque actualit = get_local_id(0); //Se almacena en it el ındice del hilo en el bloquetid = get_global_id(0); //Se almacena en tid el ındice del hilo en la GPU
/* Copia el individuo de la memoria global a la memoria local */
/* Copia la aptitud */l_pob[it].ap = pob[tid].ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
l_pob[it].x[d] = pob[tid].x[d];}
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
/* Condicional para hacer trabajar al primer hilo de cada bloque */if(it == 0){
/* Ciclo para recorrer y ordenar todos los individuos de la poblacionlocal (individuos del bloque) */
for(i = 0 ; i < MU-1 ; i++){
/* Se almacena en im el valor de i */im = i;
/* Ciclo para buscar al mejor individuo del bloque desde el ındice i
B.4. ARCHIVO “AGKERNEL.H” 67
hasta el ultimo del bloque */for(j = i+1 ; j < MU ; j++){
/* Condicional que decide si im contiene el ındice del mejorindividuo,
de no ser ası reemplaza por el valor de im por de j */if(l_pob[im].ap > l_pob[j].ap){
im = j;}
}
/* Se pone en la posicion i al mejor para que ya no participe en la busqueda del mejor */
/* Intercambio de la aptitud */aux.ap = l_pob[im].ap;l_pob[im].ap = l_pob[i].ap;l_pob[i].ap = aux.ap;
/* Intercambio de los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
aux.x[d] = l_pob[im].x[d];l_pob[im].x[d] = l_pob[i].x[d];l_pob[i].x[d] = aux.x[d];
}}
/* Actualizacion del mejor de cada bloque */
/* Copia la aptitud */d_mejores[ib].ap = l_pob[0].ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
d_mejores[ib].x[d] = l_pob[0].x[d];}
}
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
/* Copia el individuo de la memoria local a la memoria global */
/* Copia la aptitud */pob[tid].ap = l_pob[it].ap;
/* Copia los cromosomas */for(d=0; d<DIMENSIONES; d++){
B.4. ARCHIVO “AGKERNEL.H” 68
pob[tid].x[d] = l_pob[it].x[d];}
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
}
/* Funcion que contiene la logica del AG */__kernel void ag_esc(__global individuo*pob, __global individuo*d_mejores,
__global gsl_rng *rng){
/* Declaracion de variables */__local individuo l_pob[MU], psig[MU];individuo aux, mejor_ap;int i, j, k, g, l, d, im, ip, gen, CZ, it, ib, tid;float r,xnew1, xnew2, lambda, xold1, xold2;float cromosomasA[DIMENSIONES], cromosomasB[DIMENSIONES];
ib = get_group_id(0); //Se almacena en ib el ındice del bloque actualit = get_local_id(0); //Se almacena en it el ındice del hilo en el bloquetid = get_global_id(0); //Se almacena en tid el ındice del hilo en la GPU
/* Copia el individuo de la memoria global a la memoria local */l_pob[it].ap = pob[tid].ap;
/* Copia los cromosomas */for(d = 0; d < DIMENSIONES; d++){
l_pob[it].x[d] = pob[tid].x[d];}
/* Condicional para hacer trabajar al primer hilo de cada bloque */if(it == 0){
/* Copia en mejor_ap al mejor individuo del bloque */
/* Copia la aptitud */mejor_ap.ap = d_mejores[ib].ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++) // y sus dimensiones{
mejor_ap.x[d] = d_mejores[ib].x[d];}
}
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
/* Ciclo que se ejecuta MPB veces */for(g = 0 ; g < MPB ; g++)
B.4. ARCHIVO “AGKERNEL.H” 69
{///////////////////////////////////////////////////////////////////////////// S e l e c c i o n////////////////////////////////////////////////////////////////////
/* Se selecciona el ındice del primer individuo de forma aleatoria */k = UNI(&rng[tid]) * MU;
/* Seleccion por torneo */for(j = 1 ; j < Z ; j++){
/* Se selecciona el ındice del siguiente individuo de forma aleatoria*/
l = UNI(&rng[tid]) * MU;
/* Condicional que decide que individuo tiene mejor aptitud */if(l_pob[k].ap > l_pob[l].ap){
k = l;}
}
/* Copia en psig al mejor individuo de la seleccion por torneo */
/* Copia la aptitud */psig[it].ap = l_pob[k].ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
psig[it].x[d] = l_pob[k].x[d];}
/* Se espera a que todos los hilos ejecuten este metodo antes decontinuar */
barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
///////////////////////////////////////////////////////////////////////////// C r u c e////////////////////////////////////////////////////////////////////
/* Condicional para hacer trabajar al primer hilo de cada bloque */if(it == 0){
/* Calculo aleatorio de la lambda */lambda = UNI(&rng[tid]);
/* Se calcula el numero de cruces a efectuar */CZ = (MU * PC) / 2;
/* Ciclo de cruces que se efectuara CZ veces */for(i = 0 ; i < CZ ; i++){
B.4. ARCHIVO “AGKERNEL.H” 70
/* Se seleccionan ındices de forma aleatoria */j = UNI(&rng[tid]) * MU;k = UNI(&rng[tid]) * MU;
/* Ciclo que se efectuara tantas veces como cromosomas tenga unindividuo,
y cruzara los genes de los individuos antes seleccionados */for(d = 0 ; d < DIMENSIONES ; d++){
xold1 = psig[j].x[d]; //Se obtiene el cromosoma del individuo Axold2 = psig[k].x[d]; //Se obtiene el cromosoma del individuo B
psig[j].x[d] = (xold1 * lambda) + (xold2 * (1-lambda)); //Secalcula el nuevo cromosoma del individuo A
psig[k].x[d] = (xold1 * (1-lambda)) + (xold2 * lambda); //Secalcula el nuevo cromosoma del individuo B
cromosomasA[d] = psig[j].x[d]; //Se almacenan los nuevoscromosomas del individuo A en variable auxiliar
cromosomasB[d] = psig[k].x[d]; //Se almacenan los nuevoscromosomas del individuo B en variable auxiliar
}
psig[j].ap = aptitud(cromosomasA); //Calculo de la aptitud delindividuo A
psig[k].ap = aptitud(cromosomasB); //Calculo de la aptitud delindividuo B
}}
/* Se espera a que todos los hilos ejecuten este metodo antes decontinuar */
barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
///////////////////////////////////////////////////////////////////////////// M u t a c i o n////////////////////////////////////////////////////////////////////
/* Calculo de numero aleatorio */r = UNI(&rng[tid]);
/* Condicional que decide si se aplica el operador en base a PM */if(r <= PM){
/* Calculo de numero aleatorio para decidir cual cromosoma mutar x[0], x[1],...,x[gen] */
gen = UNI(&rng[tid])*DIMENSIONES;
/* Mutacion del cromosoma */psig[it].x[gen] = NNI(&rng[tid], XMAX);
/* Copia los cromosomas en variable auxiliar */for(d = 0 ; d < DIMENSIONES ; d++){
B.4. ARCHIVO “AGKERNEL.H” 71
cromosomasA[d] = psig[it].x[d];}
/* Calculo de la nueva aptitud despues de haber mutado el cromosoma*/
psig[it].ap = aptitud(cromosomasA);}
/* Se espera a que todos los hilos ejecuten este metodo antes decontinuar */
barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
/* Copia al individuo de la poblacion psig en el correspondiente lugardel individuo en la
poblacion l_pob (poblacion actual) */
/* Copia la aptitud */l_pob[it].ap = psig[it].ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
l_pob[it].x[d] = psig[it].x[d];}
/* Se espera a que todos los hilos ejecuten este metodo antes decontinuar */
barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
///////////////////////////////////////////////////////////////////////////// encuentra el mejor y el peor////////////////////////////////////////////////////////////////////
/* Condicional para hacer trabajar al primer hilo de cada bloque */if(it == 0){
/* se inicializan a 0 el peor y el mejor */im=ip=0;
/* Ciclo para recorrer a todos los individuos en un bloque */for(i = 1 ; i < MU ; i++){
/* Condicional que decide si l_pob[i] tiene una mejor aptitud quel_pob[im] */
if(l_pob[im].ap > l_pob[i].ap){
im = i;}
/* Condicional que decide si l_pob[i] tiene una peor aptitud quel_pob[ip] */
if(l_pob[ip].ap < l_pob[i].ap){
B.4. ARCHIVO “AGKERNEL.H” 72
ip = i;}
}
/* Condicional que decide si el mejor de la generacion anterior, esmejor que el de la
generacion actual, lo incluye en la POS DEL PEOR */if(l_pob[im].ap > mejor_ap.ap){
/* Copia la aptitud */l_pob[ip].ap = mejor_ap.ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
l_pob[ip].x[d] = mejor_ap.x[d];}
}else{
/* Copia la aptitud */mejor_ap.ap = l_pob[im].ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
mejor_ap.x[d]=l_pob[im].x[d];}
}
/* Actualizacion del mejor del bloque */
/* Copia la aptitud */d_mejores[ib].ap = mejor_ap.ap;
/* Copia los cromosomas */for(d = 0 ; d < DIMENSIONES ; d++){
d_mejores[ib].x[d] = mejor_ap.x[d];}
}
/* Se espera a que todos los hilos ejecuten este metodo antes decontinuar */
barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);}
/* Actualizacion de la variable global que tiene la poblacion actual */
/* Copia los cromosomas */pob[tid].ap = l_pob[it].ap;
/* Copia los cromosomas */
B.4. ARCHIVO “AGKERNEL.H” 73
for(d = 0 ; d < DIMENSIONES ; d++){
pob[tid].x[d] = l_pob[it].x[d];}
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
}
/* Funcion que copia a los mejores individuos del bloque siguiente en lospeores del bloque actual */
__kernel void migra(__global individuo*pob){
/* Declaracion de variables globales del metodo */int it;
it = get_local_id(0); //Se almacena en it el ındice del hilo en el bloque
/* Condicion para saber si el hilo es menor a la tasa de migracion, en casode ser ası
copia a los mejores individuos del bloque siguiente en los peores delbloque actual */if(it<MR){
/* Declaration de variables */int d, bloqueActual, bloqueSiguiente, individuoBloqueActual,
individuoBloqueSiguiente;
bloqueActual = get_group_id(0); // Se almacena en bloqueActual el ındicedel bloque actualbloqueSiguiente = (bloqueActual+1) % (BLK); // Se almacena en
bloqueSiguiente el siguiente bloque
individuoBloqueActual = (bloqueActual * MU) + ((MU - 1) - it);individuoBloqueSiguiente = (bloqueSiguiente * MU) + it;
/* Copia la aptitud de los mejores del bloque siguiente en los peoresdel bloque actual */
pob[individuoBloqueActual].ap = pob[individuoBloqueSiguiente].ap;
/* Copia los cromosomas de los mejores del bloque siguiente en lospeores del bloque actual */
for(d = 0 ; d < DIMENSIONES ; d++){
pob[individuoBloqueActual].x[d] = pob[individuoBloqueSiguiente].x[d];}
}
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_GLOBAL_MEM_FENCE);
}
B.4. ARCHIVO “AGKERNEL.H” 74
/* Funcion que encuentra al mejor de cada bloque por reduccion binaria */__kernel void mejorPorBloque(__global individuo*pob, __global individuo*
d_mejores){
/* Declaracion de variables */int mitad,tid,it,ib,d;__local individuo l_pob[MU];__local int idmejor[MU];
ib = get_group_id(0); //Se almacena en ib el ındice del bloque actualit = get_local_id(0); //Se almacena en it el ındice del hilo en el bloquetid = get_global_id(0); //Se almacena en tid el ındice del hilo en la GPU
/* Copia el individuo de la memoria global a la memoria local */
l_pob[it].ap = pob[tid].ap; //Copia la aptitud del individuo
/* Copia los cromosomas del individuo*/for (d = 0 ; d < DIMENSIONES ; d++){
l_pob[it].x[d] = pob[tid].x[d];}
/* Se almacena en idmejor el ındice de cada hilo */idmejor[it] = it;
/* Se espera a que todos los hilos ejecuten este metodo antes de continuar*/barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
/* Se almacena en mitad el lımite de hilos que van a trabajar */mitad = MU / 2;
/* Ciclo de la busqueda por reduccion binaria */do{
/* Condicional para hacer trabajar a la mitad de los hilos */if(it < mitad){
/* Condicional que decide que individuo tiene mejor aptitud */if(l_pob[idmejor[it]].ap > l_pob[idmejor[it+mitad]].ap){
/* Se almacena en idmejor[it] el ındice del individuo con mejoraptitud */
idmejor[it] = idmejor[it+mitad];}
}
/* Se espera a que todos los hilos ejecuten este metodo antes decontinuar */
barrier(CLK_LOCAL_MEM_FENCE|CLK_GLOBAL_MEM_FENCE);
/* La variable mitad se va rediciendo la mitad cada ciclo */
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 75
mitad /= 2;
}while(mitad > 0);
/* Condicional para hacer trabajar al primer hilo de cada bloque y copiarend_mejores al mejor individuo de cada bloque */if (it == 0){
/* Copia la aptitud del mejor del bloque */d_mejores[ib].ap = l_pob[idmejor[0]].ap;
/* Copia los cromosomas del mejor del bloque */for(d = 0 ; d < DIMENSIONES ; d++){
d_mejores[ib].x[d] = l_pob[idmejor[0]].x[d];}
}}
/* Funcion que copia el individuo recibido en la ultima posicion del primerbloque */
__kernel void dispersaIndividuo(__global individuo*pob, __global individuo*itempD)
{/* Declaracion de variables */int l;
/* Copia aptitud */pob[MU-1].ap = itempD[0].ap;
/* Copia cromosomas */for (l=0; l<DIMENSIONES;l++){
pob[MU-1].x[l]=itempD[0].x[l];}
}
B.5. Archivo “OpenCLFramework.h”
#include <stdarg.h>#ifdef MAC#include <OpenCL/cl.h>#else#include <CL/cl.h>#endif
/* Estructura para el framework de OpenCL */typedef struct{
cl_platform_id platform;// ID de la plataforma
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 76
cl_device_id device;// ID del device que se utilizara para programar pordefault solo tomamos el primer device del tipo GPUcl_context context;// Contextocl_command_queue queue;// Cola de comandoscl_program program;// Programa fuentecl_kernel kernels[100];// Arreglo donde se almacenan los kernelschar*kernelNames[100];// Arreglo donde se guardan los nombres de loskey_compint kernelCount;// Contador de cuantos kernels se han agregadocl_mem buffers[100];// Buffers que se utilizaran en los kernelschar*bufferNames[100];// Nombre de los buffersint bufferSizes[100];// Tama~no en bytes de los buffersint bufferCount;// Contador de cuantos buffers fueron agregados
} OpenCL;
/* Funcion encargada de leer un archivo y cargarlo en memoria, esta funcion esutilizada para leer
el archivo que contiene los kernels */void loadProgramFromFile(char*programFileName, char**program_buffer_pointer,
size_t*program_size_pointer){
/* Apertura del archivo */FILE*programFile = fopen(programFileName, "r");
/* Se valida si el archivo pudo ser abierto */if(programFile == NULL){
perror("Couldn’t find the program file");exit(1);
}
/* Desplazamiento del cursor al final del archivo */fseek(programFile, 0, SEEK_END);
/* Conteo de los caracteres desde el inicio del archivo hasta la posiciondel cursor (final del archivo en este caso) */size_t program_size = ftell(programFile);
/* Regreso del cursor a la posicion inicial */rewind(programFile);
/* Se reserva la memoria suficiente para alojar el contenido del archivo */char*program_buffer = (char*)malloc(program_size + 1);
/* Final de cadena */program_buffer[program_size] = ’\0’;
/* Lectura del contenido del archivo y almacenamiento en program_buffer */fread(program_buffer, sizeof(char), program_size, programFile);
/* Cierre del archivo */fclose(programFile);
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 77
/* Se almacena en el apuntador del espacio de memoria donde se almaceno elcontenido del archivo */*program_buffer_pointer = program_buffer;
/* Se almacena el apuntador al espacio de memoria donde se guardo el tama~noen caracteres del archivo */
*program_size_pointer = program_size;}
/* Funcion que recibe el apuntador a una estructura del tipo OpenCL y seencarga de inicializar los elementos
independientes a los kernels y buffers */void initOpenCL(OpenCL**framePointer, char*programFileName){
/* Declaracion de variables */int err;
/* Asignacion de memoria */OpenCL*frame = (OpenCL*)malloc(sizeof(OpenCL));
/* Busca una sola plataforma y es agregada a platform en frame */err = clGetPlatformIDs(1, &frame->platform, NULL);
/* Se valida si se encontro una plataforma */if(err < 0){
perror("Couldn’t find any platforms");exit(1);
}
/* Busca y guarda en device de frame el primer device de tipo GPU */err = clGetDeviceIDs(frame->platform, CL_DEVICE_TYPE_GPU, 1, &frame->device, NULL);
/* Se valida si se encontro un device */if(err < 0){
perror("Couldn’t find any devices");exit(1);
}
/* Creacion del contexto */frame->context = clCreateContext(NULL, 1, &frame->device, NULL, NULL, &err);
/* Se valida si se creo correctamente el contexto */if(err < 0){
perror("Couldn’t create a context");exit(1);
}
/* Declaracion de variables */char*program_buffer;
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 78
size_t program_size;
/* Invocacion de funcion encarga de leer el archivo con los kernels y pasarel texto al
apuntador program_buffer */loadProgramFromFile(programFileName, &program_buffer, &program_size);
/* Creacion del programa */frame->program = clCreateProgramWithSource(frame->context, 1, (const char**)&program_buffer, &program_size, &err);
/* Se valida si se creo correctamente el programa */if(err < 0){
perror("Couldn’t create the program");exit(1);
}
/* Liberacion del codigo fuente cargado en memoria */free(program_buffer);
/* Compilacion del programa */err = clBuildProgram(frame->program, 0, NULL, NULL, NULL, NULL);
/* Se valida si la compilacion fue exitosa en caso de haber fallado seextrae el log y se muestra por pantalla */if(err < 0){
/* Declaracion de variables */size_t log_size;
/* Extraccion del tama~no del log */clGetProgramBuildInfo(frame->program, frame->device,
CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
/* Se reserva la memoria suficiente para alojar el log */char*program_log = (char*) malloc(log_size + 1);
/* Final de cadena */program_log[log_size] = ’\0’;
/* Extraccion del log y almacenamiento del mismo en program_log */clGetProgramBuildInfo(frame->program, frame->device,
CL_PROGRAM_BUILD_LOG, log_size + 1, program_log, NULL);
/* Salida por pantalla del log */printf(" %s\n", program_log);
/* Liberacion de memoria ocupada por el log */free(program_log);
exit(1);}
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 79
/* Creacion de la cola de comandos */frame->queue = clCreateCommandQueueWithProperties(frame->context, frame->device, 0, &err);
/* Se valida si se creo correctamente la cola de comandos */if(err < 0){
perror("Couldn’t create the command queue");exit(1);
}
/* Se guarda el apuntador del objeto que fue creado y configurado a lolargo de la funcion y se guardaen el apuntador que se recibio por parametro */*framePointer = frame;
}
/* Funcion que crea un buffer y lo agrega a la lista de buffers del parametroframe en caso de no existir o lo
vuelve a crear si ya existe dejandolo en la misma posicion de la lista */void addBuffer(OpenCL**frame, char*bufferName, void*objeto, int length){
/* Declaracion e inicializacion de variables */int i, err, find;find = 0;// Inicializacion de find a 0 indicativo de que el nombre enbufferName no ha sido encontrado en la lista de buffers
/* Ciclo que recorre los buffers y el cual se usa para buscar el nombre delbuffer especificado en bufferName
en los buffers que han sido agregados a frame */for(i = 0; i < (*frame)->bufferCount; i++){
/* Se verifica si el nombre del buffer esta en la posicion i es igual alnombre del buffer especificado en bufferName */if(strcmp(bufferName, (*frame)->bufferNames[i]) == 0){
/* Se libera la memoria ocupada por el buffer ya existente y que serareemplazado */
clReleaseMemObject((*frame)->buffers[i]);
/* Se crea el buffer del objeto */(*frame)->buffers[i] = clCreateBuffer((*frame)->context,
CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, length, objeto, &err);
/* Se agrega la longitud en bytes del objeto a la lista de longitudesde los buffers */
(*frame)->bufferSizes[i] = length;
/* Se valida si la creacion del buffer fue exitosa */if(err < 0){
perror("Couldn’t create a buffer object");exit(1);
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 80
}
/* Asignacion de 1 a find indicativo de que el nombre en bufferNameha sido encontrado en la lista de buffers */
find = 1;
break;}
}
/* Se valida si se encontro el nombre del buffer ya que de no haber sidoencontrado significa quedebe ser creado por primera vez */if(!find){
/* Creacion del buffer con opciones de lectura y escritura ademas decopiarse en la GPU (device), una vez creado
es agregado a la lista de buffers de frame */(*frame)->buffers[i] = clCreateBuffer((*frame)->context,
CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, length, objeto, &err);
/* Se agrega el nombre del buffer a la lista de nombres con el mismo ındice en el cual se encuentra el buffer
recien creado en la lista de buffers, esto con la idea de buscar bufferspor nombre y no por ındice */(*frame)->bufferNames[i] = bufferName;
/* Se agrega la longitud en bytes del objeto a la lista de longitudes delos buffers con el mismo ındice en el cualse encuentra el buffer recien creado en la lista de buffers*/(*frame)->bufferSizes[i] = length;
/* Aumentamos bufferCount en uno para tener el ındice siguiente al cualpoder agregar un buffer */
(*frame)->bufferCount++;
/* Se valida si la creacion del buffer fue exitosa */if(err < 0){
perror("Couldn’t create a buffer object");exit(1);
}}
}
/* Funcion que lee un buffer en la GPU (devide) y lo escribe en una variableen la CPU (host) */
void readBuffer(OpenCL*frame, char*bufferName, void*objeto){
/* Declaracion e inicializacion de variables */int i, err, find;find = 0;// Inicializacion de find a 0 indicativo de que el nombre enbufferName no ha sido encontrado en la lista de buffers
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 81
/* Ciclo que recorre los buffers y el cual se usa para buscar el nombre delbuffer especificado en bufferName
en los buffers que han sido agregados a frame */for(i = 0; i < frame->bufferCount; i++){
/* Se verifica si el nombre del buffer esta en la posicion i es igual alnombre del buffer especificado en bufferName */if(strcmp(bufferName, frame->bufferNames[i]) == 0){
/* Lectura del buffer y escritura en el parametro objeto, se guardaen err el resultado de la accion */
err = clEnqueueReadBuffer(frame->queue, frame->buffers[i], CL_TRUE,0, frame->bufferSizes[i], objeto, 0, NULL, NULL);
/* Se valida si la lectura del buffer fue exitosa */if(err < 0){
perror("Couldn’t enqueue the read buffer command");exit(1);
}
/* Asignacion de 1 a find indicativo de que el nombre en bufferNameha sido encontrado en la lista de buffers */
find = 1;
/* Salida del ciclo pues no tiene caso seguir recorriendo los bufferssi ya se encontro */
break;}
}
/* Se valida si se encontro el nombre del buffer */if(!find){
perror("Couldn’t find the buffer name");exit(1);
}}
/* Funcion que agrega un kernel a frame, se crea el kernel especificado porkernelName y es agregado
a la lista de kernels de frame */void addKernel(OpenCL**frame, char*kernelName){
/* Declaracion de variables */int err;
/* Se crea el kernel de la funcion especificada por el parametro kernelNameque se debe de encontrar en el programa
previamente cargado al inicializar frame, el nuevo kernel creado esagregado a la lista de kernels */(*frame)->kernels[(*frame)->kernelCount] = clCreateKernel((*frame)->program, kernelName, &err);
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 82
/* Se agrega el nombre del kernel a la lista de nombres con el mismo ındiceen el cual se encuentra el kernel
recien creado en la lista de kernels, esto con la idea de buscar kernelspor nombre y no por ındice */(*frame)->kernelNames[(*frame)->kernelCount] = kernelName;
/* Se valida si la creacion del kernel fue exitosa */if(err < 0){
perror("Couldn’t create the kernel");exit(1);
}
/* Aumentamos kernelCount en uno para tener el ındice siguiente al cualpoder agregar kernel y kernelName */(*frame)->kernelCount++;
}
/* Funcion que corre un kernel, el kernel debe haber sido agregado previamenteen frame. Se ejecuta el kernel especificado por
la variable kernelName el cual contiene el nombre del kernel a ejecutar y el numero de bloques e hilos por bloque deben ser
especificados ademas deben pasarse el numero de buffers que se usaran y losrespectivos buffers que ocupa el kernel */
void runKernel(OpenCL*frame, char*kernelName, size_t blocks, size_t threads,int count,...)
{/* Declaracion e inicializacion de variables */int i, j, k, err, findKernelName, findBufferName;findKernelName = 0;
size_t totalThreads = blocks*threads;// Numero total de hilos a correr
/* Ciclo que recorre los kernels que han sido agregados a frame en buscadel kernel que se quiere correr */for(i = 0; i < frame->kernelCount; i++){
/* Se verifica si el nombre del kernel en la posicion i es igual alnombre del kernel que se busca ejecutar */
if(strcmp(kernelName, frame->kernelNames[i]) == 0){
/* Se guarda 1 en la variable auxiliar find para avisar que si seencontro el nombre del kernel */
findKernelName = 1;
va_list ap;
char*bufferName;
/* Se guarda en ap los argumentos pasados por parametro simbolizadospor (...) en los parametros de esta funcion */
va_start(ap, count);
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 83
/* Ciclo que recorre los ultimos parametros de esta funcion loscuales deben ser los nombres de los buffers que ocupa el kernel */
for(j = 0; j < count; j++){
/* Se extrae un nombre de buffer */bufferName = va_arg(ap, char*);
/* Variable auxiliar se iguala a 0 que significa que el nombre delbuffer no se a encontrado en los nombres de buffers
que han sido agregados a frame */findBufferName = 0;
/* Ciclo que recorre los buffers agregados a frame en busqueda delnombre del buffer que se acaba de extraer de la lista de parametros */
for(k = 0; k < frame->bufferCount; k++){
/* Se verifica si el nombre del buffer en la posicion k esigual al nombre del buffer que se acaba de extraer */
if(strcmp(bufferName, frame->bufferNames[k]) == 0){
/* Se guarda 1 en la variable auxiliar find para avisar quesi se encontro el nombre del buffer */
findBufferName = 1;
/* Se agrega el buffer a los argumentos del kernel y seguarda en err el resultado de la accion */
err = clSetKernelArg(frame->kernels[i], j, sizeof(cl_mem), &frame->buffers[k]);
/* Se valida que no hubo error al agregar el buffer comoargumento */
if(err < 0){
perror("Couldn’t set the kernel argument");exit(1);
}
break;}
}
/* Se valida si se encontro el nombre del buffer */if(!findBufferName){
perror("Couldn’t find the buffer name");exit(1);
}}
/* Finalizacion del recorrido de la lista de argumentos */va_end(ap);
/* Se pone en la cola de comandos la ejecucion del kernel y se guarda
B.5. ARCHIVO “OPENCLFRAMEWORK.H” 84
en err el resultado de la accion */err = clEnqueueNDRangeKernel(frame->queue, frame->kernels[i], 1, NULL
, &totalThreads, &threads, 0, NULL, NULL);
/* Se espera a que el kernel se haya ejecutado */clFinish(frame->queue);
/* Se valida que no hubo error al agregar el kernel a la cola decomandos */
if(err < 0){
perror("Couldn’t enqueue the kernel execution command");exit(1);
}
break;}
}
/* Se valida si se encontro el nombre del kernel */if(!findKernelName){
perror("Couldn’t find the kernel name");exit(1);
}}
/* Funcion que libera la memoria de los elementos que conforman la estructuraOpenCL */
void freeOpenCl(OpenCL*frame){
/* Declaracion de variables */int i;
/* Ciclo que recorre los buffers agregados y libera la memoria ocupada porcada uno */for(i = 0 ; i< frame->bufferCount ; i++ ){
/* Se libera memoria */clReleaseMemObject(frame->buffers[i]);
}
/* Ciclo que recorre los kernels agregados y libera la memoria ocupada porcada uno */for(i = 0 ; i< frame->kernelCount ; i++ ){
/* Se libera memoria */clReleaseKernel(frame->kernels[i]);
}
/* Se libera la memoria ocupada por el queue de comandos */clReleaseCommandQueue(frame->queue);
B.6. ARCHIVO “MT19937.H” 85
/* Se libera la memoria ocupada por el programa almacenado en la variableprogram del tipo cl_program */clReleaseProgram(frame->program);
/* Se libera la memoria ocupada por el contexto */clReleaseContext(frame->context);
}
B.6. Archivo “mt19937.h”
/* Regresa un numero positivo uniformemente distribuido entre 0 y 1 excluyendolos */
#define UNI(A) gsl_rng_uniform_pos(A) // (0.0,1.0)
/* Regresa un numero distribuido entre -1 y 1 excluyendolos */#define VNI(A) (1.0-2.0*UNI(A)) // (-1.0,1.0)
/* Regresa un numero distribuido entre -n y n excluyendolos */#define NNI(A,B) (VNI(A)*B) // (-n,n)
/* Regresa un numero distribuido entre 1 y n excluyendolos */#define OTN(A,B) (UNI(A)*B) // (0,n)
#define N 624 /* Period parameters */#define M 397
typedef struct{
unsigned long mt[N];int mti;
}mt_state_t;
typedef struct{
char name[20];unsigned long int max;unsigned long int min;size_t size;
}gsl_rng_type;
typedef struct{
gsl_rng_type type;mt_state_t state;
}gsl_rng;
B.7. ARCHIVO “MT19937HOST.H” 86
B.7. Archivo “mt19937Host.h”
/* This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public License aspublished by the Free Software Foundation; either version 3 of theLicense, or (at your option) any later version.
This program is distributed in the hope that it will be useful, butWITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNUGeneral Public License for more details. You should have receiveda copy of the GNU General Public License along with this program;if not, write to the Free Foundation, Inc., 59 Temple Place, Suite330, Boston, MA 02111-1307 USA
Original implementation was copyright (C) 1997 Makoto Matsumoto andTakuji Nishimura. Coded by Takuji Nishimura, considering thesuggestions by Topher Cooper and Marc Rieffel in July-Aug. 1997, "AC-program for MT19937: Integer version (1998/4/6)"
This implementation copyright (C) 1998 Brian Gough. I reorganizedthe code to use the module framework of GSL. The license on thisimplementation was changed from LGPL to GPL, following paragraph 3of the LGPL, version 2.
Update:
The seeding procedure has been updated to match the 10/99 releaseof MT19937.
Update:
The seeding procedure has been updated again to match the 2002release of MT19937
The original code included the comment: "When you use this, send anemail to: [email protected] with an appropriate reference toyour work".
Makoto Matsumoto has a web page with more information about thegenerator, http://www.math.keio.ac.jp/~matumoto/emt.html.
The paper below has details of the algorithm.
From: Makoto Matsumoto and Takuji Nishimura, "Mersenne Twister: A623-dimensionally equidistributerd uniform pseudorandom numbergenerator". ACM Transactions on Modeling and Computer Simulation,Vol. 8, No. 1 (Jan. 1998), Pages 3-30
You can obtain the paper directly from Makoto Matsumoto’s web page.
The period of this generator is 2^{19937} - 1.
*/
#include "mt19937.h"
B.7. ARCHIVO “MT19937HOST.H” 87
///////////////////////////////////////////////////////////////////////////// Host Functions////////////////////////////////////////////////////////////////////void mt_set (gsl_rng *object, unsigned long int s){
mt_state_t *state = &object->state;int i;
if (s == 0){
s = 4357; /* the default seed is 4357 */}
state->mt[0]= s & 0xffffffffUL;
for (i = 1; i < N; i++){
/* See Knuth’s "Art of Computer Programming" Vol. 2, 3rdEd. p.106 for multiplier. */
state->mt[i] =(1812433253UL * (state->mt[i-1] ^ (state->mt[i-1] >> 30)) + i);
state->mt[i] &= 0xffffffffUL;}
state->mti = i;}
void mt_1999_set (void *vstate, unsigned long int s){
mt_state_t *state = (mt_state_t *) vstate;int i;
if (s == 0)s = 4357; /* the default seed is 4357 */
/* This is the October 1999 version of the seeding procedure. Itwas updated by the original developers to avoid the periodicityin the simple congruence originally used.
Note that an ANSI-C unsigned long integer arithmetic isautomatically modulo 2^32 (or a higher power of two), so we cansafely ignore overflow. */
#define LCG(x) ((69069 * x) + 1) &0xffffffffUL
for (i = 0; i < N; i++){
state->mt[i] = s & 0xffff0000UL;s = LCG(s);state->mt[i] |= (s &0xffff0000UL) >> 16;s = LCG(s);
B.7. ARCHIVO “MT19937HOST.H” 88
}
state->mti = i;}
/* This is the original version of the seeding procedure, no longerused but available for compatibility with the original MT19937. */
void mt_1998_set (void *vstate, unsigned long int s){
mt_state_t *state = (mt_state_t *) vstate;int i;
if (s == 0)s = 4357; /* the default seed is 4357 */
state->mt[0] = s & 0xffffffffUL;
#define LCG1998(n) ((69069 * n) & 0xffffffffUL)
for (i = 1; i < N; i++)state->mt[i] = LCG1998 (state->mt[i - 1]);
state->mti = i;}
gsl_rng_type mt_type ={"mt19937", /* name */
0xffffffffUL, /* RAND_MAX */0, /* RAND_MIN */sizeof (mt_state_t)};
gsl_rng_type mt_1999_type ={"mt19937_1999", /* name */
0xffffffffUL, /* RAND_MAX */0, /* RAND_MIN */sizeof (mt_state_t)};
gsl_rng_type mt_1998_type ={"mt19937_1998", /* name */
0xffffffffUL, /* RAND_MAX */0, /* RAND_MIN */sizeof (mt_state_t)};
/* MT19937 is the default generator, so define that here too */
unsigned long int gsl_rng_default_seed = 0;
gsl_rng * gsl_rng_alloc (gsl_rng_type T){
gsl_rng *r = (gsl_rng *) malloc (sizeof (gsl_rng));
B.8. ARCHIVO “MT19937KERNEL.H” 89
if (r == 0){
perror ("failed to allocate space for rng struct");};
r->type = T;
struct timeval tv;struct timezone tz;gettimeofday(&tv,&tz);
mt_set(r, tv.tv_usec); /* seed the generator */
return r;}
gsl_rng getGslRng(){
struct timeval tv;struct timezone tz;gettimeofday(&tv,&tz);gsl_rng r;r = *gsl_rng_alloc(mt_type);r.type = mt_type;return r;
}
B.8. Archivo “mt19937Kernel.h”
/* This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public License aspublished by the Free Software Foundation; either version 3 of theLicense, or (at your option) any later version.
This program is distributed in the hope that it will be useful, butWITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNUGeneral Public License for more details. You should have receiveda copy of the GNU General Public License along with this program;if not, write to the Free Foundation, Inc., 59 Temple Place, Suite330, Boston, MA 02111-1307 USA
Original implementation was copyright (C) 1997 Makoto Matsumoto andTakuji Nishimura. Coded by Takuji Nishimura, considering thesuggestions by Topher Cooper and Marc Rieffel in July-Aug. 1997, "AC-program for MT19937: Integer version (1998/4/6)"
This implementation copyright (C) 1998 Brian Gough. I reorganizedthe code to use the module framework of GSL. The license on thisimplementation was changed from LGPL to GPL, following paragraph 3of the LGPL, version 2.
Update:
B.8. ARCHIVO “MT19937KERNEL.H” 90
The seeding procedure has been updated to match the 10/99 releaseof MT19937.
Update:
The seeding procedure has been updated again to match the 2002release of MT19937
The original code included the comment: "When you use this, send anemail to: [email protected] with an appropriate reference toyour work".
Makoto Matsumoto has a web page with more information about thegenerator, http://www.math.keio.ac.jp/~matumoto/emt.html.
The paper below has details of the algorithm.
From: Makoto Matsumoto and Takuji Nishimura, "Mersenne Twister: A623-dimensionally equidistributerd uniform pseudorandom numbergenerator". ACM Transactions on Modeling and Computer Simulation,Vol. 8, No. 1 (Jan. 1998), Pages 3-30
You can obtain the paper directly from Makoto Matsumoto’s web page.
The period of this generator is 2^{19937} - 1.
*/
#include "mt19937.h"
////////////////////////////////////////////////////////////////////KernelFunctions
inline unsigned long mt_get (__global mt_state_t *vstate){
//mt_state_t *state = (mt_state_t *) vstate;
unsigned long k ;__global unsigned long * mt = vstate->mt;
#define MAGIC(y) (((y)&0x1) ? 0x9908b0dfUL : 0)
if (vstate->mti >= N){ /* generate N words at one time */
int kk;
for (kk = 0; kk < N - M; kk++){
unsigned long y = (mt[kk] & 0x80000000UL) | (mt[kk + 1] & 0x7fffffffUL);
mt[kk] = mt[kk + M] ^ (y >> 1) ^ MAGIC(y);}for (; kk < N - 1; kk++){
unsigned long y = (mt[kk] & 0x80000000UL) | (mt[kk + 1] & 0
B.8. ARCHIVO “MT19937KERNEL.H” 91
x7fffffffUL);mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ MAGIC(y);
}
{unsigned long y = (mt[N - 1] & 0x80000000UL) | (mt[0] & 0x7fffffffUL)
;mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ MAGIC(y);
}
vstate->mti = 0;}
/* Tempering */
k = mt[vstate->mti];k ^= (k >> 11);k ^= (k << 7) & 0x9d2c5680UL;k ^= (k << 15) & 0xefc60000UL;k ^= (k >> 18);
vstate->mti++;
return k;}
double mt_get_double (__global mt_state_t * vstate){
return mt_get (vstate) / 4294967296.0 ;}
inline double gsl_rng_uniform_pos (__global gsl_rng * r){
double x ;do{
x = mt_get_double(&r->state) ;}while (x == (double)0.0) ;
return x ;}
Bibliografıa
[1] A.G Fallis. CUDA by example, volume 53. 2013.
[2] Iliana Castro Liera. Paralelizacion de Algoritmos de Optimizacion Basados en Poblaciones
Usando GPGPU. PhD thesis, 2011.
[3] Jesus Ivan Mercado Bareno. Implementacion de un Cluster de GPGPU para Algoritmos
de Optimizacion. PhD thesis, 2015.
[4] Raymond Tay. OpenCL Parallel Programming Development Cookbook. 2013.
[5] B Gaster, L Howes, D R Kaeli, P Mistry, and D Schaa. Heterogeneous computing with
OpenCL, 2nd Edition. 2013.
[6] Amd. AMD APP SDK v2.4 Installation Notes, 2008.
[7] Weblet Importer, 2018.
[8] Matthew Scarpino. OpenCL in Action. Number January. Manning Publications Co., 2012.
[9] Group Khronos. OpenCL 1.2 Reference Pages, 2011.
[10] Ravishekhar Banger and Koushik Bhattacharyya. OpenCL Programming by Example - A
comprehensive guide on OpenCL programming with examples. 2013.
[11] Aaftab Munshi, Benedict Gaster, Timothy G. Mattson, James Fung, and Dan Ginsburg.
OpenCL Programming Guide. oct 2011.
[12] Opencl Api, Reference Card, and Command Queues. OpenCL API 1.2 Reference Card,
2011.
92
BIBLIOGRAFIA 93
[13] OpenMPI. Open Source High Performance Computing, 2014.
[14] Ewing Lusk, S Huss, B Saphir, and M Snir. MPI: A message-passing interface standard.
2009.
[15] Peter S. Pacheco. Introduction to Parallel Programming. 2011.
[16] Thomas Rauber and Gudula Runger. Parallel programming: For multicore and cluster
systems. 2010.
[17] Franz Rothlauf. Design of Modern Heuristics - Principles and Application. 2011.
[18] Xin-She Yang. Nature-Inspired Optimization Algorithms. 2014.
[19] John H. Holland. Adaptation in natural and artificial systems: An introductory analysis
with applications to biology, control, and artificial intelligence., volume 8. University of
Michigan Press, Ann Arbor, 1975.
[20] David E. Goldberg. Genetic Algorithms in Search, Optimization & Machine Learning.
Addison-Wesley Longman Publishing Co., Inc., 1989.
[21] Stefan Edelkamp, Stefan Schrodl, Sven Koenig, and Craig Tovey. Heuristic Search. 2012.
[22] M. Nowostawski and R. Poli. Parallel genetic algorithm taxonomy. 1999 Third International
Conference on Knowledge-Based Intelligent Information Engineering Systems. Proceedings
(Cat. No.99TH8410), (January):88–92, 2000.
[23] P. N. Suganthan, N. Hansen, J. J. Liang, K. Deb, Y. P. Chen, A. Auger, and S. Tiwari. Pro-
blem Definitions and Evaluation Criteria for the CEC 2006 Special Session on Constrained
Real-Parameter Optimization. KanGAL, (May):251–256, 2005.