opencl tutorial

Upload: bastian-garrido

Post on 14-Jan-2016

48 views

Category:

Documents


1 download

DESCRIPTION

TUTORIAL OPENCL

TRANSCRIPT

  • Publicaciones del Departamento de Matemticas

    Series en Ciencias de la Computacin

    Universidad de SonoraDivisin de Ciencias Exactas y Naturales

    N.o 6 (2015)

    Tutorial para OpenCL enANSI C, Java y C#

    Octavio Israel Rentera Vidales y JuanCarlos Cuevas Tello

  • 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 yC#

    Universidad de SonoraDepartamento de Matematicas

  • Indice general

    1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.1. Que es OpenCL/OCL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2. Contenido del tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.3. Que se requiere para programar en OCL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.4. Surgimiento de OCL y estado del arte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.5. Estructura del Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

    2. Conociendo OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.1. Una vista general de OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2. Como funciona? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.3. Codigo de ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.4. El procesamiento en paralelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

    3. Mi primer programa OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.1. API OCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.2. Configuracion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.3. Creando el codigo OCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.4. Creando el codigo anfitrion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

    3.4.1. Inicializando OCL y compilando el codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.4.2. El Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.4.3. Creacion de los vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173.4.4. Escritura en la memoria del dispositivo OCL . . . . . . . . . . . . . . . . . . . . . . . . . . 173.4.5. Ejecutando el Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183.4.6. Recuperacion de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

    4. OpenCL en Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214.1. ANSI C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

    4.1.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214.1.2. Configurando Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234.1.3. HelloOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

    4.2. Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

    3

  • 4 Indice general

    4.2.1. Descargas e Instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334.2.2. Configurando NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344.2.3. HelloJOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

    4.3. C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.3.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.3.2. Configurando MonoDevelop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444.3.3. HelloSharpOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

    5. OpenCL en Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.1. ANSI C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

    5.1.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.1.2. Configurando Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525.1.3. HelloOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

    5.2. Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625.2.1. Descargas e instalaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625.2.2. Configurando NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645.2.3. HelloJOCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

    6. Caso de estudio: fractal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 736.1. Introduccion a los fractales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 736.2. Algoritmo de tiempo de escape . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 746.3. Escribiendo el codigo OpenCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

    6.3.1. image2d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 776.3.2. Definicion del Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

    6.4. Escribiendo el codigo anfitrion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796.4.1. FractalCPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796.4.2. FractalOCL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 826.4.3. Main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 836.4.4. Los resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

    Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

  • Captulo 1

    Introduccion

    1.1. Que es OpenCL/OCL?

    OpenCL1 (Open Computing Language) es el primer estandar abierto, gratuito y multi-plataforma para la programacion paralela de procesadores modernos usados en computadoraspersonales, servidores, dispositivos portatiles y embebidos 2 [2]. Otro acronimo para OpenCL esOCL, as que usaremos ambos para referirnos al mismo lenguaje. OCL mejora en gran medida lavelocidad y capacidad de respuesta para una amplia gama de aplicaciones en numerosos sectoresdel mercado de juegos y entretenimiento as como software cientfico y medico.

    1.2. Contenido del tutorial

    Este tutorial pretende ser lo mas sencillo posible, y los objetivos son: i) explicar de manerageneral como funciona un programa OCL, ii) justificar la computacion paralela y iii) crear unaaplicacion capaz de ejecutar este tipo de codigo en algunos de los lenguajes de programacionmas populares (ANSI C, Java y C#). Los ejemplos que se mostraran en este tutorial se puedendescargar de un sitio Web3.

    En este tutorial se cubriran 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. Inicializacion de los dispositivos OCL.4. Manejo de bufferes (escribir y recuperar datos del dispositivo OCL).5. Programacion basica de un Kernel.6. Ejecucion del Kernel.7. El uso de workers (hilos).

    1 http://www.khronos.org/opencl/2 http://en.wikipedia.org/wiki/Embedded_system3 http://infocomp.ingenieria.uaslp.mx/opencl

    5

  • 6 1 Introduccion

    8. Caso de estudio, Fractal.

    1.3. Que se requiere para programar en OCL?

    Para programar en OCL, se necesita un dispositivo que lo soporte. Existe una lista oficialproporcionada por Khronos Group donde se detallan los dispositivos que oficialmente soportanel estandar OCL 4. Tambien es necesario poseer los controladores del dispositivo, esto dependedel 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 versionen diciembre de 2008 [3]. Al ser un lenguaje relativamente nuevo, hay pocos libros relacionadoscon OCL, cada uno con diferente enfoque. Por ejemplo, Tsuchiyama et al. hace enfasis en latransformada de Fourier y OCL con el lenguaje C [4]. Por otro lado, Kowalik et al. cubre aspectosde la programacion en paralelo con MPI y CUDA, y se enfoca mas a OCL con el lenguaje C++[1]. Finalmente, Gaster et al. cubre unicamente OCL con C/C++.

    Hay varios tutoriales relacionados en OCL (la mayora en ingles) que se pueden consultar en lapagina del grupo Khronos8, sin embargo ninguno de ellos cubre al mismo tiempo todos lenguajesde programacion que se presentan en este tutorial, y sobre los sistemas operativos Windows yLinux.

    1.5. Estructura del Tutorial

    En el captulo 2 se comienza con un ejemplo en OCL usando C# como lenguaje de programa-cion, 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, sehace con un ejemplo con Visual C#. El captulo 4 cubre OCL en Linux con los lenguajes ANSIC, 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. Alfinal se agregan algunos apendices con el codigo fuente completo de los ejemplos descritos en loscaptulos anteriores.

    4 http://www.khronos.org/conformance/adopters/conformant-products/5 http://support.amd.com/us/gpudownload/Pages/index.aspx6 http://www.nvidia.com/Download/index.aspx?lang=en-us7 http://downloadcenter.intel.com/8 http://www.khronos.org/opencl/resources

  • Captulo 2

    Conociendo OpenCL

    2.1. Una vista general de OpenCL

    Figura 2.1 Esquema simplificado

    7

  • 8 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, envacomandos de ejecucion y recibe datos procesados de los dispositivos.

    2.2. Como funciona?

    El Anfitrion ejecuta el codigo que se ha escrito en C#, C++ o cualquier lenguaje de pro-gramacion soportado por el sistema. Este mismo programa se ejecuta en el procesador, siendogestionado por el sistema operativo y ejecutado localmente. Los dispositivos que se encargan deejecutar codigo OCL, basado en el lenguaje C99 [3]. Hay un compilador especfico OCL para laCPU, la GPU y de las tarjetas aceleradoras (NVidia Tesla 1, AMD FirePro 2, Intel Xeon Phi 3

    por mencionar algunas).La pregunta es como 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 yrecibir informacion y ejecutar estos programas en el dispositivo elegido.

    Basicamente as es como funciona:

    Codigo OCL:

    1. Crear el codigo que desee ejecutar utilizando el lenguaje C99.

    Codigo Anfitrion:

    1. Crear un programa estandar (escrito en C# en este caso).2. Crear los datos que se desean procesar.3. Inicializar los dispositivos OCL.4. Utilizar la API de OCL para transferir estos datos a estos dispositivos.5. Utilizar la API de OCL para enviar los comandos para la ejecucion del codigo en el dispositivo.6. Recuperar todos los datos procesados.

    2.3. Codigo de ejemplo

    El siguiente codigo nos servira para ilustrar los pasos anteriores. Por ahora no hay que preo-cuparse por la comprension de la sintaxis, mas adelante se vera a detalle.

    1 http://www.nvidia.com/object/tesla-supercomputing-solutions.html2 http://www.amd.com/us/products/workstation/graphics/ati-firepro-3d/Pages/ati-firepro-3d.aspx3 http://www.intel.com/content/www/us/en/high-performance-computing/high-performance-xeon-phi-coprocessor-brief.

    html

  • 2.3 Codigo de ejemplo 9

    //Biblioteca OpenCL

    using OpenCLTemplate;

    public static void Main (string[] args)

    {

    //Inicializacion los dispositivos CL disponibles

    CLCalc.InitCL();

    //Creacion de las variables que van a pasarse al dispositivo

    //Estas variables son memoria local, es decir, memoria del anfitrion

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

    //ejecucion.

    CLCalc.Program.Compile(new string[] { code });

    //Cracion del kernel, es quien se encargara de ejecutar el codigo OCL.

    //Como se puede ver, recibe como parametro 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 numero de "Workers" que ejecutaran 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 funcion y el numero de "workers" que se definieron

    sum.Execute(args, max);

    //Al terminar la ejecucion, 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 util OCL? La respuesta es simple, por que puede hacer procesamiento enparalelo. Consideremos el siguiente ejemplo: sumar dos vectores de tamano n.

    Codigo C#

    int n = 1000;

    float [] = new float v1 [n];

    float [] = new float v2 [n];

    for (int i = 0; i

  • 2.4 El procesamiento en paralelo 11

    4. Establecer v1 y v2 como argumentos de la funcion vector_add5. Definir que habra 1000 workers para ejecutar la funcion vector_add6. Leer la informacion de v3

    Como se puede ver, con OCL podemos tener muchos workers encargandose de un pequenaparte del trabajo en lugar de un solo worker haciendo todo el trabajo. Las 1000 sumas se ejecutanal 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 ambientedonde se ejecutara el programa, el codigo OCL, el codigo anfitrion, el Kernel, la escritura en lamemoria de dispositivos OCL, la ejecucion del Kernel y la recuperacion de datos. El ejemplo esun programa escrito en C# que demostrara los topicos vistos hasta el momento.

    3.1. API OCL

    3.2. Configuracion

    Primero hay que crear una nueva aplicacion para consola en Visual C#, en este ejemplo seusara la version 2008.

    13

  • 14 3 Mi primer programa OpenCL

    A continuacion se debe incluir las referencias a las bibliotecas Cloo y OpenCLTemplate.Para esto se utiliza el menu Proyecto -> Agregar referencia para abrir las referencias:

    Ahora se requiere de las bibliotecas necesarias, en el caso de C# se usa la bibliotecaOpenCLTemplate 1, es necesario descargarla y descomprimirla en algun directorio de facil ac-ceso. Una vez descomprimidos, buscar los archivos cloo.dll y OpenCLTemplate.dll y agregarlas referencias al proyecto.OpenCLTemplate tiene algunas dependencias como la biblioteca Cloo por lo que tambien sedeben incluir.

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

  • 3.3 Creando el codigo OCL 15

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

    3.3. Creando el codigo OCL

    Aqu se muestra un programa simple que suma dos vectores y almacena el resultado en untercer vector. Primero se incializa una variable de tipo String con el codigo OCL que se encar-gara 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 cubriran algunos de los conceptos basicos de OCL-C99.

    1. Observe que el codigo fuente de OCL es una cadena que sera compilada por el dispositivoOCL.

    2. La etiqueta __kernel indica que esta funcion es un kernel, estas funciones son publicas yson las unicas que pueden invocarse a traves de la API OCL.

    3. La etiqueta __global indica que la memoria de las variables v1 y v2 es publica y el anfitrionpuede acceder a ellas.

  • 16 3 Mi primer programa OpenCL

    4. La funcion get_global_id(0) es una funcion OCL nativa que devuelve el identificadordel worker ejecutando esta funcion (desde 0 hasta el numero de workers establecidos -1). Enel codigo ejemplo, la tarea del worker i es sumar los componentes i de v1 y v2.

    3.4. Creando el codigo anfitrion

    Una vez que se tiene el codigo fuente en OCL entonces se crean el codigo del anfitrion queiniciara los dispositivos OCL, compilar el codigo OCL, transferir los datos necesarios para eldispositivo, ejecutar el Kernel y recuperar los datos procesados.

    3.4.1. Inicializando OCL y compilando el codigo

    Al inicializar OCL se deben identificar todos los dispositivos disponibles y crear las Colas decomandos que se utilizan para decirle a OCL en que dispositivo debe ejecutar el codigo. Todo estoesta encapsulado en la funcion de inicializacion InitCL. Despues de la compilacion, es necesarioel uso de la API de OCL para que el anfitrion tenga acceso al Kernel para que posteriormente,pueda ordenar la ejecucion del codigo.

    // Inicializar las plataformas y dispositivos OCL disponibles

    CLCalc.InitCL();

    // Se compila el codigo fuente OCL escrito anteriormente

    CLCalc.Program.Compile(new string[] { sum });

    OpenCLTemplate establece la primera GPU como el dispositivo predeterminado para ejecutarcomandos OCL. Si se desea seleccionar un dispositivo diferente, se debe establecer en la variableCLCalc.Program.DefaultCQ al numero 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 existenotras funciones que se pueden ejecutar en el (funciones nativas y funciones creadas por el usua-rio, se vera mas adelante), pero hay una ligera diferencia entre estas funciones y los Kernels: losKernels son puntos de entrada para el programa en el dispositivo. En otras palabras, los Kernelsson las unicas funciones que se pueden llamar desde la computadora anfitrion (host).

    // El Anfitrion obtiene acceso a la funcion Kernel "vector_add"

  • 3.4 Creando el codigo anfitrion 17

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

    3.4.3. Creacion de los vectores

    Hay que crear e inicializar los vectores que se van a sumar. Este codigo se realiza en la maquinaanfitrion.

    /* Se desea sumar 1000 numeros */

    int n = 1000;

    // Crear vectores con 1000 numeros

    float[] v1 = new float[n], v2 = new float[n], v3 = new float[n];

    // Llenado de los vectores v1 y v2

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

    {

    v1 [i] = (float) i / 10;

    v2 [i] = - (float) i / 9;

    }

    3.4.4. Escritura en la memoria del dispositivo OCL

    Primero hay que enviar los datos creados en la maquina anfitrion hacia la memoria del dispo-sitivo OCL, para esto se crean las variables varV1, varV2 y varV3 que copiaran los datos de v1y 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 serdiferentes memorias. La variable varV3, aunque no escribe datos al dispositivo (ya que estavariable es donde se guardaran los resultados), aun as debe reservar espacio en el dispositivo,para esto la API OCL debe saber el tipo y tamano del buffer a reservar.

  • 18 3 Mi primer programa OpenCL

    3.4.5. Ejecutando el Kernel

    Para llamar a una funcion de OCL es necesario especificar los parametros y cuantos workersse desean usar. Siempre hay que tener en mente que va a hacer cada worker y de acuerdo a esto,saber cuantos workers se necesitaran.

    En este caso, cada worker suma el componente i de varV1 y varV2 (hay que recordar que OCLse 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 numero de workers se puede especificar en multiples dimensiones, es decir, una serie desub-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 varV3y 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. Recuperacion de datos

    El ultimo paso consiste en recuperar la informacion procesada de la memoria del dispositivo.

    // Leer varV3 de la memoria del dispositivo a la memoria local del anfitrion, "v3"

    varV3.ReadFromDeviceTo(v3);

    // Imprimir los datos recuperados

    foreach (float f in v3)

    {

    Console.Write(f + ",");

    }

  • 3.4 Creando el codigo anfitrion 19

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

  • Captulo 4

    OpenCL en Linux

    En el captulo anterior se mostro la programacion de OpenCL sobre Windows, con el objetivode explicar los conceptos basicos de OCL, en este captulo se muestra como crear una aplicacionOCL sobre Linux utilizando tres lenguajes de programacion diferentes: ANSI C, Java y C#. Porconveniencia y para reutilizar codigo de programacion, el codigo OCL se guarda en un archivoindependiente llamado example.cl (un archivo de texto).

    4.1. ANSI C

    Para crear el HelloOCL en ANSI C necesitaremos de algunas herramientas. Por ejemplo, serequiere un IDE para programar en C en Linux; se recomienda un IDE multi-plataforma llamadoCode::Blocks1, es libre y gratuito y soporta tanto Windows, Linux y MacOS. La instalacionde este IDE depende de la distribucion de Linux a usar, para este ejemplo se uso la distribucionSabayon Linux2.

    4.1.1. Descargas e instalaciones

    En la consola se inicia sesion 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]

    1 http://www.codeblocks.org/2 http://www.sabayon.org/

    21

  • 22 4 OpenCL en Linux

    >> Disponible: version: 10.05-r1 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: 10.05-r1 ~ tag: NoTag ~ revision: 0

    >> Bloque: 0

    >> Pagina: http://www.codeblocks.org/

    >> Descripcion: 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 busqueda:

    # 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 comprobacion 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 aplicacion

    >>> Regenerating /etc/ld.so.cache...

    >>> Regenerating /etc/ld.so.cache...

    >> ## Limpiando: dev-util/codeblocks-10.05-r1

    >> @@ Instalacion completada.

    >> @@ No configuration files to update.

    Solo queda cerrar la sesion de super usuario:

    # exit

    Se puede iniciar Code::Blocks desde consola escribiendo codeblocks o buscandolo en el lan-zador 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 sonestandares y pueden obtenerse de la pagina de Kronos Group, por otro lado, las libreras dependendel fabricante del dispositivo OCL. Si ya se tienen instalados los controladores de video, entoncestanto los encabezado de OCL como las libreras ya se encuentran instalados en el sistema. Suelenestar 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 encabezadosy vendors, donde estan las carpetas de los fabricantes del dispositivo (tpicamente estan AMDy 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 hayque crear un nuevo proyecto haciendo clic en el menu File y de ah a New, seleccionandoProject:

    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 librerasOCL, 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 pestana de Search Directories, en esta parte, seselecciona la pestana Compiler, aqu se presiona el boton Add para agregar la ruta de losencabezados de OCL.

    Cabe senalar que la carpeta a seleccionar no es la carpeta CL, si no una carpeta ante-rior a esta dado que las cabeceras hacen referencia a los otros archivos por medio de la rutaCL/archivo.h, por esta razon se elige la carpeta include.

    Ahora sigue agregar la ruta de las libreras binarias, para esto se selecciona la pestana Linkersettings, aqu se presiona el boton Add y en la ventana buscamos el directorio donde estanlas 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 pestanaLinker 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 unpequeno 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 granmanera el uso de OCL, pero en este caso se trabaja con las funciones crudas de OCL (esconveniente 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 deldispositivo, copiar los datos al dispositivo, crear los comandos de ejecucion, definir los Workersa usar, ejecutar el programa en el dispositivo y recuperar los datos).

    Primero se definen las cabeceras a usar:

    #include

    #include

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

    Las constantes:

  • 4.1 ANSI C 29

    #define MAX_SOURCE_SIZE 512 // tama~no maximo del archivo con el codigo 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 codigo 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 ejecucion:

    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, v1_mem_obj, CL_TRUE, 0,

    VECTOR_SIZE * sizeof(float), V1, 0, NULL, NULL);

    ret = clEnqueueWriteBuffer(command, v2_mem_obj, CL_TRUE, 0,

    VECTOR_SIZE * sizeof(float), V2, 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 funcion 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

    Ejecucion 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 yescribir el codigo 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);

  • 32 4 OpenCL en Linux

    Finalmente solo hay que compilar y correr el programa, si todo sale bien se obtiene un resultadocomo 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 laoriginal de OpenCL. Las funciones se proporcionan como metodos estaticos, y la semantica deestos metodos se han mantenido coherentes con las funciones de la librera original, salvo laslimitaciones especficas del lenguaje de Java. Esta API proporciona una traduccion casi literalde 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 orientadaa objetos de OpenCL para Java. Esto simplifica su uso y puede ser mas natural y convenientepara la mayora de los programadores de Java. La librera tambien ofrece una interfaz de bajonivel que se genera mediante la librera GlueGen. Esta interfaz es similar a la API OpenCLoriginal, pero no esta destinada a ser utilizada por los clientes, si no mas bien sirve como base

    3 http://www.jocl.org/downloads/downloads.html4 http://jogamp.org/jocl/www/

  • 4.2 Java 33

    para la creacion de Wrappers orientados a objetos.

    JavaCL5 Esta librera tambien ofrece una abstraccion orientada a objetos de OpenCL paraJava. 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 noesta destinado a ser utilizado por los clientes.

    En este tutorial se utilizo la librera JOCL de JogAmp.org, por ser la mas simplificada yesta orientada a objetos.

    4.2.1. Descargas e Instalaciones

    Una de las ventajas de programar en Java, es que el codigo puede compilarse y correrseen cualquier sistema que posea la maquina virtual Java, por lo tanto es necesario que este lamaquina virtual instalada en Linux, as como las dependencias necesarias. Tambien es necesarioalgun IDE para programar, en este caso se usara NetBeans. La manera mas sencilla de tener lonecesario es instalar NetBeans directamente y dejar que el manejador de paquetes calcule todaslas 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 NetBeansfue el siguiente: Abrir la consola del sistema e iniciar sesion como super usuario con el comandosu. Ahora se utiliza el gestor de paquetes de la distribucion llamado equo, primero hay quebuscar NetBeans para saber el nombre exacto del paquete a instalar:

    # equo search netbeans-ide

    Esta busqueda arroja un resultado como el siguiente:

    >> @@ Buscando...

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

    >> Disponible: version: 7.0.1 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: No instalado ~ tag: N/D ~ revision: N/D

    >> Bloque: 7.0

    >> Pagina: http://netbeans.org/projects/ide

    >> Descripcion: Netbeans IDE Cluster

    >> Licencia: CDDL GPL-2-with-linking-exception

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

    >> Disponible: version: 7.1.2 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: 7.1.2 ~ tag: NoTag ~ revision: 0

    >> Bloque: 7.1

    >> Pagina: http://netbeans.org/projects/ide

    >> Descripcion: Netbeans IDE Cluster

    >> Licencia: CDDL GPL-2-with-linking-exception

    5 http://code.google.com/p/javacl/6 http://www.sabayon.org/

  • 34 4 OpenCL en Linux

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

    >> Disponible: version: 7.2-r1 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: No instalado ~ tag: N/D ~ revision: N/D

    >> Bloque: 7.2

    >> Pagina: http://netbeans.org/projects/ide

    >> Descripcion: 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 automaticamente busca las dependencias del paquete a instalar, por lo quese 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 semenciono anteriormente se usara la librera JOCL de JogAmp.org7, en este caso solo bastabajar el comprimido y descomprimirlo en algun directorio de facil acceso.

    4.2.2. Configurando NetBeans

    Ya que se ha instalado todo el software necesario, se procede a configurar NetBeans; para ellocreamos un nuevo proyecto:

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

  • 4.2 Java 35

    Escoger Java Application, y oprimir Siguiente:

  • 36 4 OpenCL en Linux

    Asignar un nombre al proyecto y oprimir Terminar. Una vez abierto el proyecto, se da cliccon 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 elnombre 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 usarla 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-platformsy 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 seselecciona la librera que acabamos de crear y se agrega al proyecto:

    Ahora la librera aparecera en el proyecto:

  • 4.2 Java 39

    4.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;

    import com.jogamp.opencl.*;

    import java.io.File;

    import java.io.FileNotFoundException;

    import java.io.FileReader;

    import static java.lang.System.out;

    import java.nio.FloatBuffer;

    import java.util.Scanner;

    Ahora procedemos al main. Lo primero que hay que hacer es crear nuestro contexto para eldispositivo 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 tieneun arreglo SLI (mas de una tarjeta de video Nvidia) o Crossfire (mas de una tarjeta de videoAMD/ATI), se debe escoger uno de los dispositivos disponibles, en este caso, tomaremos el primerdispositivo disponible.

    Se carga el programa OCL desde archivo. A lo largo de este tutorial se ha estado usando elmismo archivo con el codigo OCL example.cl, solo basta agregarlo a la carpeta del proyectopara 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 codigo OCL en el contexto creado, para esto se pasa la cadena que contiene elcodigo del programa.

    CLProgram program = context.createProgram(source_str).build();

    Se crean los buffers que contendran los datos a procesar:

    CLBuffer bufferv1 = context.createFloatBuffer(VECTOR_SIZE, READ_ONLY);

    CLBuffer bufferv2 = context.createFloatBuffer(VECTOR_SIZE, READ_ONLY);

    CLBuffer 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 recordarque la cantidad de variables a pasar debe coincidir en numero y tipo de datos definidos en elKernel.

  • 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 funcion pasamos el Kernel de nuestro programa, as como las dimensiones de workersque queremos usar, cada worker es un hilo de ejecucion en el dispositivo, como nuestro ejemploes unidimensional solo requerimos de VECTOR SIZE workers (uno por cada celda de losbuffers), cada uno de ellos solo posee 1 sub-hilo de ejecucion.

    Cuando el programa termine, podemos leer los resultados, para ello extraemos los datos deldispositivo 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 OpenCL en Linux

    4.3. 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. Eneste punto puede parecer extrano usar este framework en Linux, pero existe una implementacionmulti plataforma de .Net llamado Mono.

    Para realizar nuestro HelloOCL necesitamos de Mono instalado en la distribucion de Linuxa usar, tambien es necesario algun IDE que nos permita utilizar el lenguaje C#. Mono posee elIDE MonoDevelop que nos permitira crear proyectos .Net en Linux.

    4.3.1. Descargas e instalaciones

    La instalacion del framework Mono depende de la distribucion de Linux a usar, en este casose trabajo con la distribucion Sabayon Linux, la herramienta para instalar paquetes se denominaequo, para instalar los paquetes necesarios se abre una terminal de consola y se inicia sesioncomo super usuario con el comando su. La instalacion requerira de varias dependencias, lamanera mas sencilla de tener todo lo necesario para empezar a programar es instalar directamenteel IDE de Mono, para esto buscamos el nombre completo del paquete a instalar con el programaequo:

  • 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: version: 2.8.5.1 ~ tag: NoTag ~ revision: 1

    >> Instalado: version: 2.8.5.1 ~ tag: NoTag ~ revision: 1

    >> Bloque: 0

    >> Pagina: http://www.monodevelop.com/

    >> Descripcion: Integrated Development Environment

    >> for .NET

    >> Licencia: GPL-2

    >> @@ Paquete: dev-util/monodevelop-database-2.8.5.1 rama: 5, [sabayon-weekly]

    >> Disponible: version: 2.8.5.1 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: No instalado ~ tag: N/D ~ revision: N/D

    >> Bloque: 0

    >> Pagina: http://www.monodevelop.com/

    >> Descripcion: Database Browser Extension for

    >> MonoDevelop

    >> Licencia: GPL-2

    >> @@ Paquete: dev-util/monodevelop-debugger-gdb-2.8.5.1 rama: 5, [sabayon-weekly]

    >> Disponible: version: 2.8.5.1 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: No instalado ~ tag: N/D ~ revision: N/D

    >> Bloque: 0

    >> Pagina: http://www.monodevelop.com/

    >> Descripcion: GDB Extension for MonoDevelop

    >> Licencia: GPL-2

    >> @@ Paquete: dev-util/monodevelop-java-2.8.5.1 rama: 5, [sabayon-weekly]

    >> Disponible: version: 2.8.5.1 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: No instalado ~ tag: N/D ~ revision: N/D

    >> Bloque: 0

    >> Pagina: http://www.monodevelop.com/

    >> Descripcion: Java Extension for MonoDevelop

    >> Licencia: GPL-2

    >> @@ Paquete: dev-util/monodevelop-vala-2.8.5.1 rama: 5, [sabayon-weekly]

    >> Disponible: version: 2.8.5.1 ~ tag: NoTag ~ revision: 0

    >> Instalado: version: No instalado ~ tag: N/D ~ revision: N/D

    >> Bloque: 0

    >> Pagina: http://www.monodevelop.com/

    >> Descripcion: 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 instalacionequo 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 casousaremos la librera OpenCLTemplate 8. El archivo a descargar hay que descomprimirlo en algundirectorio de facil acceso.

    4.3.2. Configurando MonoDevelop

    Ya que tenemos todo lo necesario, hay que empezar un nuevo proyecto, para esto ejecutamosMonoDevelop. Nos dirigimos al menu 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 boton derecho del mouse sobre nuestro espacio de trabajo, seleccio-namos Anadir y de ah Nueva Solucion:

    En la siguiente ventana, seleccionamos Proyecto de consola C# e ingresamos el mismo nom-bre 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 lasreferencias de nuestra solucion:

  • 4.3 C# 47

    De la ventana emergente, seleccionamos la pestana Net Assembly, aqu buscamos el direc-torio donde descomprimimos la librera OpenCLTemplate. Elegimos los archivos Cloo.dll yOpenCLTemplate.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 inicia-liza el dispositivo GPU si esta disponible.

    CLCalc.InitCL();

    Ahora se carga el codigo OCL de archivo, es el mismo archivo fuente que se ha estado usando alo largo de este tutorial, para importarlo solo basta copiar el archivo example.cl a la carpetaDebug 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});

    Creacion de la funcion 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 dis-positivo 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};

    Ejecucion del Kernel con la cantidad de workers a usar, como en el resto de los ejemplos, usaremosuna dimension 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 delKernel 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 seccion es para aquellos que no estan familiarizados con Linux y desean probar OpenCLen Windows con los lenguajes que figuran en este manual, los pasos son practicamente igualesa los expuestos en la seccion de Linux, salvo la instalacion de software y un par de elementos atomar en cuenta sobre Windows.

    5.1. ANSI C

    En esta parte del tutorial vamos a utilizar ANSI C para nuestro HelloOCL, para esto necesi-taremos de tres herramientas:

    5.1.1. Descargas e instalaciones

    Un IDE para programar en C; en nuestro caso y para mantener la consistencia con Linuxusaremos Code::Blocks, es una herramienta libre y gratuita que la puedes descargar de supagina oficial: 1. Usaremos la version mingw-setup que es la que posee el compilador GCC yel Depurador GDB incluidos (suele ser el instalador mas grande).

    Necesitaremos las cabeceras de la librera de OpenCL; se pueden conseguir directamente dela pagina de Krhonos Group 2, estos archivos son:1. opencl.h2. cl platform.h3. cl.h4. cl ext.h5. cl dx9 media sharing.h6. cl d3d10.h

    1 http://www.codeblocks.org/downloads/26#windows2 http://www.khronos.org/registry/cl/

    51

  • 52 5 OpenCL en Windows

    7. cl d3d11.h8. cl gl.h9. cl gl ext.h

    Hay que crear una carpeta llamada CL y copiar todos los archivos en ella. Tambien esposible 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 lasfunciones de las cabeceras, estas libreras dependen del dispositivo y por lo tanto se debenconseguir con el fabricante de este, para nuestro ejemplo usare una tarjeta ATI cuyo fabricantees AMD. Para obtener la librera se siguieron los siguientes pasos: Descargar el SDK 3 de lapagina oficial. En nuestro caso se usara la version de 64 bits.

    Ejecuta el instalador, aqu se iniciara el instalador del Catalyst Control Center para nuestratarjeta de video, si no se posee el controlador de video o se tiene un controlador basico sepuede continuar e instalar las opciones por defecto, de esta forma tendremos todos los archivosnecesarios para trabajar.

    Por otro lado puede que tengas los controladores de video al da junto con el controladorOpenCL ya instalados, por lo tanto no es necesario instalar el Catalyst Control Center, en estecaso puede buscar en la carpeta donde se descomprimio el archivo y buscar el SDK de OpenCLpara 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 quepueda usarlos, para esto primero hay que crear un nuevo proyecto haciendo clic en el menu Filey 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 librerasOpenCL, para esto se hace clic en Settings y luego en Compiler:

    En la ventana emergente se selecciona la pestana de Search Directories, en esta parte,seleccionamos la pestana Compiler, aqu presionamos el boton Add para agregar la ruta delos encabezados de OpenCL.

  • 56 5 OpenCL en Windows

    Cabe senalar que la carpeta a seleccionar no es la carpeta CL, si no una carpeta anterior aesta 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 enla ruta.

    Ahora toca agregar la ruta de las libreras binarias, para esto seleccionamos la pestana Linkersettings, aqu presionamos el boton Add y en la ventana que aparezca, buscamos el directoriodonde estan las libreras, en este ejemplo usamos la libreras de AMD, estas se encuentran en eldirectorio: C:\Program Files (x86)\AMD APP\lib\x86:

  • 5.1 ANSI C 57

    Finalmente queda agregar los parametros del compilador, para esto presionamos la pestanaLinker 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, hare-mos un pequeno programa que sume los elementos de dos vectores y guarde los resultados en untercero. En los lenguajes siguientes (C# y Java), se usan libreras de terceros que simplifican engran 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 com-plicado, 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 deldispositivo, copiar los datos al dispositivo, crear los comandos de ejecucion, definir los workersa usar, ejecutar el programa en el dispositivo y recuperar los datos de este).

    Empezamos definiendo las cabeceras que usaremos:

    #include

    #include

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

    Las constantes:

    #define MAX_SOURCE_SIZE 512 // tama~no maximo del archivo con el codigo fuente OCL

    #define VECTOR_SIZE 1024 // tama~no de los vectores

    Se crean los vectores que contendran 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 codigo 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 ejecucion:

  • 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 maquina local al dispositivo OCL:

    ret = clEnqueueWriteBuffer(command, v1_mem_obj, CL_TRUE, 0,

    VECTOR_SIZE * sizeof(float), V1, 0, NULL, NULL);

    ret = clEnqueueWriteBuffer(command, v2_mem_obj, CL_TRUE, 0,

    VECTOR_SIZE * sizeof(float), V2, 0, NULL, NULL);

    Se crea el programa que ejecutara el dispositivo OCL con el codigo fuente que se guardo conanterioridad:

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

    Ejecucion 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 yescribir el codigo 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 resultadocomo este:

    5.2. Java

    Una de las ventajas de programar en Java, es que el codigo puede compilarse y ejecutarse encualquier sistema que posea la maquina virtual Java, por lo tanto es necesario que este instaladaen Windows. Tambien es necesario el Kit SDK de Java y algun IDE para programar, para seguiren 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 superiorpara Windows, as como NetBeans5 para Windows. Se puede descargar de la pagina oficial deNetBeans, recuerda descargar para la version de Windows a utilizar, hay que descargar la versionNetBeans Platform SDK Java SE (alrededor de 70MB).

    Primero se instala el Kit de Java.

    4 http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html5 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 instaladorde NetBeans lo reconocera:

  • 64 5 OpenCL en Windows

    Finalmente solo se necesitan las libreras que nos permitan usar OpenCL en Java, se usarala misma librera que se uso en la seccion de Linux JOCL de JogAmp.org6, solo basta condescargar el comprimido y descomprimirlo en algun directorio de facil acceso.

    5.2.2. Configurando NetBeans

    Ya que se ha instalado todo el software necesario, se procede a configurar NetBeans; para ellocreamos 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 elnombre 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 usarla 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 jogamp-all-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 seagregan. Finalmente se selecciona la librera que acabamos de crear y se agrega al proyecto:

    Ahora la librera aparecera 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 com.jogamp.opencl.*;

    import java.io.File;

    import java.io.FileNotFoundException;

    import java.io.FileReader;

    import static java.lang.System.out;

    import java.nio.FloatBuffer;

    import java.util.Scanner;

    Ahora procedemos al main. Lo primero que hay que hacer es crear nuestro contexto para eldispositivo 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 tieneun arreglo SLI (mas de una tarjeta de video Nvidia) o Crossfire (mas de una tarjeta de videoAMD/ATI), se debe escoger uno de los dispositivos disponibles, en este caso, tomaremos el primerdispositivo disponible.

    Se carga el programa OCL desde archivo. A lo largo de este tutorial se ha estado usando elmismo archivo con el codigo OCL example.cl, solo basta agregarlo a la carpeta del proyectopara 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 codigo OCL en el contexto creado, para esto se pasa la cadena que contiene elcodigo del programa.

  • 70 5 OpenCL en Windows

    CLProgram program = context.createProgram(source_str).build();

    Se crean los buffers que contendran los datos a procesar:

    CLBuffer bufferv1 = context.createFloatBuffer(VECTOR_SIZE, READ_ONLY);

    CLBuffer bufferv2 = context.createFloatBuffer(VECTOR_SIZE, READ_ONLY);

    CLBuffer 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 recordarque la cantidad de variables a pasar debe coincidir en numero y tipo de datos definidos en elKernel.

    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 funcion pasamos el Kernel de nuestro programa, as como las dimensiones de workersque queremos usar, cada worker es un hilo de ejecucion en el dispositivo, como nuestro ejemploes unidimensional solo requerimos de VECTOR SIZE workers (uno por cada celda de losbuffers), cada uno de ellos solo posee 1 sub-hilo de ejecucion.

    Cuando el programa termine, podemos leer los resultados, para ello extraemos los datos deldispositivo 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 71

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

    }

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

  • Captulo 6

    Caso de estudio: fractal

    A lo largo de este tutorial se ha visto como utilizar OpenCL en algunos lenguajes de progra-macion populares, como inicializar dispositivos, manipular datos en estos y ejecutar programasOCL, todos son ejemplos introductorios sobre como funciona esta tecnologa. En este puntose querra ver algun ejemplo mas funcional de lo que se podra hacer con OpenCL, para estoharemos una pequena aplicacion con C# ( que puede ser facilmente traducida a otro lenguajepues no se usan rutinas muy especificas del lenguaje) que genere fractales.

    En esta seccion se veran algunos topicos como:1. Uso multidimensional de workers.2. Utilizacion de funciones locales en OCL.3. Utilizacion de estructuras en OCL.4. Buffers de imagen en OpenCL.

    6.1. Introduccion a los fractales

    Un fractal es un objeto geometrico cuya estructura basica, fragmentada o irregular, se repite adiferentes escalas. En otras palabras, es un objeto creado con replicas mas pequenas de si mismo,son figuras bastante demandantes a la hora de generarlas pues cada punto se calcula para sabersi 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 elplano espacial (x y y), pues son funciones con numeros complejos y trabajan en planos complejos.Para representar el fractal en un plano espacial, suele utilizarse un algoritmo llamado algoritmode 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 sucesioncorrespondiente a dicho punto. Si aplicamos una serie de iteraciones por punto (siguiendo elalgoritmo), observaremos que algunos puntos llegaran a divergir despues de pocas iteraciones,mientras que otros puntos necesitaran de mas iteraciones. Como no se pueden realizar iteracionesinfinitas, es necesario establecer un lmite y decidir que si los n primeros terminos de la sucesionestan acotados, entonces se considera que el punto pertenece al conjunto. Al aumentar el valorde n se mejora la precision 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 codigo OpenCL 75

    En este caso se calcula Z con la ecuacion del fractal a dibujar (la ecuacion del ejemplo perteneceal denominado conjunto de Julia 2).

    Una vez calculado Z se verifica si no diverge, se utiliza la formula x2 + y2 = 4 . Se poseeun lmite de iteraciones para ver si el numero diverge, en este caso es n, entre mas grande seamas precision tendra la imagen. Un limite comun es 256, que permite generar puntos para unaimagen de 8 bits.

    Como se puede ver, toma demasiado tiempo calcular el color para cada pixel (siendo el peorde los casos, n iteraciones por pixel). Tiempo que se agrava con forme mas resolucion se deseapara la imagen final.

    Este es un buen lugar para ver como el computo paralelo puede incrementar la velocidad deprocesado y compararlo con el procesamiento secuencial.

    6.3. Escribiendo el codigo OpenCL

    La primera parte sera escribir el codigo OCL en un archivo de texto plano, una vez creadas lasrutinas y el Kernel, se pasa a convertir estas mismas funciones en el lenguaje de programacionC# para hacer la version secuencial. Finalmente se medira la velocidad de ejecucion de ambasversiones. Primero se definien las constantes del programa, para definir una constantes, se utilizala 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 numeros com-plejos 3, suena mas complicado a primera vista, pero podemos decir que los numeros complejosposeen una parte real y una imaginaria.

    El lenguaje OCL-C99 posee tipos de datos que pueden ser utilizados para este proposito, estosson 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 numero de datospor 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 http://en.wikipedia.org/wiki/Julia_set3 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 datosvector, se puede definir la estructura de los numeros 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 basicapara estos numeros, dada la formula que se utilza, se necesita sumar y multiplicar, as como lafuncion que calcule la divergencia del numero. Las funciones locales en OCL se declaran conla palabra reservada inline, seguida de la declaracion de la funcion (tipo de dato que regresa,nombre de la funcion y parametros).

    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 guardarael fractal (esto define la resolucion de la imagen) y el buffer donde se guardaran los puntosobtenidos.

  • 6.3 Escribiendo el codigo OpenCL 77

    6.3.1. image2d

    Aqu conviene hacer un parentesis y explicar un poco al respecto de las variables image2d. LasGPUs estan optimizadas para manejar caches de imagenes/texturas de manera eficiente, pues esparte de su naturaleza, siendo dispositivos que fueron creados con ese proposito (manejar texturassobre 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. Permitenutilizar 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 son1024Mb, es decir, 1 Gb, en contra parte con los buffers de memoria convencional que tienenlimites de tamano y son mas lentos de accesar.

    Por desgracia tambien tienen sus desventajas que hay que tener en cuenta:1. Las imagenes 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 usode este tipo de variable nos ahorra algunos pasos como calcular la posicion del pixel y el formatode color, si en algun caso el hardware con el que se trabaja no soporta este tipo de variable, sepuede sustituir la variable image2d por un vector de tipo int, en la seccion de apendice, vieneeste ejemplo usando vectores en lugar de imagenes.

    6.3.2. Definicion del Kernel

    Explicacion 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 REALMIN = -1.0;

    float IMAGMIN = -1.0;

    float REALMAX = 1.0 ;

    float IMAGMAX = 1.0;

    Complex C; // nuestro numero de control

    /* estos vectores solo poseen un elemento,

    solo basta leer la primera posicion (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 encargara

    de una lnea de la imagen y los sub-workers de este se encargaran de cada

    pixel de la lnea.

    */

    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. Notese el uso del ancho en x, esto es por que la imagen se dibuja

    invertida

    */

    int2 coord = (int2)((*width) - x,y);

    /* Calculo de Z con respecto a su posicion 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); // Ecuacion del fractal

    c++;

    if (Norm(Z) > DIVERGE)

    break;

    } while (c < MAX);

    /*

    Si el numero de iteraciones no llego al lmite del ciclo, entonces el

    numero 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 codigo anfitrion 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 numero de iteraciones

    que se obtuvo del algoritmo, (cuyo lmite 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 codigo anfitrion

    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 ventanasdepende del sistema operativo, por lo que se necesitara reescribir el codigo en la parte graficapara cada sistema o bien, usar alguna librera multi-plataforma (como la librera GTK), eso darapara otro tutorial en si mismo. Por esta razon crearemos un proyecto en C# para aplicacion deconsola y nuestro fractal lo guardaremos en un archivo de imagen.

    Empezaremos agregando dos nuevas clases a nuestro proyecto, las llamaremos FractalCPU yFractalOCL.

    6.4.1. FractalCPU

    Empezaremos editando la clase FractalCPU, esta sera la clase que creara el fractal de manerasecuencial haciendo uso de la CPU, basicamente sera la traduccion a C# del codigo OCL quehicimos en la seccion anterior.

    Variables globales:

    float DIVERGE = 4.0f;

    UInt32 MAX = 256;

    Bitmap buffer;

  • 80 6 Caso de estudio: fractal

    Nuestra estructura para numero complejos. Es cierto que existe la librera nativa para mani-pulacion de numero complejos en C#, pero la idea de este proyecto es ver el desempeno de loscodigos escritos en OCL y en C#, por lo que es mas acorde comparar el desempeno con lasmismas 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 lacreacion de este por medio de 2 ciclos, recibira como parametros el tamano del fractal, as comosu 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 funcion regresara el bitmap con el fractal. Se puede omitir haciendo publica la variabledel bitmap.

    public Bitmap GetBmp()

    {

    return buffer;

    }

    Las funciones para las operaciones basicas con numeros 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 codigo anfitrion 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 traduccion del Kernel a C#. con la diferencia que sera llamada secuencialmente enlos 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 dispositivoOpenCL y recuperara 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, basicamente incializa los dispositivos OCL y ejecuta el codigo, en estepunto no es necesario explicar que hace cada funcion pues es lo que se ha estado viendo en todoel 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 codigo anfitrion 83

    Fractal.Execute(Args, workers);

    }

    La funcion 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 tamano de la imagen:

    static int w = 4000; // ancho de la imagen

    static int h = 4000; // alto de la imagen

    static float real = 0.285f; // Parte real del fractal

    static float imag = 0.01f; // parte imaginaria del fractal

    Como se puede ver, crearemos una imagen muy grande de 4000x4000 pixeles para ver ladiferencia de tiempos de procesado. El tamano de la imagen puede generar excepciones al ejecutarel codigo pues los limites de resolucion de textura difieren entre fabricantes y memoria disponibleen el dispositivo, por ejemplo las tarjetas NVidia suelen tener u