Documentos de Académico
Documentos de Profesional
Documentos de Cultura
N.o 6 (2015)
Universidad de Sonora
Divisin de Ciencias Exactas y Naturales
Universidad de Sonora
Departamento de Matem
aticas
Indice general
1.
Introducci
on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1. Que es OpenCL/OCL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2. Contenido del tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3. Que se requiere para programar en OCL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4. Surgimiento de OCL y estado del arte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.5. Estructura del Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.
Conociendo OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1. Una vista general de OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2. C
omo funciona? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.3. C
odigo de ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.4. El procesamiento en paralelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.
13
13
13
15
16
16
16
17
17
18
18
4.
OpenCL en Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1. ANSI C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.2. Configurando Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.3. HelloOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2. Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
21
21
23
28
32
5
5
5
6
6
6
Indice general
33
34
39
42
42
44
47
5.
OpenCL en Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1. ANSI C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1.2. Configurando Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1.3. HelloOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2. Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.2. Configurando NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.3. HelloJOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
51
51
52
58
62
62
64
68
6.
73
73
74
75
77
77
79
79
82
83
84
Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Captulo 1
Introducci
on
1.1.
Qu
e es OpenCL/OCL?
OpenCL1 (Open Computing Language) es el primer estandar abierto, gratuito y multiplataforma para la programaci
on paralela de procesadores modernos usados en computadoras
personales, servidores, dispositivos portatiles y embebidos 2 [2]. Otro acronimo para OpenCL es
OCL, as que usaremos ambos para referirnos al mismo lenguaje. OCL mejora en gran medida la
velocidad y capacidad de respuesta para una amplia gama de aplicaciones en numerosos sectores
del mercado de juegos y entretenimiento as como software cientfico y medico.
1.2.
http://www.khronos.org/opencl/
http://en.wikipedia.org/wiki/Embedded_system
http://infocomp.ingenieria.uaslp.mx/opencl
1 Introducci
on
1.3.
Qu
e se requiere para programar en OCL?
Para programar en OCL, se necesita un dispositivo que lo soporte. Existe una lista oficial
proporcionada por Khronos Group donde se detallan los dispositivos que oficialmente soportan
el est
andar OCL 4 . Tambien es necesario poseer los controladores del dispositivo, esto depende
del fabricante y el sistema operativo donde se va a trabajar 5 6 7 .
1.4.
OCL surge en el 2008 cuando se forma el grupo de desarrollo y se lanza la primera version
en diciembre de 2008 [3]. Al ser un lenguaje relativamente nuevo, hay pocos libros relacionados
con OCL, cada uno con diferente enfoque. Por ejemplo, Tsuchiyama et al. hace enfasis en la
transformada de Fourier y OCL con el lenguaje C [4]. Por otro lado, Kowalik et al. cubre aspectos
de la programaci
on en paralelo con MPI y CUDA, y se enfoca mas a OCL con el lenguaje C++
[1]. Finalmente, Gaster et al. cubre u
nicamente OCL con C/C++.
Hay varios tutoriales relacionados en OCL (la mayora en ingles) que se pueden consultar en la
p
agina del grupo Khronos8 , sin embargo ninguno de ellos cubre al mismo tiempo todos lenguajes
de programaci
on que se presentan en este tutorial, y sobre los sistemas operativos Windows y
Linux.
1.5.
http://www.khronos.org/conformance/adopters/conformant-products/
http://support.amd.com/us/gpudownload/Pages/index.aspx
http://www.nvidia.com/Download/index.aspx?lang=en-us
http://downloadcenter.intel.com/
http://www.khronos.org/opencl/resources
Captulo 2
Conociendo OpenCL
2.1.
2 Conociendo OpenCL
En la Figura 2.1 hay un Anfitrion (la computadora) y hay Dispositivos (el chip de video,
el microprocesador o tarjetas aceleradoras), el anfitrion manda datos a los dispositivos, enva
comandos de ejecuci
on y recibe datos procesados de los dispositivos.
2.2.
C
omo funciona?
El Anfitri
on ejecuta el c
odigo que se ha escrito en C#, C++ o cualquier lenguaje de programaci
on soportado por el sistema. Este mismo programa se ejecuta en el procesador, siendo
gestionado por el sistema operativo y ejecutado localmente. Los dispositivos que se encargan de
ejecutar c
odigo OCL, basado en el lenguaje C99 [3]. Hay un compilador especfico OCL para la
CPU, la GPU y de las tarjetas aceleradoras (NVidia Tesla 1 , AMD FirePro 2 , Intel Xeon Phi 3
por mencionar algunas).
La pregunta es c
omo se ejecuta un programa OCL?, ah es donde la API OCL entra en juego.
La API de OCL tiene funciones para identificar los dispositivos, compilar programas, enviar y
recibir informaci
on y ejecutar estos programas en el dispositivo elegido.
B
asicamente as es como funciona:
C
odigo OCL:
1. Crear el c
odigo que desee ejecutar utilizando el lenguaje C99.
C
odigo Anfitri
on:
1.
2.
3.
4.
5.
6.
2.3.
C
odigo de ejemplo
El siguiente c
odigo nos servir
a para ilustrar los pasos anteriores. Por ahora no hay que preocuparse por la comprensi
on de la sintaxis, mas adelante se vera a detalle.
1
http://www.nvidia.com/object/tesla-supercomputing-solutions.html
http://www.amd.com/us/products/workstation/graphics/ati-firepro-3d/Pages/ati-firepro-3d.aspx
3 http://www.intel.com/content/www/us/en/high-performance-computing/high-performance-xeon-phi-coprocessor-brief.
html
2
2.3 C
odigo de ejemplo
//Biblioteca OpenCL
using OpenCLTemplate;
public static void Main (string[] args)
{
//Inicializaci
on los dispositivos CL disponibles
CLCalc.InitCL();
//Creaci
on de las variables que van a pasarse al dispositivo
//Estas variables son memoria local, es decir, memoria del anfitri
on
float[] x = new float[] { 1.0f, 2.0f, 3.0f, 4.0f };
float[] y = new float[] { 1.0f, 2.0f, 1.0f, 1.0f };
//Este es el codigo OpenCL, no sera compilado por el anfitri
on si no por el
//dispositivo OpenCL. Como se puede ver, es una sencilla suma de vectores,
//donde la suma de x[i]+y[i] se guarda en x[i]
string code = @"
__kernel void vector_add(__global float *x, __global float *y)
{
x[0] = x[0] + y[0];
}";
//Se llama a la API para compilar el programa.
//Este codigo se compila y guarda en el dispositivo OCL para su posterior
//ejecuci
on.
CLCalc.Program.Compile(new string[] { code });
//Craci
on del kernel, es quien se encargara de ejecutar el codigo OCL.
//Como se puede ver, recibe como par
ametro el nombre del kernel
CLCalc.Program.Kernel sum = new CLCalc.Program.Kernel("vector_add");
//Se crean las variables del dispositivo OCL y se copian
//los datos del host (variables locales "x" y "y") al dispositivo OCL
CLCalc.Program.Variable varx=new CLCalc.Program.Variable(x);
CLCalc.Program.Variable vary=new CLCalc.Program.Variable(y);
//Se crean los argumentos del kernel "vector_add" son varx y vary
CLCalc.Program.Variable[] args = { varx, vary };
//Este es el n
umero de "Workers" que ejecutar
an el programa
//(hablaremos de ellos mas adelante)
//Cada vector posee 4 elementos, por lo que se define un worker por dato,
int[] max = new int[] { 4 };
10
2 Conociendo OpenCL
//Se llama a la API OpenCL para ejecutar el kernel "vector_add" con los
//argumentos de la funci
on y el numero de "workers" que se definieron
sum.Execute(args, max);
//Al terminar la ejecuci
on, se requiere leer los resultados procesados
//por el dispositivo OCL, estos resultados estan en la memoria del
//dispositivo, por lo que hay que copiarlos de regreso al host.
varx.ReadFromDeviceTo(x);
}
2.4.
El procesamiento en paralelo
11
Captulo 3
Aqu se muestra como crear un programa OCL paso a paso: la configuracion del ambiente
donde se ejecutar
a el programa, el codigo OCL, el codigo anfitrion, el Kernel, la escritura en la
memoria de dispositivos OCL, la ejecucion del Kernel y la recuperacion de datos. El ejemplo es
un programa escrito en C# que demostrara los topicos vistos hasta el momento.
3.1.
API OCL
3.2.
Configuraci
on
Primero hay que crear una nueva aplicacion para consola en Visual C#, en este ejemplo se
usar
a la versi
on 2008.
13
14
A continuaci
on se debe incluir las referencias a las bibliotecas Cloo y OpenCLTemplate.
Para esto se utiliza el men
u Proyecto -> Agregar referencia para abrir las referencias:
http://www.cmsoft.com.br/download/OpenCLTemplate.zip
3.3 Creando el c
odigo OCL
15
3.3.
Creando el c
odigo OCL
Aqu se muestra un programa simple que suma dos vectores y almacena el resultado en un
tercer vector. Primero se incializa una variable de tipo String con el codigo OCL que se encargar
a de la tarea:
string sum = @"
__kernel void vector_add(__global float *v1, __global float *v2, __global float *v3)
{
int i = get_global_id(0);
v3[i] = v1[i] + v2[i];
}
";
Aqu se cubrir
an algunos de los conceptos basicos de OCL-C99.
1. Observe que el c
odigo fuente de OCL es una cadena que sera compilada por el dispositivo
OCL.
2. La etiqueta __kernel indica que esta funcion es un kernel, estas funciones son p
ublicas y
son las u
nicas que pueden invocarse a traves de la API OCL.
3. La etiqueta __global indica que la memoria de las variables v1 y v2 es p
ublica y el anfitrion
puede acceder a ellas.
16
4. La funci
on get_global_id(0) es una funcion OCL nativa que devuelve el identificador
del worker ejecutando esta funcion (desde 0 hasta el n
umero de workers establecidos -1). En
el c
odigo ejemplo, la tarea del worker i es sumar los componentes i de v1 y v2.
3.4.
Creando el c
odigo anfitri
on
3.4.1.
Al inicializar OCL se deben identificar todos los dispositivos disponibles y crear las Colas de
comandos que se utilizan para decirle a OCL en que dispositivo debe ejecutar el codigo. Todo esto
esta encapsulado en la funci
on de inicializacion InitCL. Despues de la compilacion, es necesario
el uso de la API de OCL para que el anfitrion tenga acceso al Kernel para que posteriormente,
pueda ordenar la ejecuci
on del c
odigo.
3.4.2.
El Kernel
Se puede pensar en el Kernel como una funcion que se ejecuta en el dispositivo. Pero existen
otras funciones que se pueden ejecutar en el (funciones nativas y funciones creadas por el usuario, se ver
a mas adelante), pero hay una ligera diferencia entre estas funciones y los Kernels: los
Kernels son puntos de entrada para el programa en el dispositivo. En otras palabras, los Kernels
son las u
nicas funciones que se pueden llamar desde la computadora anfitrion (host).
// El Anfitri
on obtiene acceso a la funci
on Kernel "vector_add"
3.4 Creando el c
odigo anfitri
on
17
3.4.3.
Creaci
on de los vectores
Hay que crear e inicializar los vectores que se van a sumar. Este codigo se realiza en la maquina
anfitri
on.
3.4.4.
de los vectores v1 y v2
= 0; i < n; i++)
(float) i / 10;
- (float) i / 9;
Primero hay que enviar los datos creados en la maquina anfitrion hacia la memoria del dispositivo OCL, para esto se crean las variables varV1, varV2 y varV3 que copiaran los datos de v1
y v2 hacia la memoria del dispositivo.
/* Crear los vectores v1, v2 y v3 en la memoria del dispositivo */
CLCalc.Program.Variable varV1 = new CLCalc.Program.Variable (v1);
CLCalc.Program.Variable varV2 = new CLCalc.Program.Variable (v2);
CLCalc.Program.Variable varV3 = new CLCalc.Program.Variable (v3);
Hay que recordar que las memorias de los dispositivo OCL y de la maquina anfitrion suelen ser
diferentes memorias. La variable varV3, aunque no escribe datos al dispositivo (ya que esta
variable es donde se guardar
an los resultados), a
un as debe reservar espacio en el dispositivo,
para esto la API OCL debe saber el tipo y tama
no del buffer a reservar.
18
3.4.5.
Ejecutando el Kernel
3.4.6.
Recuperaci
on de datos
El u
ltimo paso consiste en recuperar la informacion procesada de la memoria del dispositivo.
// Leer varV3 de la memoria del dispositivo a la memoria local del anfitri
on, "v3"
varV3.ReadFromDeviceTo(v3);
// Imprimir los datos recuperados
foreach (float f in v3)
{
Console.Write(f + ",");
}
3.4 Creando el c
odigo anfitri
on
19
Captulo 4
OpenCL en Linux
4.1.
ANSI C
4.1.1.
Descargas e instalaciones
@@ Buscando...
@@ Paquete: dev-util/codeblocks-10.05-r1 rama: 5, [sabayon-weekly]
http://www.codeblocks.org/
http://www.sabayon.org/
21
22
4 OpenCL en Linux
>>
>>
>>
>>
>>
>>
>>
>>
>>
Disponible:
Instalado:
Bloque:
P
agina:
Descripci
on:
versi
on: 10.05-r1 ~ tag: NoTag ~ revisi
on: 0
versi
on: 10.05-r1 ~ tag: NoTag ~ revisi
on: 0
0
http://www.codeblocks.org/
The open source, cross platform,
free C++ IDE.
Licencia:
GPL-3
Palabras clave: codeblocks
Encontradas:
1 entry
Ahora hay que instalarlo con el nombre del paquete que se obtuvo de la b
usqueda:
# equo install dev-util/codeblocks-10.05-r1
>> @@ Calculando dependencias...
>> ## [R] [sabayon-weekly] dev-util/codeblocks-10.05-r1|0
[10.05-r1|0]
>> @@ Paquetes que necesitan ser instalados/actualizados/desactualizados: 1
>> @@ Paquetes que necesitan ser eliminados: 0
>> @@ Tama~
no de la descarga: 0b
>> @@ Espacio liberado en disco: 0.0b
>> @@ Necesitas al menos: 20.5MB de espacio libre
>> ::: >>> (1/1) 1 paquete
>>
## La comprobaci
on de suma del paquete coincide: dev-util:codeblocks-10.05-r1~0.tbz2
>> +++ >>> (1/1) dev-util/codeblocks-10.05-r1
>>
## Desempaquetando: dev-util:codeblocks-10.05-r1~0.tbz2
>>
## Instalando el paquete: dev-util/codeblocks-10.05-r1
>>
## [The open source, cross platform, free C++ IDE.]
>>
## Actualizando en la base de datos: dev-util/codeblocks-10.05-r1
>>
## Limpiando los datos previamente instalados de la aplicaci
on
>>> Regenerating /etc/ld.so.cache...
>>> Regenerating /etc/ld.so.cache...
>>
## Limpiando: dev-util/codeblocks-10.05-r1
>> @@ Instalaci
on completada.
>> @@ No configuration files to update.
Solo queda cerrar la sesi
on de super usuario:
# exit
Se puede iniciar Code::Blocks desde consola escribiendo codeblocks o buscandolo en el lanzador de aplicaciones (depende de la distribucion y el tipo de escritorio a usar).
La primera vez que inicia el programa, la primera pregunta es el tipo de compilador a usar,
en este caso es GNU GCC Compiler.
4.1 ANSI C
23
Ya instalado el IDE necesitamos los encabezados y libreras de OCL, los encabezados son
est
andares y pueden obtenerse de la pagina de Kronos Group, por otro lado, las libreras dependen
del fabricante del dispositivo OCL. Si ya se tienen instalados los controladores de video, entonces
tanto los encabezado de OCL como las libreras ya se encuentran instalados en el sistema. Suelen
estar en el directorio: /usr/lib/OpenCL/ o alguna variante como: /usr/lib32/OpenCL/,
/usr/lib64/OpenCL/ En este directorio estan las carpetas: global que posee los encabezados
y vendors, donde est
an las carpetas de los fabricantes del dispositivo (tpicamente estan AMD
y NVIDIA), ah se encuentran las libreras que se necesitan.
4.1.2.
Configurando Code::Blocks
En este punto solo queda configurar nuestro IDE para poder usar OCL, para esto primero hay
que crear un nuevo proyecto haciendo clic en el men
u File y de ah a New, seleccionando
Project:
24
4 OpenCL en Linux
Ahora solo queda definir el nombre del proyecto y el directorio donde se va a trabajar:
4.1 ANSI C
25
Finalmente se piden opciones del compilador, en este caso se dejan las opciones por defecto:
Ahora necesitamos definir las rutas donde estan los archivos de encabezado y las libreras
OCL, para esto se hace clic en Settings y luego en Compiler and debugger:
26
4 OpenCL en Linux
Cabe se
nalar que la carpeta a seleccionar no es la carpeta CL, si no una carpeta anterior a esta dado que las cabeceras hacen referencia a los otros archivos por medio de la ruta
CL/archivo.h, por esta raz
on se elige la carpeta include.
Ahora sigue agregar la ruta de las libreras binarias, para esto se selecciona la pesta
na Linker
settings, aqu se presiona el bot
on Add y en la ventana buscamos el directorio donde estan
las libreras, en este ejemplo usamos las libreras de AMD, estas se encuentran en el directorio:
/usr/lib/OpenCL/vendors/amd/
Se pueden usar las libreras de 32 o 64 bits en las respectivas carpetas lib32 y lib64.
4.1 ANSI C
27
Finalmente queda agregar los parametros del compilador, para esto se presiona la pesta
na
Linker settings y en el cuadro Other linker options se escribe la opcion -lOpenCL.
28
4 OpenCL en Linux
4.1.3.
HelloOCL
Ahora toca escribir el programa, siguiendo con la metodologa hasta el momento, se hace un
peque
no programa que sume los elementos de dos vectores y guarde los resultados en un tercero.
En los lenguajes siguientes (C# y Java), se usan Libreras de terceros que simplifican en gran
manera el uso de OCL, pero en este caso se trabaja con las funciones crudas de OCL (es
conveniente conocer las funciones oficiales del estandar), en un principio parece mas complicado,
pero es el mismo principio de funcionamiento que se ha estado manejando hasta el momento
(crear variables locales e inicializarlas, crear el contexto del dispositivo, crear las variables del
dispositivo, copiar los datos al dispositivo, crear los comandos de ejecucion, definir los Workers
a usar, ejecutar el programa en el dispositivo y recuperar los datos).
Primero se definen las cabeceras a usar:
#include <stdio.h>
#include <stdlib.h>
#include <CL/cl.h>
Las constantes:
4.1 ANSI C
29
30
4 OpenCL en Linux
v1_mem_obj, CL_TRUE, 0,
NULL, NULL);
v2_mem_obj, CL_TRUE, 0,
NULL, NULL);
4.1 ANSI C
31
Ejecuci
on del Kernel:
size_t global_item_size = VECTOR_SIZE; // Cantidad de Workers a usar
size_t local_item_size = 64; // Se procesaran el grupos de 64
ret = clEnqueueNDRangeKernel(command, kernel, 1, NULL, &global_item_size,
&local_item_size, 0, NULL, NULL);
Se recuperan los datos procesados en el dispositivo y se guardan en una variable local:
float *V3 = (float*)malloc(sizeof(float)*VECTOR_SIZE);
ret = clEnqueueReadBuffer(command, v3_mem_obj, CL_TRUE, 0,VECTOR_SIZE * sizeof(float),
V3, 0, NULL, NULL);
Ahora hay que crear un archivo de texto en la carpeta del proyecto y llamarlo example.cl y
escribir el c
odigo OCL en el:
__kernel void vector_add(__global float *v1, __global float *v2, __global float *v3)
{
int i = get_global_id(0);
v3[i] = v1[i] + v2[i];
}
Imprimir los primeros 100 resultados
for(i = 0; i < 100; i++)
printf("%f + %f = %f\n", V1[i], V2[i], V3[i]);
Liberar la memoria que se reserv
o:
ret = clFlush(command);
ret = clFinish(command);
ret = clReleaseKernel(kernel);
ret = clReleaseProgram(program);
ret = clReleaseMemObject(v1_mem_obj);
ret = clReleaseMemObject(v2_mem_obj);
ret = clReleaseMemObject(v3_mem_obj);
ret = clReleaseCommandQueue(command);
ret = clReleaseContext(context);
free(V1);
free(V2);
free(V3);
32
4 OpenCL en Linux
Finalmente s
olo hay que compilar y correr el programa, si todo sale bien se obtiene un resultado
como este:
4.2.
Java
Hay varias libreras que permiten el uso de OCL en java. Aqu se muestran algunas:
JOCL3 Esta librera ofrece funcionalidad OpenCL en Java, esta API es muy similares a la
original de OpenCL. Las funciones se proporcionan como metodos estaticos, y la semantica de
estos metodos se han mantenido coherentes con las funciones de la librera original, salvo las
limitaciones especficas del lenguaje de Java. Esta API proporciona una traduccion casi literal
de las funciones originales en C, por lo que no es muy recomendable para recien iniciados.
JOCL de JogAmp.org4 El objetivo de esta librera es proporcionar una abstraccion orientada
a objetos de OpenCL para Java. Esto simplifica su uso y puede ser mas natural y conveniente
para la mayora de los programadores de Java. La librera tambien ofrece una interfaz de bajo
nivel que se genera mediante la librera GlueGen. Esta interfaz es similar a la API OpenCL
original, pero no est
a destinada a ser utilizada por los clientes, si no mas bien sirve como base
3
http://www.jocl.org/downloads/downloads.html
http://jogamp.org/jocl/www/
4.2 Java
33
para la creaci
on de Wrappers orientados a objetos.
JavaCL5 Esta librera tambien ofrece una abstraccion orientada a objetos de OpenCL para
Java. Tiene una interfaz de bajo nivel que se basa en JNA creada usando la librera JNAerator.
La interfaz de bajo nivel sirve como base para crear Wrappers orientados a objetos, pero no
est
a destinado a ser utilizado por los clientes.
En este tutorial se utilizo la librera JOCL de JogAmp.org, por ser la mas simplificada y
esta orientada a objetos.
4.2.1.
Descargas e Instalaciones
Una de las ventajas de programar en Java, es que el codigo puede compilarse y correrse
en cualquier sistema que posea la maquina virtual Java, por lo tanto es necesario que este la
m
aquina virtual instalada en Linux, as como las dependencias necesarias. Tambien es necesario
alg
un IDE para programar, en este caso se usara NetBeans. La manera mas sencilla de tener lo
necesario es instalar NetBeans directamente y dejar que el manejador de paquetes calcule todas
las dependencias que se necesitan. Esto depende de la distribucion de Linux a usar.
En este ejemplo se uso la distribucion Sabayon Linux6 , el procedimiento para instalar NetBeans
fue el siguiente: Abrir la consola del sistema e iniciar sesion como super usuario con el comando
su. Ahora se utiliza el gestor de paquetes de la distribucion llamado equo, primero hay que
buscar NetBeans para saber el nombre exacto del paquete a instalar:
# equo search netbeans-ide
Esta b
usqueda arroja un resultado como el siguiente:
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
5
6
@@ Buscando...
@@ Paquete: dev-java/netbeans-ide-7.0.1 rama: 5, [sabayon-weekly]
Disponible:
versi
on: 7.0.1 ~ tag: NoTag ~ revisi
on: 0
Instalado:
versi
on: No instalado ~ tag: N/D ~ revisi
on: N/D
Bloque:
7.0
P
agina:
http://netbeans.org/projects/ide
Descripci
on:
Netbeans IDE Cluster
Licencia:
CDDL GPL-2-with-linking-exception
@@ Paquete: dev-java/netbeans-ide-7.1.2 rama: 5, [sabayon-weekly]
Disponible:
versi
on: 7.1.2 ~ tag: NoTag ~ revisi
on: 0
Instalado:
versi
on: 7.1.2 ~ tag: NoTag ~ revisi
on: 0
Bloque:
7.1
P
agina:
http://netbeans.org/projects/ide
Descripci
on:
Netbeans IDE Cluster
Licencia:
CDDL GPL-2-with-linking-exception
http://code.google.com/p/javacl/
http://www.sabayon.org/
34
4 OpenCL en Linux
>>
>>
>>
>>
>>
>>
>>
>>
>>
4.2.2.
Configurando NetBeans
Ya que se ha instalado todo el software necesario, se procede a configurar NetBeans; para ello
creamos un nuevo proyecto:
7
http://jogamp.org/deployment/jogamp-current/archive/jogamp-all-platforms.7z
4.2 Java
35
36
4 OpenCL en Linux
Asignar un nombre al proyecto y oprimir Terminar. Una vez abierto el proyecto, se da clic
con el bot
on derecho del rat
on sobre Bibliotecas y se selecciona Agregar biblioteca.
4.2 Java
37
Una vez oprimido Aceptar, abrira otra ventana: aqu se escogen los archivos JAR que usar
la nueva librera, para ello se presiona el boton Agregar archivo JAR/Carpeta: Aqu se debe
38
4 OpenCL en Linux
4.2 Java
4.2.3.
39
HelloJOCL
Ahora que todo esta listo, empezaremos a escribir nuestro primer programa OpenCL en Java,
comenzamos por importar las libreras a usar:
import
import
import
import
import
import
import
import
import
static com.jogamp.opencl.CLMemory.Mem.READ_ONLY;
static com.jogamp.opencl.CLMemory.Mem.WRITE_ONLY;
com.jogamp.opencl.*;
java.io.File;
java.io.FileNotFoundException;
java.io.FileReader;
static java.lang.System.out;
java.nio.FloatBuffer;
java.util.Scanner;
Ahora procedemos al main. Lo primero que hay que hacer es crear nuestro contexto para el
dispositivo OpenCL a usar y el tipo de este:
CLContext context = CLContext.create(CLDevice.Type.GPU);
Esta lnea crea el contexto con un dispositivo de tipo GPU.
Ahora se escoge el dispositivo a usar:
CLDevice device = context.getDevices()[0];
Dependiendo del sistema, puedes tener varios dispositivos disponibles (por ejemplo, si se tiene
un arreglo SLI (m
as de una tarjeta de video Nvidia) o Crossfire (mas de una tarjeta de video
AMD/ATI), se debe escoger uno de los dispositivos disponibles, en este caso, tomaremos el primer
dispositivo disponible.
Se carga el programa OCL desde archivo. A lo largo de este tutorial se ha estado usando el
mismo archivo con el c
odigo OCL example.cl, solo basta agregarlo a la carpeta del proyecto
para acceder directamente a el o escribir la ruta completa donde esta el archivo.
40
4 OpenCL en Linux
Scanner scanner;
String source_str = "";
try{
scanner = new Scanner(new FileReader(new File("example.cl")));
while ( scanner.hasNextLine() ){
source_str += scanner.nextLine();
}
scanner.close();
}
catch (FileNotFoundException ex){
out.println(ex);
}
Se compila el c
odigo OCL en el contexto creado, para esto se pasa la cadena que contiene el
c
odigo del programa.
CLProgram program = context.createProgram(source_str).build();
Se crean los buffers que contendr
an los datos a procesar:
CLBuffer<FloatBuffer> bufferv1 = context.createFloatBuffer(VECTOR_SIZE, READ_ONLY);
CLBuffer<FloatBuffer> bufferv2 = context.createFloatBuffer(VECTOR_SIZE, READ_ONLY);
CLBuffer<FloatBuffer> bufferv3 = context.createFloatBuffer(VECTOR_SIZE, WRITE_ONLY);
Se inicializan los buffers con los datos que se van a procesar:
for(int i = 0; i < VECTOR_SIZE; i++){
bufferv1.getBuffer().put((float)i / 10);
bufferv2.getBuffer().put(-(float)i / 9);
}
bufferv1.getBuffer().rewind();
bufferv2.getBuffer().rewind();
Ahora se crea el Kernel que ejecutara el programa:
CLKernel kernel = program.createCLKernel("vector_add");
Ahora solo hay que pasar al Kernel los buffers creados como argumentos, hay que recordar
que la cantidad de variables a pasar debe coincidir en numero y tipo de datos definidos en el
Kernel.
4.2 Java
41
42
4.3.
4 OpenCL en Linux
C#
4.3.1.
Descargas e instalaciones
La instalaci
on del framework Mono depende de la distribucion de Linux a usar, en este caso
se trabajo con la distribuci
on Sabayon Linux, la herramienta para instalar paquetes se denomina
equo, para instalar los paquetes necesarios se abre una terminal de consola y se inicia sesion
como super usuario con el comando su. La instalacion requerira de varias dependencias, la
manera m
as sencilla de tener todo lo necesario para empezar a programar es instalar directamente
el IDE de Mono, para esto buscamos el nombre completo del paquete a instalar con el programa
equo:
4.3 C#
43
@@ Buscando...
@@ Paquete: dev-util/monodevelop-2.8.5.1 rama: 5, [sabayon-weekly]
Disponible:
versi
on: 2.8.5.1 ~ tag: NoTag ~ revisi
on: 1
Instalado:
versi
on: 2.8.5.1 ~ tag: NoTag ~ revisi
on: 1
Bloque:
0
P
agina:
http://www.monodevelop.com/
Descripci
on:
Integrated Development Environment
for .NET
Licencia:
GPL-2
@@ Paquete: dev-util/monodevelop-database-2.8.5.1 rama: 5, [sabayon-weekly]
Disponible:
versi
on: 2.8.5.1 ~ tag: NoTag ~ revisi
on: 0
Instalado:
versi
on: No instalado ~ tag: N/D ~ revisi
on: N/D
Bloque:
0
P
agina:
http://www.monodevelop.com/
Descripci
on:
Database Browser Extension for
MonoDevelop
Licencia:
GPL-2
@@ Paquete: dev-util/monodevelop-debugger-gdb-2.8.5.1 rama: 5, [sabayon-weekly]
Disponible:
versi
on: 2.8.5.1 ~ tag: NoTag ~ revisi
on: 0
Instalado:
versi
on: No instalado ~ tag: N/D ~ revisi
on: N/D
Bloque:
0
P
agina:
http://www.monodevelop.com/
Descripci
on:
GDB Extension for MonoDevelop
Licencia:
GPL-2
@@ Paquete: dev-util/monodevelop-java-2.8.5.1 rama: 5, [sabayon-weekly]
Disponible:
versi
on: 2.8.5.1 ~ tag: NoTag ~ revisi
on: 0
Instalado:
versi
on: No instalado ~ tag: N/D ~ revisi
on: N/D
Bloque:
0
P
agina:
http://www.monodevelop.com/
Descripci
on:
Java Extension for MonoDevelop
Licencia:
GPL-2
@@ Paquete: dev-util/monodevelop-vala-2.8.5.1 rama: 5, [sabayon-weekly]
Disponible:
versi
on: 2.8.5.1 ~ tag: NoTag ~ revisi
on: 0
Instalado:
versi
on: No instalado ~ tag: N/D ~ revisi
on: N/D
Bloque:
0
P
agina:
http://www.monodevelop.com/
Descripci
on:
Vala Extension for MonoDevelop
Licencia:
GPL-2
Palabras clave: monodevelop
Encontradas:
5 entries
44
4 OpenCL en Linux
4.3.2.
Configurando MonoDevelop
Ya que tenemos todo lo necesario, hay que empezar un nuevo proyecto, para esto ejecutamos
MonoDevelop. Nos dirigimos al men
u Archivo, Nuevo y Espacio de Trabajo:
http://www.cmsoft.com.br/download/OpenCLTemplate.zip
4.3 C#
45
En la siguiente ventana, seleccionamos Proyecto de consola C# e ingresamos el mismo nombre del espacio de trabajo:
46
4 OpenCL en Linux
Ahora queda agregar las referencias de la librera que vamos a utilizar, para esto editamos las
referencias de nuestra soluci
on:
4.3 C#
47
4.3.3.
HelloSharpOCL
Ahora todo esta listo para empezar, iniciamos por importar las librera que usaremos:
using System;
using System.IO;
using OpenCLTemplate;
Las constantes:
static int VECTOR_SIZE = 1024;
// tama~
no de los vectores
48
4 OpenCL en Linux
4.3 C#
49
Captulo 5
OpenCL en Windows
Esta secci
on es para aquellos que no estan familiarizados con Linux y desean probar OpenCL
en Windows con los lenguajes que figuran en este manual, los pasos son practicamente iguales
a los expuestos en la secci
on de Linux, salvo la instalacion de software y un par de elementos a
tomar en cuenta sobre Windows.
5.1.
ANSI C
En esta parte del tutorial vamos a utilizar ANSI C para nuestro HelloOCL, para esto necesitaremos de tres herramientas:
5.1.1.
Descargas e instalaciones
Un IDE para programar en C; en nuestro caso y para mantener la consistencia con Linux
usaremos Code::Blocks, es una herramienta libre y gratuita que la puedes descargar de su
pagina oficial: 1 . Usaremos la version mingw-setup que es la que posee el compilador GCC y
el Depurador GDB incluidos (suele ser el instalador mas grande).
Necesitaremos las cabeceras de la librera de OpenCL; se pueden conseguir directamente de
la pagina de Krhonos Group 2 , estos archivos son:
1. opencl.h
2. cl platform.h
3. cl.h
4. cl ext.h
5. cl dx9 media sharing.h
6. cl d3d10.h
1
http://www.codeblocks.org/downloads/26#windows
http://www.khronos.org/registry/cl/
51
52
5 OpenCL en Windows
7. cl d3d11.h
8. cl gl.h
9. cl gl ext.h
Hay que crear una carpeta llamada CL y copiar todos los archivos en ella. Tambien es
posible obtener estos archivos de las herramientas SDK de los fabricantes del dispositivo OpenCL.
Por ultimo, necesitaras las libreras binarias OpenCL.lib y OpenCL.a para poder usar las
funciones de las cabeceras, estas libreras dependen del dispositivo y por lo tanto se deben
conseguir con el fabricante de este, para nuestro ejemplo usare una tarjeta ATI cuyo fabricante
es AMD. Para obtener la librera se siguieron los siguientes pasos: Descargar el SDK 3 de la
p
agina oficial. En nuestro caso se usara la version de 64 bits.
Ejecuta el instalador, aqu se iniciara el instalador del Catalyst Control Center para nuestra
tarjeta de video, si no se posee el controlador de video o se tiene un controlador basico se
puede continuar e instalar las opciones por defecto, de esta forma tendremos todos los archivos
necesarios para trabajar.
Por otro lado puede que tengas los controladores de video al da junto con el controlador
OpenCL ya instalados, por lo tanto no es necesario instalar el Catalyst Control Center, en este
caso puede buscar en la carpeta donde se descomprimio el archivo y buscar el SDK de OpenCL
para obtener la libreria que necesitamos, este instalador esta en esta carpeta:
C:\AMD\Support\streamsdk_v2_8_win64\Packages\Apps\AMDAPPSDK_Dev64
Ejecutamos el archivo AMDAPPSDK Dev.msi e instalamos con las opciones por defecto.
Siguiendo cualquiera de los dos casos, nuestra librera OpenCL.lib estara en la carpeta:
C:\Program Files (x86)\AMD APP\lib\x86
5.1.2.
Configurando Code::Blocks
Ya que se instalaron los archivos necesarios, necesitamos configurar nuestro IDE para que
pueda usarlos, para esto primero hay que crear un nuevo proyecto haciendo clic en el men
u File
y de ah a New, seleccionando Project:
3
http://developer.amd.com/tools/heterogeneous-computing/amd-accelerated-parallel-processing-app-sdk/
downloads/
5.1 ANSI C
53
54
5 OpenCL en Windows
Ahora solo queda definir el nombre del proyecto y el directorio donde se va trabajar:
Finalmente se piden opciones del compilador, en este caso se dejan las opciones por defecto:
5.1 ANSI C
55
Ahora necesitamos definir las rutas donde estan los archivos de encabezado y las libreras
OpenCL, para esto se hace clic en Settings y luego en Compiler:
56
5 OpenCL en Windows
Cabe se
nalar que la carpeta a seleccionar no es la carpeta CL, si no una carpeta anterior a
esta pues las cabeceras hacen referencia a los otros archivos por medio de la ruta CL\archivo.h.
Por comodidad puedes copiar la carpeta CL a la carpeta del proyecto y usar este directorio en
la ruta.
Ahora toca agregar la ruta de las libreras binarias, para esto seleccionamos la pesta
na Linker
settings, aqu presionamos el bot
on Add y en la ventana que aparezca, buscamos el directorio
donde estan las libreras, en este ejemplo usamos la libreras de AMD, estas se encuentran en el
directorio: C:\Program Files (x86)\AMD APP\lib\x86:
5.1 ANSI C
57
Finalmente queda agregar los parametros del compilador, para esto presionamos la pesta
na
Linker settings y en el cuadro Other linker options escribimos la opcion -lOpenCL
58
5 OpenCL en Windows
5.1.3.
HelloOCL
Ahora toca escribir nuestro programa, siguiendo con la metodologa hasta el momento, haremos un peque
no programa que sume los elementos de dos vectores y guarde los resultados en un
tercero. En los lenguajes siguientes (C# y Java), se usan libreras de terceros que simplifican en
gran manera el uso de OCL, pero en este caso se trabajara con las funciones crudas de OpenCL
(es conveniente conocer las funciones oficiales del estandar), en un principio parecera mas complicado, pero es el mismo principio de funcionamiento que se ha estado viendo hasta el momento
(crear variables locales e inicializarlas, crear el contexto del dispositivo, crear las variables del
dispositivo, copiar los datos al dispositivo, crear los comandos de ejecucion, definir los workers
a usar, ejecutar el programa en el dispositivo y recuperar los datos de este).
Empezamos definiendo las cabeceras que usaremos:
#include <stdio.h>
#include <stdlib.h>
#include <CL\cl.h>
Las constantes:
#define MAX_SOURCE_SIZE 512 // tama~
no m
aximo del archivo con el c
odigo fuente OCL
#define VECTOR_SIZE 1024 // tama~
no de los vectores
Se crean los vectores que contendr
an los datos y se inicializan con valores:
5.1 ANSI C
59
60
5 OpenCL en Windows
v1_mem_obj, CL_TRUE, 0,
NULL, NULL);
v2_mem_obj, CL_TRUE, 0,
NULL, NULL);
Se crea el programa que ejecutara el dispositivo OCL con el codigo fuente que se guardo con
anterioridad:
cl_program program = clCreateProgramWithSource(context, 1,
(const char **)&source_str, (const size_t *)&source_size, &ret);
Se compila el programa en el dispositivo OCL:
ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
Se crea el Kernel con la funci
on principal:
cl_kernel kernel = clCreateKernel(program, "vector_add", &ret);
Se crean los argumentos que se pasaran al Kernel:
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&v1_mem_obj);
ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&v2_mem_obj);
ret = clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&v3_mem_obj);
Ejecuci
on del Kernel:
5.1 ANSI C
61
62
5 OpenCL en Windows
Finalmente solo hay que compilar y correr el programa, si todo salio bien se obtendra un resultado
como este:
5.2.
Java
Una de las ventajas de programar en Java, es que el codigo puede compilarse y ejecutarse en
cualquier sistema que posea la maquina virtual Java, por lo tanto es necesario que este instalada
en Windows. Tambien es necesario el Kit SDK de Java y alg
un IDE para programar, para seguir
en consistencia con Linux, usaremos NetBeans.
5.2.1.
Descargas e instalaciones
http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
http://netbeans.org/downloads/index.html
5.2 Java
63
64
5 OpenCL en Windows
Finalmente solo se necesitan las libreras que nos permitan usar OpenCL en Java, se usara
la misma librera que se uso en la seccion de Linux JOCL de JogAmp.org6 , solo basta con
descargar el comprimido y descomprimirlo en alg
un directorio de facil acceso.
5.2.2.
Configurando NetBeans
Ya que se ha instalado todo el software necesario, se procede a configurar NetBeans; para ello
creamos un nuevo proyecto:
http://jogamp.org/deployment/jogamp-current/archive/jogamp-all-platforms.7z
5.2 Java
65
66
5 OpenCL en Windows
Una vez oprimido Aceptar, abrira otra ventana: aqu se escogen los archivos JAR que usar
la nueva librera, para ello se presiona el boton Agregar archivo JAR/Carpeta:
5.2 Java
67
Aqu se debe buscar la carpeta donde se descomprimio la librera, la carpeta se llama jogampall-platforms y en su interior esta la carpeta jar que posee las archivos que se necesitan:
68
5 OpenCL en Windows
5.2.3.
HelloJOCL
Ahora que todo esta listo, empezaremos a escribir nuestro primer programa OpenCL en Java,
comenzamos por importar las libreras a usar:
import static com.jogamp.opencl.CLMemory.Mem.READ_ONLY;
import static com.jogamp.opencl.CLMemory.Mem.WRITE_ONLY;
5.2 Java
69
import
import
import
import
import
import
import
com.jogamp.opencl.*;
java.io.File;
java.io.FileNotFoundException;
java.io.FileReader;
static java.lang.System.out;
java.nio.FloatBuffer;
java.util.Scanner;
Ahora procedemos al main. Lo primero que hay que hacer es crear nuestro contexto para el
dispositivo OpenCL a usar y el tipo de este:
CLContext context = CLContext.create(CLDevice.Type.GPU);
Esta lnea crea el contexto con un dispositivo de tipo GPU.
Ahora se escoge el dispositivo a usar:
CLDevice device = context.getDevices()[0];
Dependiendo del sistema, puedes tener varios dispositivos disponibles (por ejemplo, si se tiene
un arreglo SLI (m
as de una tarjeta de video Nvidia) o Crossfire (mas de una tarjeta de video
AMD/ATI), se debe escoger uno de los dispositivos disponibles, en este caso, tomaremos el primer
dispositivo disponible.
Se carga el programa OCL desde archivo. A lo largo de este tutorial se ha estado usando el
mismo archivo con el c
odigo OCL example.cl, solo basta agregarlo a la carpeta del proyecto
para acceder directamente a el o escribir la ruta completa donde esta el archivo.
Scanner scanner;
String source_str = "";
try{
scanner = new Scanner(new FileReader(new File("example.cl")));
while ( scanner.hasNextLine() ){
source_str += scanner.nextLine();
}
scanner.close();
}
catch (FileNotFoundException ex){
out.println(ex);
}
Se compila el c
odigo OCL en el contexto creado, para esto se pasa la cadena que contiene el
c
odigo del programa.
70
5 OpenCL en Windows
5.2 Java
71
Captulo 6
A lo largo de este tutorial se ha visto como utilizar OpenCL en algunos lenguajes de programaci
on populares, como inicializar dispositivos, manipular datos en estos y ejecutar programas
OCL, todos son ejemplos introductorios sobre como funciona esta tecnologa. En este punto
se querr
a ver alg
un ejemplo m
as funcional de lo que se podra hacer con OpenCL, para esto
haremos una peque
na aplicaci
on con C# ( que puede ser facilmente traducida a otro lenguaje
pues no se usan rutinas muy especificas del lenguaje) que genere fractales.
En esta secci
on se ver
an algunos topicos como:
1. Uso multidimensional de workers.
2. Utilizaci
on de funciones locales en OCL.
3. Utilizaci
on de estructuras en OCL.
4. Buffers de imagen en OpenCL.
6.1.
Introducci
on a los fractales
http://es.wikipedia.org/wiki/Conjunto_de_Mandelbrot
73
74
6.2.
6.3 Escribiendo el c
odigo OpenCL
75
En este caso se calcula Z con la ecuacion del fractal a dibujar (la ecuacion del ejemplo pertenece
al denominado conjunto de Julia 2 ).
Una vez calculado Z se verifica si no diverge, se utiliza la formula x2 + y 2 = 4 . Se posee
un lmite de iteraciones para ver si el n
umero diverge, en este caso es n, entre mas grande sea
m
as precisi
on tendr
a la imagen. Un limite com
un es 256, que permite generar puntos para una
imagen de 8 bits.
Como se puede ver, toma demasiado tiempo calcular el color para cada pixel (siendo el peor
de los casos, n iteraciones por pixel). Tiempo que se agrava con forme mas resolucion se desea
para la imagen final.
Este es un buen lugar para ver como el computo paralelo puede incrementar la velocidad de
procesado y compararlo con el procesamiento secuencial.
6.3.
Escribiendo el c
odigo OpenCL
La primera parte sera escribir el codigo OCL en un archivo de texto plano, una vez creadas las
rutinas y el Kernel, se pasa a convertir estas mismas funciones en el lenguaje de programacion
C# para hacer la versi
on secuencial. Finalmente se medira la velocidad de ejecucion de ambas
versiones. Primero se definien las constantes del programa, para definir una constantes, se utiliza
la palabra reservada __constant seguida de la definicion de la constante:
/* Limite de la divergencia */
__constant float DIVERGE = 4.0;
/* Profundidad de color por pixel, se tendran 256 tonos de color */
__constant uint MAX = 256;
Como se menciono al principio, las formulas de los Fractales son ecuaciones con n
umeros complejos 3 , suena mas complicado a primera vista, pero podemos decir que los n
umeros complejos
poseen una parte real y una imaginaria.
El lenguaje OCL-C99 posee tipos de datos que pueden ser utilizados para este proposito, estos
son los tipos de datos vector. Estos tipos de datos son variantes de los tipos de datos nativos (int,
float, long, etc) definidos de la forma intn, floatn, longn donde n denota el n
umero de datos
por tipo, es decir. Un float2 es un tipo de dato formado por dos componentes de tipo flotante,
la siguiente tabla explica como se accede a estos componentes:
2
3
http://en.wikipedia.org/wiki/Julia_set
http://en.wikipedia.org/wiki/Complex_number
76
Por otro lado, puede que exista situaciones donde se requera de algun tipo de datos especial,
para esto se puede recurrir una estructura. Por ejemplo, en lugar de utilizar los tipos de datos
vector, se puede definir la estructura de los n
umeros complejos.
typedef struct Complex
{
float Real;
float Img;
} Complex;
De igual manera como se hara en C clasico. Ahora se necesitan las rutinas de aritmetica basica
para estos n
umeros, dada la formula que se utilza, se necesita sumar y multiplicar, as como la
funci
on que calcule la divergencia del n
umero. Las funciones locales en OCL se declaran con
la palabra reservada inline, seguida de la declaracion de la funcion (tipo de dato que regresa,
nombre de la funci
on y par
ametros).
inline Complex Sum(struct Complex a, struct Complex b)
{
Complex aux;
aux.Real = a.Real + b.Real;
aux.Img = a.Img + b.Img;
return aux;
}
inline Complex Mult(struct Complex a,struct Complex b)
{
Complex aux;
aux.Real = a.Real * b.Real - a.Img * b.Img;
aux.Img = a.Real * b.Img + a.Img * b.Real;
return aux;
}
inline float Norm(struct Complex a)
{
return a.Real * a.Real + a.Img * a.Img;
}
Ahora se crea la parte importante del codigo, el Kernel. El Kernel recibira algunos parametros,
como la parte real e imaginaria de la ecuacion. El ancho y alto de la imagen donde se guardara
el fractal (esto define la resoluci
on de la imagen) y el buffer donde se guardaran los puntos
obtenidos.
6.3 Escribiendo el c
odigo OpenCL
6.3.1.
77
image2d
Aqu conviene hacer un parentesis y explicar un poco al respecto de las variables image2d. Las
GPUs est
an optimizadas para manejar caches de imagenes/texturas de manera eficiente, pues es
parte de su naturaleza, siendo dispositivos que fueron creados con ese proposito (manejar texturas
sobre vertices, etc). Brindan algunas ventajas sobre el uso de buffers de memoria convencional,
por ejemplo:
Por ser buffers de textura, su manipulacion y tiempos de acceso son mas rapidos. Permiten
utilizar toda la memoria de video disponible. Las texturas pueden ser de 8192x8192 flotantes,
esto es 226 elementos * 4 componentes (RGBA) por elemento * 4 bytes por flotante, estos son
1024Mb, es decir, 1 Gb, en contra parte con los buffers de memoria convencional que tienen
limites de tama
no y son mas lentos de accesar.
Por desgracia tambien tienen sus desventajas que hay que tener en cuenta:
1. Las im
agenes solo pueden ser de lectura o escritura, no pueden ser ambas a la vez.
2. Los buffers de memoria convencionales son mas faciles de usar que las imagenes.
3. No todo el Hardware OCL es compatible con este tipo de variable.
Teniendo esto en mente, usaremos una variable image2d donde se escribira el fractal, el uso
de este tipo de variable nos ahorra algunos pasos como calcular la posicion del pixel y el formato
de color, si en alg
un caso el hardware con el que se trabaja no soporta este tipo de variable, se
puede sustituir la variable image2d por un vector de tipo int, en la seccion de apendice, viene
este ejemplo usando vectores en lugar de imagenes.
6.3.2.
Definici
on del Kernel
Explicaci
on del Kernel para el fractal explicando por lneas:
__kernel void Fractal(__global float *r, __global float *i, __global int *width,
__global int *height, write_only image2d_t buffer)
{
/* limites del plano a dibujar*/
float
float
float
float
REALMIN
IMAGMIN
REALMAX
IMAGMAX
=
=
=
=
-1.0;
-1.0;
1.0 ;
1.0;
Complex C; // nuestro n
umero de control
/* estos vectores solo poseen un elemento,
solo basta leer la primera posici
on (viejo truco de C) */
C.Real = (*r);
78
C.Img = (*i);
/*
El fractal usara workers de 2 dimensiones, esto es, cada worker se encargar
a
de una l
nea de la imagen y los sub-workers de este se encargar
an de cada
pixel de la l
nea.
*/
int x = get_global_id(0); // worker principal
int y = get_global_id(1); // worker secundario
/*
Se convierten las coordenadas x, y en el tipo de coordenadas que usa el buffer
de imagen. N
otese el uso del ancho en x, esto es por que la imagen se dibuja
invertida
*/
int2 coord = (int2)((*width) - x,y);
/* C
alculo de Z con respecto a su posici
on en el plano */
Complex Z;
Z.Real=(((REALMAX - REALMIN) / convert_float(*width)) * x + REALMIN);
Z.Img=(((IMAGMAX - IMAGMIN) / convert_float(*height)) * y + IMAGMIN);
/* Algoritmo de Tiempo de Escape*/
uint c = 0;
uint4 pixel = 0;
do
{
Z = Sum(Mult(Z, Z), C); // Ecuaci
on del fractal
c++;
if (Norm(Z) > DIVERGE)
break;
} while (c < MAX);
/*
Si el n
umero de iteraciones no llego al l
mite del ciclo, entonces el
n
umero no pertenece al conjunto de Mandelbrot.
*/
if (c != MAX)
{
/* los componentes del pixel x,y,z,w forman parte de los componentes
de color:
x es azul
6.4 Escribiendo el c
odigo anfitri
on
79
y es rojo
z es verde
w es el canal Alfa (transparencia)
*/
pixel.y = c;
pixel.x = c;
pixel.z = c;
pixel.w = 255;
/*
Al aplicar a los 3 componentes de color el mismo n
umero de iteraciones
que se obtuvo del algoritmo, (cuyo l
mite es 255), obtendremos un
gradiente de color que va del negro al blanco (una escala de grises)
*/
}
/*Guardar en la imagen el pixel y las coordenadas que se calcularon*/
write_imageui(buffer, coord, pixel);
}
6.4.
Escribiendo el c
odigo anfitri
on
El ejemplo que se esta realizando no esta pensado para una plataforma en especial (Window,
Linux, BSD, Mac etc), por lo que el programa anfitrion no tendra ventanas, el uso de ventanas
depende del sistema operativo, por lo que se necesitara reescribir el codigo en la parte grafica
para cada sistema o bien, usar alguna librera multi-plataforma (como la librera GTK), eso dara
para otro tutorial en si mismo. Por esta razon crearemos un proyecto en C# para aplicacion de
consola y nuestro fractal lo guardaremos en un archivo de imagen.
Empezaremos agregando dos nuevas clases a nuestro proyecto, las llamaremos FractalCPU y
FractalOCL.
6.4.1.
FractalCPU
Empezaremos editando la clase FractalCPU, esta sera la clase que creara el fractal de manera
secuencial haciendo uso de la CPU, basicamente sera la traduccion a C# del codigo OCL que
hicimos en la secci
on anterior.
Variables globales:
float DIVERGE = 4.0f;
UInt32 MAX = 256;
Bitmap buffer;
80
6.4 Escribiendo el c
odigo anfitri
on
81
}
Complex Div(Complex a, Complex b)
{
Complex aux;
aux.Real=b.Real;
aux.Img=b.Img*-1.0f;
a = Mult(a,aux);
b = Mult(b,aux);
b.Real= 1 / b.Real;
return Mult(a,b);
}
float Norm(Complex a)
{
return a.Real * a.Real + a.Img * a.Img;
}
Esta es la traducci
on del Kernel a C#. con la diferencia que sera llamada secuencialmente en
los ciclos del constructor.
void Fractal(int x, int y, float r, float imag, int width, int height)
{
float REALMIN = -1.0f;
float IMAGMIN = -1.0f;
float REALMAX = 1.0f;
float IMAGMAX = 1.0f;
Complex C;
C.Real = (r);
C.Img = (imag);
Complex Z;
Z.Real=(((REALMAX - REALMIN) / (float)width) * x + REALMIN);
Z.Img=(((IMAGMAX - IMAGMIN) / (float)height) * y + IMAGMIN);
int c = 0;
Color color = Color.FromArgb(255,0,0,0);
do
{
Z = Sum(Mult(Z, Z), C);
c++;
if (Norm(Z) > DIVERGE)
break;
} while (c < MAX);
if (c != MAX)
color = Color.FromArgb(255,c,c,c);
buffer.SetPixel((width)-x-1,y,color);
}
82
6.4.2.
FractalOCL
Editamos la clase FractalOCL que se encargara de ejecutar el codigo del fractal en el dispositivo
OpenCL y recuperar
a la imagen creada, esta clase solo requiere del constructor y una funcion.
La variable global que guardara el buffer con fractal a crear:
CLCalc.Program.Image2D buffer;
El constructor de la clase, b
asicamente incializa los dispositivos OCL y ejecuta el codigo, en este
punto no es necesario explicar que hace cada funcion pues es lo que se ha estado viendo en todo
el tutorial:
public FractalOCL(Size size, float real, float img)
{
string program = "";
/* Cargar el programa OCL desde archivo, debe estar en la misma carpeta
que el programa ejecutable (generalmente la carpeta bin/debug/)*/
try
{
StreamReader r = new StreamReader("program.cl");
program = r.ReadToEnd();
r.Close();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
CLCalc.InitCL();
CLCalc.Program.Compile(new string[] { program });
CLCalc.Program.Kernel Fractal = new CLCalc.Program.Kernel("Fractal");
Bitmap bmp = new Bitmap(size.Width, size.Height);
/* Se crea el buffer a partir de un BMP */
buffer = new CLCalc.Program.Image2D(bmp);
bmp.Dispose();
CLCalc.Program.Variable vreal =
new CLCalc.Program.Variable(new float[] { real });
CLCalc.Program.Variable vimag =
new CLCalc.Program.Variable(new float[] { img });
CLCalc.Program.Variable vwidth =
new CLCalc.Program.Variable(new int[] { size.Width });
CLCalc.Program.Variable vheight =
new CLCalc.Program.Variable(new int[] { size.Height });
CLCalc.Program.MemoryObject[] Args =
new CLCalc.Program.MemoryObject[]
{ vreal, vimag, vwidth, vheight, buffer };
int[] workers = new int[] { size.Width, size.Height };
6.4 Escribiendo el c
odigo anfitri
on
83
Fractal.Execute(Args, workers);
}
La funci
on que regresara el bitmap con el fractal dibujado.
public Bitmap GetBmp()
{
return buffer.ReadBitmap();
}
6.4.3.
Main
Solo queda ejecutar y medir tiempos, para esto editaremos la funcion main del proyecto.
Empezamos declarando las constantes del fractal, estas definiran la forma y tama
no de la imagen:
static
static
static
static
int w
int h
float
float
Como se puede ver, crearemos una imagen muy grande de 4000x4000 pixeles para ver la
diferencia de tiempos de procesado. El tama
no de la imagen puede generar excepciones al ejecutar
el c
odigo pues los limites de resolucion de textura difieren entre fabricantes y memoria disponible
en el dispositivo, por ejemplo las tarjetas NVidia suelen tener un limite de resolucion 4096x4096
y 8192x8192 para las tarjetas ATI.
public static void Main (string[] args)
{
/* Imprimir la resoluci
on de la imagen*/
Console.WriteLine(
String.Format("Creaci
on de Fractal (Resoluci
on {0} x {1} pixeles)",w,h));
/* Prueba con OCL */
Console.WriteLine("Prueba con dispositivo OpenCL...");
/* Creaci
on de los relojes de medici
on */
Stopwatch sw1 = new Stopwatch();
Stopwatch sw2 = new Stopwatch();
/* Corriendo el primer reloj */
sw1.Reset();
sw1.Start();
/* Hay que recordar que los fractales se inicializan en el constructor,
por eso hay que medir en esta parte */
FractalOCL fractalocl = new FractalOCL(new Size(w,h),real,imag);
/* Detener el reloj de medici
on */
sw1.Stop();
/* Guardar el fractal generado como un BMP */
84
fractalocl.GetBmp().Save("OCLfractal.bmp",
System.Drawing.Imaging.ImageFormat.Bmp);
/* Prueba con la CPU */
Console.WriteLine("Prueba con CPU...");
/* Inicializaci
on del segundo reloj de medicion */
sw2.Reset();
sw2.Start();
/* Medir el tiempo de ejecuci
on */
FractalCPU fractalcpu = new FractalCPU(new Size(w,h),real,imag);
/* Detener el segundo reloj de medici
on */
sw2.Stop();
/* Guardar el fractal generado como un BMP */
fractalcpu.GetBmp().Save("CPUfractal.bmp",
System.Drawing.Imaging.ImageFormat.Bmp);
/* Imprimir los tiempos, el resultado se muestra con el formato
(horas:minutos:segundos.fracciones de segundo) */
Console.WriteLine("Tiempo OpenCL " + sw1.Elapsed);
Console.WriteLine("Tiempo CPU " + sw2.Elapsed);
}
6.4.4.
Los resultados
http://www.notebookcheck.net/AMD-E-Series-E-300-Notebook-Processor.60141.0.html
http://www.notebookcheck.net/AMD-Radeon-HD-6310.40952.0.html
6.4 Escribiendo el c
odigo anfitri
on
85
Mientras que el tiempo utilizando computo paralelo con OpenCL fue de 3 segundos con 236
milesimas, en computo secuencial fue de 1 minuto con 15 segundos y 227 milesimas. Comparando
86
el desempe
no, vemos que OpenCL fue casi 23 veces mas rapido que la CPU (3,236 milisegundos
contra 74,227 milisegundos).
Referencias
1. Janusz Kowalik, Tadeusz Puzniakowski: Using OpenCL: programming massively parallel computers. Khronos
Group, 2012.
2. Munshi, Aaftab: The OpenCL Specifications. Khronos Group, 2012.
3. Rosenberg, Ofer: OpenCL Overview. Khronos Group, 2011.
4. Ryoji Tsuchiyama, Takuro Iizuka Akihiro Asahara Jeongdo Son Satoshi Miki, Takashi Nakamura: The OpenCL
Programming Book. Fixstars, 2012.
87