Está en la página 1de 87

Publicaciones del Departamento de Matemticas

Series en Ciencias de la Computacin

N.o 6 (2015)

Tutorial para OpenCL en


ANSI C, Java y C#
Octavio Israel Rentera Vidales y Juan
Carlos Cuevas Tello

Universidad de Sonora
Divisin de Ciencias Exactas y Naturales

Publicaciones del Departamento de Matematicas


Series en Ciencias de la Computacion: n. 6, 2015.

Octavio Israel Rentera Vidales y Juan Carlos Cuevas Tello

Tutorial para OpenCL en ANSI C, Java y


C#

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.

Mi primer programa OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


3.1. API OCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2. Configuraci
on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3. Creando el c
odigo OCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4. Creando el c
odigo anfitri
on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.1. Inicializando OCL y compilando el codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.2. El Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.3. Creaci
on de los vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.4. Escritura en la memoria del dispositivo OCL . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.5. Ejecutando el Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.6. Recuperaci
on de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

4.2.1. Descargas e Instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


4.2.2. Configurando NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.3. HelloJOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3. C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.2. Configurando MonoDevelop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.3. HelloSharpOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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.

Caso de estudio: fractal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


6.1. Introducci
on a los fractales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2. Algoritmo de tiempo de escape . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3. Escribiendo el c
odigo OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3.1. image2d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3.2. Definici
on del Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4. Escribiendo el c
odigo anfitrion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4.1. FractalCPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4.2. FractalOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4.3. Main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4.4. Los resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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.

Contenido del tutorial

Este tutorial pretende ser lo m


as sencillo posible, y los objetivos son: i) explicar de manera
general como funciona un programa OCL, ii) justificar la computacion paralela y iii) crear una
aplicaci
on capaz de ejecutar este tipo de codigo en algunos de los lenguajes de programacion
m
as populares (ANSI C, Java y C#). Los ejemplos que se mostraran en este tutorial se pueden
descargar de un sitio Web3 .
En este tutorial se cubrir
an algunos topicos como:
1. Una vista general de OCL.
2. Como configurar algunos lenguajes de programacion (ANSI C, Java y C#) para poder usar
OCL en Linux y Windows.
3. Inicializaci
on de los dispositivos OCL.
4. Manejo de bufferes (escribir y recuperar datos del dispositivo OCL).
5. Programaci
on b
asica de un Kernel.
6. Ejecuci
on del Kernel.
7. El uso de workers (hilos).
1

http://www.khronos.org/opencl/

http://en.wikipedia.org/wiki/Embedded_system
http://infocomp.ingenieria.uaslp.mx/opencl

1 Introducci
on

8. Caso de estudio, Fractal.

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.

Surgimiento de OCL y estado del arte

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.

Estructura del Tutorial

En el captulo 2 se comienza con un ejemplo en OCL usando C# como lenguaje de programaci


on, y se resalta las ventajas de la programacion en paralelo con un ejemplo de suma de vectores.
En el captulo 3 se explican a detalle todos los elementos a considerar al programar en OCL, se
hace con un ejemplo con Visual C#. El captulo 4 cubre OCL en Linux con los lenguajes ANSI
C, C# y Java, y el captulo 5 OCL con Windows con mismos tres lenguajes de programacion.
El captulo 6 presenta un caso de estudio, la programacion de fractales en paralelo con OCL. Al
final se agregan algunos apendices con el codigo fuente completo de los ejemplos descritos en los
captulos anteriores.
4
5
6
7
8

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.

Una vista general de OpenCL

Figura 2.1 Esquema simplificado

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.

Crear un programa est


andar (escrito en C# en este caso).
Crear los datos que se desean procesar.
Inicializar los dispositivos OCL.
Utilizar la API de OCL para transferir estos datos a estos dispositivos.
Utilizar la API de OCL para enviar los comandos para la ejecucion del codigo en el dispositivo.
Recuperar todos los datos procesados.

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

Por que es tan u


til OCL? La respuesta es simple, por que puede hacer procesamiento en
paralelo. Consideremos el siguiente ejemplo: sumar dos vectores de tama
no n.
C
odigo C#
int n = 1000;
float [] = new float v1 [n];
float [] = new float v2 [n];
for (int i = 0; i <n; i + +)
{
v1 [i] = v1 [i] + v2 [i];
}
Que sucede en el ciclo interior? El programa calcula v1[0], entonces v1[1] luego v1[2] y
as sucesivamente hasta v1[n-1]. Ahora hay que comparar el codigo con la version OCL.
C
odigo OpenCL:
__kernel void vector_add(__global float *v1, __global float *v2)
{
int i = get_global_id(0);
v1[i] = v1[i] + v2[i];
}
Se crea una instancia de la funcion vector_add por cada worker definido.
Pseudoc
odigo en la m
aquina Anfitrion:
1. Inicializar OCL
2. Crear v1, v2 y v3
3. Copia v1, v2 y v3 en la memoria del dispositivo OCL

2.4 El procesamiento en paralelo

11

4. Establecer v1 y v2 como argumentos de la funcion vector_add


5. Definir que habr
a 1000 workers para ejecutar la funcion vector_add
6. Leer la informaci
on de v3
Como se puede ver, con OCL podemos tener muchos workers encargandose de un peque
na
parte del trabajo en lugar de un solo worker haciendo todo el trabajo. Las 1000 sumas se ejecutan
al mismo tiempo, donde cada worker se encarga de realizar solo una suma.

Captulo 3

Mi primer programa OpenCL

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

3 Mi primer programa OpenCL

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:

Ahora se requiere de las bibliotecas necesarias, en el caso de C# se usa la biblioteca


OpenCLTemplate 1 , es necesario descargarla y descomprimirla en alg
un directorio de facil acceso. Una vez descomprimidos, buscar los archivos cloo.dll y OpenCLTemplate.dll y agregar
las referencias al proyecto.
OpenCLTemplate tiene algunas dependencias como la biblioteca Cloo por lo que tambien se
deben incluir.

http://www.cmsoft.com.br/download/OpenCLTemplate.zip

3.3 Creando el c
odigo OCL

15

Finalmente agrega la referencia de la biblioteca en el area de codigo using OpenCLTemplate;.

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

3 Mi primer programa OpenCL

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

Una vez que se tiene el c


odigo fuente en OCL entonces se crean el codigo del anfitrion que
iniciar
a los dispositivos OCL, compilar el codigo OCL, transferir los datos necesarios para el
dispositivo, ejecutar el Kernel y recuperar los datos procesados.

3.4.1.

Inicializando OCL y compilando el c


odigo

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.

// Inicializar las plataformas y dispositivos OCL disponibles


CLCalc.InitCL();
// Se compila el c
odigo fuente OCL escrito anteriormente
CLCalc.Program.Compile(new string[] { sum });
OpenCLTemplate establece la primera GPU como el dispositivo predeterminado para ejecutar
comandos OCL. Si se desea seleccionar un dispositivo diferente, se debe establecer en la variable
CLCalc.Program.DefaultCQ al n
umero del dispositivo que se desea utilizar.

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

CLCalc.Program.Kernel VectorSum = new CLCalc.Program.Kernel("vector_add");

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.

/* Se desea sumar 1000 n


umeros */
int n = 1000;
// Crear vectores con 1000 n
umeros
float[] v1 = new float[n], v2 = new float[n], v3 = new float[n];
// Llenado
for (int i
{
v1 [i] =
v2 [i] =
}

3.4.4.

de los vectores v1 y v2
= 0; i < n; i++)
(float) i / 10;
- (float) i / 9;

Escritura en la memoria del dispositivo OCL

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 Mi primer programa OpenCL

3.4.5.

Ejecutando el Kernel

Para llamar a una funci


on de OCL es necesario especificar los parametros y cuantos workers
se desean usar. Siempre hay que tener en mente que va a hacer cada worker y de acuerdo a esto,
saber cu
antos workers se necesitar
an.
En este caso, cada worker suma el componente i de varV1 y varV2 (hay que recordar que OCL
se ejecuta en el dispositivo, lo que significa que la memoria a la que accede es la del dispositivo).
Esto significa que necesitamos a tantos workers como elementos de varV1, varV2 y varV3.
El n
umero de workers se puede especificar en m
ultiples dimensiones, es decir, una serie de
sub-workers (el worker i tiene j sub-workers). En nuestro caso solo necesitamos una dimension.
Todava hay que decirle al Kernel VectorSum que sus argumentos son varV1, varV2 y varV3
y que va a necesitar un worker para cada elemento de estos vectores.
// Argumentos del Kernel VectorSum
CLCalc.Program.Variable [] Args = new CLCalc.Program.Variable [] {varV1, varV2, varV3};
// Se necesitan "n" Workers, uno por cada elemento de los vectores (en este caso, 1000)
int[] workers = new int[1] { n };
// Ejecucion el Kernel
VectorSum.Execute (Args, workers);

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

En la figura anterior se muestra el resultado de la suma de vectores, v3 = v1 + v2.

19

Captulo 4

OpenCL en Linux

En el captulo anterior se mostr


o la programacion de OpenCL sobre Windows, con el objetivo
de explicar los conceptos b
asicos de OCL, en este captulo se muestra como crear una aplicacion
OCL sobre Linux utilizando tres lenguajes de programacion diferentes: ANSI C, Java y C#. Por
conveniencia y para reutilizar c
odigo de programacion, el codigo OCL se guarda en un archivo
independiente llamado example.cl (un archivo de texto).

4.1.

ANSI C

Para crear el HelloOCL en ANSI C necesitaremos de algunas herramientas. Por ejemplo, se


requiere un IDE para programar en C en Linux; se recomienda un IDE multi-plataforma llamado
Code::Blocks1 , es libre y gratuito y soporta tanto Windows, Linux y MacOS. La instalacion
de este IDE depende de la distribucion de Linux a usar, para este ejemplo se uso la distribucion
Sabayon Linux2 .

4.1.1.

Descargas e instalaciones

En la consola se inicia sesi


on como super usuario con el comando su.
Buscar Code::Blocks con la herramienta equo para obtener el nombre exacto del paquete a
instalar:
# equo search codeblocks
Se obtiene un resultado como este:
>>
>>

@@ 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:

En la siguiente ventana, seleccionamos Console Application y presionamos el boton Go:

24

4 OpenCL en Linux

Ahora nos pide elegir un lenguaje, en este caso es C:

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

En la ventana emergente se selecciona la pesta


na de Search Directories, en esta parte, se
selecciona la pesta
na Compiler, aqu se presiona el boton Add para agregar la ruta de los
encabezados de OCL.

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:

//"CL/cl.h" para Linux y "CL\cl.h" para Windows

4.1 ANSI C

29

#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 con los datos y se inicializan con valores:
float *V1 = (float*)malloc(sizeof(float)*VECTOR_SIZE);
float *V2 = (float*)malloc(sizeof(float)*VECTOR_SIZE);
for(i = 0; i < VECTOR_SIZE; i++)
{
V1[i] = (float)i / 10;
V2[i] = -(float)i / 9;
}
Se carga el archivo con el c
odigo fuente OCL en un buffer temporal:
FILE *fp;
char *source_str;
size_t source_size;
fp = fopen("example.cl", "r");
if (!fp)
{
fprintf(stderr, "Failed to load kernel.\n");
exit(1);
}
source_str = (char*)malloc(MAX_SOURCE_SIZE);
source_size = fread( source_str, 1, MAX_SOURCE_SIZE, fp);
fclose( fp );
Se obtienen los dispositivos disponibles en el sistema:
cl_platform_id platform_id = NULL;
cl_device_id device_id = NULL;
cl_uint ret_num_devices;
cl_uint ret_num_platforms;
cl_int ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
ret = clGetDeviceIDs( platform_id, CL_DEVICE_TYPE_ALL, 1, &device_id,
&ret_num_devices);
Se crea el Contexto para el Dispositivo a usar:
cl_context context = clCreateContext( NULL, 1, &device_id, NULL, NULL, &ret);

30

4 OpenCL en Linux

Se crean los comandos de ejecuci


on:
cl_command_queue command = clCreateCommandQueue(context, device_id, 0, &ret);
Se crean los buffers en el dispositivo que albergan los datos que se han creado:
cl_mem v1_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &ret);
cl_mem v2_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &ret);
cl_mem v3_mem_obj = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &ret);
Se copian los datos creados en la maquina local al dispositivo OCL:
ret = clEnqueueWriteBuffer(command,
VECTOR_SIZE * sizeof(float), V1, 0,
ret = clEnqueueWriteBuffer(command,
VECTOR_SIZE * sizeof(float), V2, 0,

v1_mem_obj, CL_TRUE, 0,
NULL, NULL);
v2_mem_obj, CL_TRUE, 0,
NULL, NULL);

Se crea el programa que ejecuta el dispositivo OCL con el codigo fuente:


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 pasan 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);

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

>>
>>
>>
>>
>>
>>
>>
>>
>>

@@ Paquete: dev-java/netbeans-ide-7.2-r1 rama: 5, [sabayon-weekly]


Disponible:
versi
on: 7.2-r1 ~ tag: NoTag ~ revisi
on: 0
Instalado:
versi
on: No instalado ~ tag: N/D ~ revisi
on: N/D
Bloque:
7.2
P
agina:
http://netbeans.org/projects/ide
Descripci
on:
Netbeans IDE Cluster
Licencia:
CDDL GPL-2-with-linking-exception
Palabras clave: netbeans-ide
Encontradas:
3 entries

Ahora solo basta instalar el paquete deseado:


# equo install dev-java/netbeans-ide-7.1.2
El gestor de paquetes autom
aticamente busca las dependencias del paquete a instalar, por lo que
se descargaran e instalaran los archivos necesarios para compilar y ejecutar programas en java.
Para continuar se necesitan las libreras que nos permitan usar OpenCL en Java, como se
menciono anteriormente se usara la librera JOCL de JogAmp.org7 , en este caso solo basta
bajar el comprimido y descomprimirlo en alg
un directorio de facil acceso.

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

Escoger Java Application, y oprimir Siguiente:

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.

En la siguiente ventana, se oprime el boton Crear. En la ventana emergente se ingresa el


nombre de la librera, en este caso se llamara JOCL.

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

buscar la carpeta donde se descomprimio la librera, la carpeta se llama jogamp-all-platforms


y en su interior esta la carpeta jar que posee las archivos que se necesitan: Aqu se escogen las

38

4 OpenCL en Linux

libreras glugen-rt.jar y jocl,jar, se seleccionan ambos archivos y se agregan. Finalmente se


selecciona la librera que acabamos de crear y se agrega al proyecto:

Ahora la librera aparecer


a en el proyecto:

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

kernel.putArgs(bufferv1, bufferv2, bufferv3);


Ahora se escriben los datos que poseen los buffers al dispositivo OCL:
command.putWriteBuffer(bufferv1, false);
ommand.putWriteBuffer(bufferv2, false);
Finalmente, se ejecuta el Kernel:
command.put1DRangeKernel(kernel, 0, VECTOR_SIZE, 1);
En esta funci
on pasamos el Kernel de nuestro programa, as como las dimensiones de workers
que queremos usar, cada worker es un hilo de ejecucion en el dispositivo, como nuestro ejemplo
es unidimensional solo requerimos de VECTOR SIZE workers (uno por cada celda de los
buffers), cada uno de ellos solo posee 1 sub-hilo de ejecucion.
Cuando el programa termine, podemos leer los resultados, para ello extraemos los datos del
dispositivo y se escriben en el buffer correspondiente:
command.putReadBuffer(bufferv3, true);
Finalmente se imprimen algunos de los resultados obtenidos:
for(int i = 0; i < 100; i++){
out.println( bufferv1.getBuffer().get(i)
+ " + " + bufferv2.getBuffer().get(i)
+ " = " + bufferv3.getBuffer().get() );
}
Si no hubo problemas, veremos los resultados en la consola de NetBeans:

42

4.3.

4 OpenCL en Linux

C#

.NET es un framework de Microsoft que permite un rapido y facil desarrollo de aplicaciones.


Es propia del sistema operativo Windows y viene de forma nativa desde Windows Vista. En
este punto puede parecer extra
no usar este framework en Linux, pero existe una implementacion
multi plataforma de .Net llamado Mono.
Para realizar nuestro HelloOCL necesitamos de Mono instalado en la distribucion de Linux
a usar, tambien es necesario alg
un IDE que nos permita utilizar el lenguaje C#. Mono posee el
IDE MonoDevelop que nos permitira crear proyectos .Net en Linux.

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

# equo search monodevelop


Esto nos arrojara un resultado como este:
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>

@@ 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

En este caso, el paquete a instalar es dev-util/monodevelop-2.8.5.1, al iniciar la instalacion


equo buscara todas las dependencias necesarias, por lo que solo se necesita instalar el paquete:
# equo install dev-util/monodevelop-2.8.5.1
Tambien requeriremos de una librera que nos permita utilizar OpenCL en C#, para este caso
usaremos la librera OpenCLTemplate 8 . El archivo a descargar hay que descomprimirlo en alg
un
directorio de f
acil acceso.

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:

En la ventana siguiente, elegimos espacio de Trabajo e ingresamos un nombre:


8

http://www.cmsoft.com.br/download/OpenCLTemplate.zip

4.3 C#

45

Ahora oprimimos con el bot


on derecho del mouse sobre nuestro espacio de trabajo, seleccionamos A
nadir y de ah Nueva Solucion:

En la siguiente ventana, seleccionamos Proyecto de consola C# e ingresamos el mismo nombre del espacio de trabajo:

46

4 OpenCL en Linux

La siguiente ventana la dejamos con las opciones por defecto:

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

De la ventana emergente, seleccionamos la pesta


na Net Assembly, aqu buscamos el directorio donde descomprimimos la librera OpenCLTemplate. Elegimos los archivos Cloo.dll y
OpenCLTemplate.dll. Los agregamos al proyecto y Oprimimos Aceptar:

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

Procedemos al main, primero creamos e inicializamos los vectores de datos:


float[] v1 = new float[VECTOR_SIZE];
float[] v2 = new float[VECTOR_SIZE];

48

4 OpenCL en Linux

float[] v3 = new float[VECTOR_SIZE];


for(int i = 0; i < VECTOR_SIZE; i++)
{
v1[i] = (float)i / 10;
v2[i] = -(float)i / 9;
}
Inicializar el dispositivo OpenCL disponible en el sistema, por defecto, OpenCLTemplate inicializa el dispositivo GPU si esta disponible.
CLCalc.InitCL();
Ahora se carga el c
odigo OCL de archivo, es el mismo archivo fuente que se ha estado usando a
lo largo de este tutorial, para importarlo solo basta copiar el archivo example.cl a la carpeta
Debug del proyecto.
StreamReader read = new StreamReader("example.cl");
string source = read.ReadToEnd();
read.Close();
Compilar el codigo fuente de nuestro programa OCL:
CLCalc.Program.Compile(new string[]{source});
Creaci
on de la funci
on Kernel con el nombre de la funcion del codigo OCL.
CLCalc.Program.Kernel VectorAdd = new CLCalc.Program.Kernel("vector_add");
Crear las variables de Dispositivo que copiaran los vectores de la computadora anfitrion al dispositivo OCL.
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);
Pasar estas variables como los argumentos que usara el Kernel.
CLCalc.Program.Variable [] Args = new CLCalc.Program.Variable [] {varV1, varV2, varV3};
Ejecuci
on del Kernel con la cantidad de workers a usar, como en el resto de los ejemplos, usaremos
una dimensi
on de workers (un worker por elemento de los vectores).
int[] workers = new int[1] { VECTOR_SIZE };
VectorAdd.Execute (Args, workers);
Sigue recuperar los datos procesados del dispositivo OCL, para esto copiamos la variable del
Kernel varV3 a la variable local v3.
varV3.ReadFromDeviceTo(v3);
Finalmente solo queda imprimir los datos.

4.3 C#

49

for(int i = 0; i < 100; i++)


{
Console.WriteLine(String.Format("{0} + {1} = {2}", v1[i], v2[i], v3[i]));
}
Si todo salio bien, obtendremos un resultado como este:

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

En la siguiente ventana, seleccionamos Console Application y presionamos el boton Go:

Ahora nos pide elegir un lenguaje, en nuestro caso se usara C:

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:

En la ventana emergente se selecciona la pesta


na de Search Directories, en esta parte,
seleccionamos la pesta
na Compiler, aqu presionamos el boton Add para agregar la ruta de
los encabezados de OpenCL.

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>

//"CL/cl.h" para Linux y "CL\cl.h" para Windows

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

float *V1 = (float*)malloc(sizeof(float)*VECTOR_SIZE);


float *V2 = (float*)malloc(sizeof(float)*VECTOR_SIZE);
for(i = 0; i < VECTOR_SIZE; i++)
{
V1[i] = (float)i / 10;
V2[i] = -(float)i / 9;
}
Se carga el archivo con el c
odigo fuente OCL en un buffer temporal:
FILE *fp;
char *source_str;
size_t source_size;
fp = fopen("example.cl", "r");
if (!fp)
{
fprintf(stderr, "Failed to load kernel.\n");
exit(1);
}
source_str = (char*)malloc(MAX_SOURCE_SIZE);
source_size = fread( source_str, 1, MAX_SOURCE_SIZE, fp);
fclose( fp );
Se obtienen los dispositivos disponibles en el sistema:
cl_platform_id platform_id = NULL;
cl_device_id device_id = NULL;
cl_uint ret_num_devices;
cl_uint ret_num_platforms;
cl_int ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
ret = clGetDeviceIDs( platform_id, CL_DEVICE_TYPE_ALL, 1, &device_id,
&ret_num_devices);
Se crea el Contexto para el Dispositivo a usar:
cl_context context = clCreateContext( NULL, 1, &device_id, NULL, NULL, &ret);
Se crean los comandos de ejecuci
on:

60

5 OpenCL en Windows

cl_command_queue command = clCreateCommandQueue(context, device_id, 0, &ret);


Se crean los buffers en el dispositivo que albergaran los datos que se han creado:
cl_mem v1_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &ret);
cl_mem v2_mem_obj = clCreateBuffer(context, CL_MEM_READ_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &ret);
cl_mem v3_mem_obj = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
VECTOR_SIZE * sizeof(float), NULL, &ret);
Se copian los datos creados en m
aquina local al dispositivo OCL:
ret = clEnqueueWriteBuffer(command,
VECTOR_SIZE * sizeof(float), V1, 0,
ret = clEnqueueWriteBuffer(command,
VECTOR_SIZE * sizeof(float), V2, 0,

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

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 reservo:
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);

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

Primero se requiere tener instalado el Kit de desarrollo de Java SE (JDK) 64 o superior


para Windows, as como NetBeans5 para Windows. Se puede descargar de la pagina oficial de
NetBeans, recuerda descargar para la version de Windows a utilizar, hay que descargar la version
NetBeans Platform SDK Java SE (alrededor de 70MB).
Primero se instala el Kit de Java.
4
5

http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
http://netbeans.org/downloads/index.html

5.2 Java

63

Despues se procede a instalar NetBeans, si el Kit de Java se instalo correctamente, el instalador


de NetBeans lo reconocer
a:

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:

Escoger Java Application, y oprimir Siguiente:


6

http://jogamp.org/deployment/jogamp-current/archive/jogamp-all-platforms.7z

5.2 Java

65

Asignar un nombre al proyecto y oprimir Terminar.


Una vez abierto el proyecto, se da clic con el boton derecho del raton sobre Bibliotecas y se
selecciona Agregar biblioteca.

En la siguiente ventana, se oprime el boton Crear. En la ventana emergente se ingresa el


nombre de la librera, en este caso se llamara JOCL.

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

Aqu se escogen las libreras glugen-rt.jar y jocl,jar, se seleccionan ambos archivos y se


agregan. Finalmente se selecciona la librera que acabamos de crear y se agrega al proyecto:

Ahora la librera aparecer


a en el proyecto:

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

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.
kernel.putArgs(bufferv1, bufferv2, bufferv3);
Ahora se escriben los datos que poseen los buffers al dispositivo OCL:
command.putWriteBuffer(bufferv1, false);
ommand.putWriteBuffer(bufferv2, false);
Finalmente, se ejecuta el Kernel:
command.put1DRangeKernel(kernel, 0, VECTOR_SIZE, 1);
En esta funci
on pasamos el Kernel de nuestro programa, as como las dimensiones de workers
que queremos usar, cada worker es un hilo de ejecucion en el dispositivo, como nuestro ejemplo
es unidimensional solo requerimos de VECTOR SIZE workers (uno por cada celda de los
buffers), cada uno de ellos solo posee 1 sub-hilo de ejecucion.
Cuando el programa termine, podemos leer los resultados, para ello extraemos los datos del
dispositivo y se escriben en el buffer correspondiente:
command.putReadBuffer(bufferv3, true);
Finalmente se imprimen algunos de los resultados obtenidos:
for(int i = 0; i < 100; i++){
out.println( bufferv1.getBuffer().get(i)
+ " + " + bufferv2.getBuffer().get(i)

5.2 Java

+ " = " + bufferv3.getBuffer().get() );


}
Si no hubo problemas, veremos los resultados en la consola de NetBeans:

71

Captulo 6

Caso de estudio: fractal

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

Un fractal es un objeto geometrico cuya estructura basica, fragmentada o irregular, se repite a


diferentes escalas. En otras palabras, es un objeto creado con replicas mas peque
nas de si mismo,
son figuras bastante demandantes a la hora de generarlas pues cada punto se calcula para saber
si pertenece a, lo que se denomina conjunto de Mandelbrot 1 o no (la parte negra del dibujo).
1

http://es.wikipedia.org/wiki/Conjunto_de_Mandelbrot

73

74

6 Caso de estudio: fractal

6.2.

Algoritmo de tiempo de escape

Los datos generado por la formula de un fractal no se pueden representar directamente en el


plano espacial (x y y), pues son funciones con n
umeros complejos y trabajan en planos complejos.
Para representar el fractal en un plano espacial, suele utilizarse un algoritmo llamado algoritmo
de tiempo de escape, se calcula un punto en la funcion del fractal y se le asigna un color,
los colores indican la velocidad con la que diverge (tiende al infinito, en modulo) la sucesion
correspondiente a dicho punto. Si aplicamos una serie de iteraciones por punto (siguiendo el
algoritmo), observaremos que algunos puntos llegaran a divergir despues de pocas iteraciones,
mientras que otros puntos necesitaran de mas iteraciones. Como no se pueden realizar iteraciones
infinitas, es necesario establecer un lmite y decidir que si los n primeros terminos de la sucesion
est
an acotados, entonces se considera que el punto pertenece al conjunto. Al aumentar el valor
de n se mejora la precisi
on de la imagen.
Algoritmo de tiempo de escape:
do{
Z = Z^2 + C
contador = contador + 1
if ( Z DIVERGE )
break
} while ( contador < N )

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

6 Caso de estudio: fractal

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

6 Caso de estudio: fractal

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 Caso de estudio: fractal

Nuestra estructura para n


umero complejos. Es cierto que existe la librera nativa para manipulaci
on de n
umero complejos en C#, pero la idea de este proyecto es ver el desempe
no de los
c
odigos escritos en OCL y en C#, por lo que es mas acorde comparar el desempe
no con las
mismas rutinas escritas para cada lenguaje.
struct Complex
{
public float Real;
public float Img;
};
El constructor de la clase, aqu se inicializa el bitmap donde se dibujara el fractal, as como la
creaci
on de este por medio de 2 ciclos, recibira como parametros el tama
no del fractal, as como
su parte real e imaginaria.
public FractalCPU(Size size, float real, float img)
{
buffer = new Bitmap(size.Width, size.Height);
for (int i = 0; i < size.Width; i++)
for (int j = 0; j < size.Height; j++)
{
this.Fractal(i, j, real, img, size.Width, size.Height);
}
}
Esta funci
on regresara el bitmap con el fractal. Se puede omitir haciendo publica la variable
del bitmap.
public Bitmap GetBmp()
{
return buffer;
}
Las funciones para las operaciones basicas con n
umeros complejos:
Complex Sum(Complex a, Complex b)
{
Complex aux;
aux.Real = a.Real + b.Real;
aux.Img = a.Img + b.Img;
return aux;
}
Complex Mult(Complex a, 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;

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 Caso de estudio: fractal

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

= 4000; // ancho de la imagen


= 4000; // alto de la imagen
real = 0.285f; // Parte real del fractal
imag = 0.01f; // parte imaginaria del fractal

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

6 Caso de estudio: fractal

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

La prueba que se realiz


o fue en una computadora con un procesador AMD E-300 4 doble
n
ucleo corriendo a 1.3 Ghz y un chip de video AMD Radeon HD 6310 5 . Estos son los resultados
que se obtuvieron:
4
5

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

Figura 6.1 Salida de la Consola.

Figura 6.2 Fractal resultante.

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

6 Caso de estudio: fractal

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

También podría gustarte