2 Computación con Procesadores Gráficos 35
2.5 OpenCL 80
2.5.1 Características de OpenCL 81
La característica principal de OpenCL es que busca ser adaptable a cualquier sistema de computación. Cuando una aplicación se ejecuta, inicialmente realiza una identificación del sistema en el que corre, en la que se adquiere información acerca de las plataformas y los dispositivos de esas plataformas presentes en el equipo. También se definen los contextos, que no son más que agrupaciones de dispositivos que se usarán para la resolución de un kernel.
Una plataforma está constituida por uno o varios dispositivos, cada uno de ellos con sus capacidades características, tales como unidades de ejecución, memoria, etc. A su vez, las plataformas se caracterizan por su tipo de dispositivo (CPU, GPU, etc), el fabricante, la versión de OpenCL que puede ejecutar, etc. OpenCL dispone de funciones que permiten averiguar el número y las características de las plataformas presentes en el sistema, así como las características de los dispositivos de cada plataforma. Con estos datos se forman los contextos, mediante los cuales el programador define qué dispositivos agrupa para cada operación. En OpenCL, un kernel
se ejecuta en un contexto, y para cada contexto se crea una cola a la que van llegando los kernels que deben ejecutarse en ese grupo de dispositivos.
Tanta complejidad no es necesaria en CUDA, que es un entorno más homogéneo. En CUDA, la aplicación puede obtener información sobre el sistema sobre el que corre, para averiguar el número de GPUs y sus respectivas características. Y con la compilación de última hora puede adaptarse a las GPUs presentes en el sistema. OpenCL, por su parte, tiene más requisitos en este respecto, ya que sus objetivos también son más ambiciosos. Al contrario que CUDA,
pensado exclusivamente para tarjetas GPU de NVIDIA, OpenCL puede trabajar con más tipos de dispositivos de cálculo con características más diversas.
Modelo de ejecución.
En OpenCL se consigue la escalabilidad de una forma parecida a la que usa CUDA. Para gestionar una gran cantidad de hilos sin dedicar demasiado silicio a estructuras de control, estos se agrupan en dos jerarquías. Las rutinas que se ejecutan en los contextos (también llamadas kernels) se organizan en un primer nivel en una rejilla llamada NDRange, que puede tener hasta 3 dimensiones. Los elementos de un NDRange se distribuyen en WorkGroups (grupos de trabajo), que a su vez se organizan en otra rejilla de hasta 3 dimensiones, cuyos elementos se denominan WorkItems (elementos de trabajo). Los elementos de trabajo del mismo grupo pueden sincronizarse y compartir datos.
Jerarquía de memoria.
OpenCL ofrece al programador una jerarquía virtual de memoria. En tiempo de ejecución se establecen las correspondencias necesarias entre los niveles virtuales y los físicos. Con esto se pretende que el programador no tenga que estar al corriente de todos los detalles de la memoria de los dispositivos. Los niveles de la jerarquía son muy parecidos a los que existen en CUDA: existe una memoria global que comparten todos los elementos de trabajo, y que es muy extensa y lenta. Con características parecidas existe la memoria de constantes, que se utiliza para constantes numéricas y datos que todos los elementos de trabajo leen a la vez (por ejemplo, si todos leen a la vez
el mismo elemento de un array). También existe una memoria local que pueden compartir los elementos de trabajo del mismo grupo. Y por último, la memoria privada es exclusiva de cada elemento de trabajo. La Tabla 2-8 muestra las correspondencias entre CUDA y OpenCL en lo que respecta a los niveles de la jerarquía de memoria. Como se puede ver, son los mismos. La única salvedad es que, si en un futuro los dispositivos incorporasen algún tipo adicional de memoria, las aplicaciones de OpenCL no necesitarían modificarse, ya que la compilación de última hora (just-in-time compilation) podría adaptar las correspondencias entre los niveles de la jerarquía virtual y la física para tenerlos en cuenta. CUDA, por su parte, debería incorporar esos niveles a su entorno de programación y a su modelo de ejecución.
Tabla 2-8. Tipos de memoria en CUDA y OpenCl
CUDA OpenCL Global Global Compartida Local Local Privada Constante Constante Programación.
La forma de construir los programas es parecida a la que se da en CUDA si se utiliza el driver en lugar de la biblioteca de tiempo de ejecución (el runtime). Es preciso crear un contexto, y dentro de él, una cola de comandos para cada dispositivo. El contexto sirve para coordinar la interacción entre host y dispositivos. Las colas de
comandos, al estar dedicadas cada una a uno de los dispositivos, requieren sondear el sistema para encontrar los que existen, analizar sus características, etc. Este proceso es farragoso y pesado, y en CUDA se puede evitar si se usa el runtime.
Herramientas de desarrollo.
AMD proporciona un entorno de desarrollo para OpenCL llamado CODEXL. Contiene un editor, un compilador y un potente depurador que permite analizar la ejecución de los kernels en la GPU en tiempo real. Además, también realiza análisis de rendimiento (profiling) en ejecución y en estático. Está disponible para plataformas Windows y Linux. En el caso de Windows, se integra en el entorno de Visual Studio.
OpenCL Studio es otro entorno de desarrollo, en este caso proporcionado por la empresa Geist Software Labs, Inc. En este caso, no se proporciona demasiada información sobre el producto, por lo que no ha sido posible estudiarlo más en detalle.
Bibliotecas de terceros.
El “ecosistema” de OpenCL realmente no ha terminado de despegar aún, y en lo que respecta a bibliotecas de rutinas de terceros, CUDA tiene considerable ventaja. Para OpenCL existe clAmdBlas, una adaptación de BLAS para OpenCL, proporcionada por AMD, y clAmdFft, una biblioteca de transformadas de Fourier rápidas. Además, está en proceso una versión de MAGMA para OpenCL, llamada clMAGMA. MAGMA (Matrix Algebra on GPU and Multicore Architectures, álgebra matricial en GPUs y arquitecturas multicore), que ya se ha mencionado anteriormente en
la sección de herramientas para CUDA, es una adaptación de LAPACK para sistemas con múltiples CPUs y GPUS. En la versión para OpenCL no están incluidas todas las rutinas de LAPACK, pero sí algunas de las más comunes, como, por ejemplo, las factorizaciones LU, QR y Cholesky, reducciones, transformaciones ortogonales y soluciones a problemas de autovalores y valores singulares.
A pesar de la desventaja en este campo que presenta OpenCL, se puede esperar que el entorno vaya evolucionando favorablemente, habida cuenta de la importancia de las empresas que están detrás de él (Apple, Intel, AMD, ARM, Qualcomm, TexasInstruments, etc).
Razones para la elección de CUDA frente a OpenCL.
Como se ha mencionado en secciones anteriores, la principal razón para elegir CUDA frente a OpenCL fue el estado más avanzado de desarrollo del conjunto de herramientas disponibles en el momento de tomar la decisión. NVIDIA ha venido realizando un esfuerzo considerable por dotar a los desarrolladores del mejor software posible, lo que ha conferido a CUDA una notable ventaja que las empresas que están detrás de OpenCL aún están intentando compensar. En concreto, la existencia de un depurador avanzado como es NSight facilita considerablemente la tarea de corregir los inevitables errores que se producen en el desarrollo de las aplicaciones. Esta tarea se vuelve terriblemente ardua sin una utilidad de este tipo, y es una buena noticia que haya algo parecido ahora para OpenCL, pero este no era el caso al inicio del trabajo descrito en esta memoria.
Otra de las razones que han desequilibrado la balanza en favor de CUDA ha sido la existencia de bibliotecas de funciones matemáticas.
Ya que esta tesis consiste precisamente en adaptar una serie de métodos numéricos para el análisis electromagnético a una nueva arquitectura computacional, el hecho de poder utilizar estas funciones ha permitido avanzar más en el procedimiento. De nuevo, en el momento de la toma de decisión, el entorno CUDA estaba más desarrollado, lo que ha sido un factor más en favor de esta plataforma.
CUDA presenta algún aspecto negativo. El principal es la exclusividad. Las aplicaciones desarrolladas en CUDA sólo sirven con tarjetas de NVIDIA, mientras que OpenCL hace posible su implantación en un conjunto mayor de ellas. Los fabricantes de GPUs son fundamentalmente NVIDIA y AMD, y es difícil determinar cuál de ellos fabrica la tarjeta más potente en cada momento. Utilizar CUDA limita las posibilidades a tarjetas de NVIDIA, mientras que OpenCL permite trabajar con ambos fabricantes. Ambos mejoran continuamente sus productos, y la mejor tarjeta generalmente es la más reciente. En este sentido, tener la posibilidad de cambiar de fabricante sobre la marcha es un gran punto a favor de OpenCL
En cuanto a la comparación de rendimientos entre CUDA y OpenCL, existen estudios al respecto que parecen darle cierta ventaja a CUDA, como [52] y [53]. Pero hay que tener en cuenta que las pruebas están realizadas sobre GPUs de NVIDIA (la única posibilidad, ya que CUDA no se puede utilizar con tarjetas AMD), a las que CUDA está naturalmente mejor adaptado. NVIDIA tiene mucho interés en CUDA, y está desarrollando un gran esfuerzo en esta línea, por lo que la comparación con la versión de OpenCL para
tarjetas NVIDIA, también desarrollado por NVIDIA, no se realiza en igualdad de condiciones..
A la vista de todos estos elementos de juicio, la decisión final tomada fue optar por CUDA. Las ventajas con respecto a las herramientas de desarrollo y las bibliotecas de código ya existentes fueron el factor principal en esta decisión. Es cierto que, en la práctica, se asume un compromiso de exclusividad con las tarjetas de NVIDIA, pero resulta difícil, como se ha explicado, decidir qué familia proporciona mejor rendimiento bruto, por lo que esta exclusividad no es un factor decisivo, en cualquier caso.