PROGRAMACIÓN E IMPLEMENTACIÓN DE UN CLÚSTER

Tamaño: px
Comenzar la demostración a partir de la página:

Download "PROGRAMACIÓN E IMPLEMENTACIÓN DE UN CLÚSTER"

Transcripción

1 Universidad Autónoma Metropolitana Unidad Iztapalapa PROGRAMACIÓN E IMPLEMENTACIÓN DE UN CLÚSTER DE GPU'S Autores Regalado Orocio Luis Armando Bolaños Palacios Julio Cesar Asesores M. en C. Quiroz Fabián José Luis Dr. Castro García Miguel Alfonso Dr. Aguilar Cornejo Manuel Octubre

2 Índice Introducción 3 1. GPU: Unidad de Procesamiento de Gráficos Programación para GPU's y el nuevo reto: GPGPU Modelos de programación en GPU s Requisitos en Hardware de una PC para la instalación de una GPU La Tecnología GPU y CUDA GPGPU aplicado Arquitectura Física de una GPU Desmenuzando CUDA. 11 a. Instalación de CUDA. 12 b. Instalar dependencias. 12 c. Instalar el controlador del dispositivo. 13 d. Instalación de CUDA Toolkit. 13 e. Instalar CUDA SDK. 14 f. Prueba de la instalación CUDA, un modelo de programación de propósito general. 16 a. Host y Device 16 b. Los CUDA Threads y su Jerarquía dentro de la GPU. 16 c. Jerarquía de Memoria 17 d. Kernels. 18 e. Thread Id: Un paso más allá. 19 f. Sincronizando los Threads. 19 g. CUDA: Una programación Heterogénea. 20 h. El proceso de compilación Programación en CUDA. 22 a. Suma de dos Números con CUDA. 22 b. Suma de dos vectores con CUDA. 24 c. Array CudaID. 26 d. Reducción de un Vector con Memoria Compartida. 30 e. Inicialización de un arreglo usando memoria de textura. 32 f. Inicialización de un arreglo usando memoria constante. 34 g. Inicialización de un arreglo usando pthreads Controlando más de un GPU (Programación MultiGPU). 39 a. OpenMP + Cuda. 39 b. Pthreads + Cuda Implementación de un Cluster de GPU s. 51 a. Clúster de Computadoras. 51 c. b. Requisitos o Componentes de un Clúster. 52 c. Clúster de GPU s. 53 d. Interfaz de paso de mensajes: OpenMPI. 54 e. Laboratorio de Sistemas Distribuidos UAM I. 55 f. Paralelismo CUDA + MPI. 56 g. Compilación y Ejecución de un programa CUDA + Open MPI. 56 1

3 h. Ejemplo: simplecudampi. 59 i. Ejemplo: Cuda + Pthreads + OpenMPI. 62 j. Ejemplo: SendReceiveCUDAMPI. 66 k. Ejemplo: ReduceCudaMPI Conclusiones Anexo 72 a. Funciones Open MP. 72 b. Funciones Open MPI. 73 c. Configuración de un Clúster Bibliografía 76 2

4 Introducción La programación paralela tradicionalmente se realizaba en máquinas con más de un procesador (máquinas multiprocesador). Debido al costo de las máquinas multiprocesador surgen los clúster (máquinas monoprocesador unidos mediante una red de alta velocidad). Posteriormente la reducción de costos del hardware (entre otras razones) dio surgimiento a las maquinas multicore y clústers de este tipo de arquitecturas. Las aplicaciones paralelas se han adaptado al hardware multicore buscando aprovechar su diseño (p.e. tener más de un núcleo en un mismo procesador). Haciendo uso de multicore se podría generar un hilo de trabajo por cada núcleo a fin de aumentar el rendimiento de la aplicación paralela más sin embargo estamos limitados al número reducido de núcleos (en promedio 4 por procesador) que ofrecen dichos procesadores. Este limitante se ha resuelto en diferentes aplicaciones haciendo uso de GPU's (graphics processing unit). Un GPU es un dispositivo dedicado al procesamiento de gráficos, una característica muy importante de un GPU es que puede tener múltiples núcleos (más de 100) trabajando de forma paralela. Buscando explotar los GPU's para aplicaciones en diferentes dominios se ha introducido recientemente el término GPGPU (General Purpose Computing on Graphics Processing Units, Programación de Propósito General en Unidades de Procesamiento de Gráficos), esto es: las GPU's ya no solo son usadas para el procesamiento de gráficos, ahora se busca aprovechar su poder de computó para desarrollar una amplia variedad de aplicaciones de propósito general. Además se puede agrupar un conjunto de nodos con GPU's y formar un clúster de GPU's haciendo uso de interfaces de programación paralela como MPI o PVM (entre otras). El siguiente reporte pretende mostrar la posibilidad de implementar un clúster de GPU's para aprovechar las prestaciones de estos dispositivos; para ello, se realiza una explicación de lo que es una GPU, los lenguajes creados para su programación, así como su arquitectura; posteriormente se hace énfasis en la configuración y puesta a punto de un clúster integrado por GPU's. 3

5 1. GPU: Unidad de Procesamiento de Gráficos Una GPU (Graphics Processing Unit 1 ), es un dispositivo de hardware que por sus características, se encarga del procesamiento de gráficos en una computadora, logrando aligerar en este aspecto la carga de trabajo en el CPU (Unidad de Procesamiento Central). Hoy en día se pueden encontrar GPU's en una gran cantidad de dispositivos como lo son: Computadoras de Escritorio, Computadoras Portátiles, Consolas de videojuegos y video proyectores, entre otros. Una tarjeta de procesamiento de gráficos, GPU; es altamente especializada, pues está pensada para realizar una sola tarea: Procesamiento de Gráficos, que no es otra cosa que realizar una gran cantidad de cálculos aritméticos sobre números reales (representados en una computadora mediante la representación de coma flotante 2 ). Al ser un hardware especializado, todas las operaciones se realizan más rápido pues están implementadas a nivel hardware, es decir, se ocupan circuitos de silicio para realizar dichos cálculos. Una GPU, además posee un alto grado de paralelismo, pues soporta una gran cantidad de threads 3 en ejecución al mismo tiempo, esto comparado con los procesadores más actuales. En la siguiente gráfica se puede apreciar una comparativa entre las CPU's de la marca Intel y las GPU's de la marca Nvidia. Figura 1. Gráfica comparativa sobre la cantidad de operaciones en punto flotante que puede realizar un CPU vs GPU. 1 GPU: por sus siglas en ingles: Graphics Process Unit, Unidad de Procesamiento de Gráficos. 2 La representación de coma flotante, es una forma de notación científica usada en los CPU, GPU, FPU, etc., con la cual se pueden representar números reales extremadamente grandes y pequeños de una manera muy eficiente y compacta, y con la que se pueden realizar operaciones aritméticas. El estándar para la representación en coma flotante es el IEEE En sistemas operativos, un hilo de ejecución, hebra o subproceso es la unidad de procesamiento más pequeña que puede ser planificada por un sistema operativo. 4

6 En esta imagen podemos destacar varios puntos: Para Enero de 2003 las capacidades de cálculo de los procesadores y las GPU's eran casi las mismas. En Junio de 2003 comienza un despegue entre las capacidades de computo de los procesadores convencionales y las unidades de procesamiento gráficos, aunque estas capacidades no rebasan los 100 Gigas de operaciones de coma flotante por segundo (GFLOPS). Entre los años 2006 y 2007 la tecnología de las GPU's, supera en amplio rango el número de operaciones sobre números en coma flotante, aun compitiendo contra los procesadores de última tecnología de la época. 2. Programación para GPU's y el nuevo reto: GPGPU En un principio la programación en las GPU's se llevaba a cabo mediante interrupciones de hardware 4 realizadas en el BIOS 5 de las computadoras donde estaban instaladas. Tiempo después, esta programación se realizaba utilizando el lenguaje ensamblador específico para cada modelo de GPU, lo que llevaba a tener que aprender con cada modelo de GPU un nuevo conjunto de instrucciones máquina. Esta forma de programación fue reemplazada al crearse un conjunto de interfaces de programación de aplicaciones, llamadas en el ámbito de la programación como: API's. Las API s ayudaron a manipular las diferentes características de las tarjetas gráficas, teniendo así un lenguaje de programación más homogéneo; entre las API's más importantes desarrolladas están OpenGL (Open Graphics Languaje) y DirectX. Posterior al desarrollo de las API's de programación, surgieron lenguajes de programación orientados puramente a la manipulación de gráficos; en este tipo de lenguajes el programador separaba la lógica de la aplicación en dos partes: la que se procesaba fuera de la GPU y la que se ejecutaba directamente en el dispositivo de gráficos; algunos lenguajes de programación destacados son: OpenGL Shading Languaje (GLSL), C for Graphics (Cg), y High Level Shading Languaje (HLSL). Pero quizás el desarrollo más importante sobre GPU's, se ha dado en los últimos años con el nuevo concepto: Cómputo de propósito general sobre Unidades de procesamiento de gráficos (General- Purpose Computing on Graphics Processing Units, GPGPU); un modelo de programación paralela reciente dentro de la informática, que intenta aprovechar todas las capacidades de cómputo de las GPU's con la finalidad de resolver problemas que por su naturaleza demandan un alto poder de cálculo. Para dicho propósito se han desarrollado 4 Interrupción (también conocida como interrupción de hardware o petición de interrupción): es una señal recibida por el procesador de un ordenador, indicando que debe "interrumpir" el curso de ejecución actual y pasar a ejecutar código específico para tratar esta situación. 5 El BIOS (sigla en inglés de basic input/output system; en español «sistema básico de entrada y salida») es un tipo de firmware que localiza y prepara los componentes electrónicos o periféricos de una máquina, para comunicarlos con algún sistema operativo que la gobernará. 5

7 diferentes lenguajes de programación como son: BrookGPU desarrollado en la universidad de Stanford; Sh un lenguaje de programación derivado de C++ y orientado a objetos; OpenCL (Open Computing Languaje) un ambicioso proyecto que busca unificar tanto el poder de cálculo de las GPU's como el del CPU para resolver problemas de alta demanda y por último el lenguaje de programación desarrollado por la empresa NVIDIA para programar sus propias GPU's: CUDA (Compute Unified Device Architecture), siendo este último, un lenguaje de programación relevante, ya que sus características lo hacen ideal para desarrollar aplicaciones altamente paralelas sobre GPU's nvidia. 3. Modelos de programación en GPU s Existen varios modelos de programación como arquitecturas de computadoras, de acuerdo a la taxonomía de Flynn propuesta por Michael J. Flynn en Se define una clasificación de modelos que se adaptan a distintas arquitecturas: Taxonomía de Flynn Una instrucción Múltiples instrucciones Un dato SISD MISD Múltiples datos SIMD MIMD Single Instruction Single Data (SISD): Maquina monoprocesador que ejecuta instrucciones de manera secuencial. Multiple Instruction Single Data (MISD): Paralelismo redundante, sistemas de respaldo. Single Instruction Multiple Data (SIMD): Se explotan varios flujos de datos dentro de un único flujo de instrucciones. Multiple Instruction Multiple Data (MIMD): Varios procesadores autónomos que ejecutan simultáneamente diferentes instrucciones con diferentes datos. De esta clasificación se deriva una extensión común a esta taxonomía, que modela de manera intrínseca la programación en una GPU debido a la arquitectura que la conforma. Dicha clasificación se define como Single Program Multiple Data (SPMD), donde múltiples unidades de proceso autónomas e independientes, trabajan simultáneamente sobre el mismo conjunto de instrucciones (aunque en puntos independientes). Es decir, todos los hilos (unidad mínima de proceso) que son lanzados por la GPU, ejecutan el mismo programa con datos diferentes (aunque nada impide que puedan ser los mismos). 6

8 4. Requisitos en Hardware de una PC para la instalación de un GPU Para instalar exitosamente un dispositivo de gráficos GPU, será necesario que el equipo de cómputo con el que contemos, específicamente la tarjeta madre, posea una ranura del tipo PCIe x16; se recomienda que la fuente de poder instalada sea de alto rendimiento y que posea los conectores necesarios para alimentar la GPU (aunque no todas las GPU s requieren alimentación eléctrica). También es deseable que el gabinete donde se desee instalar el dispositivo cuente con la suficiente capacidad de enfriamiento, ya que el trabajo intensivo sobre los dispositivos genera una cantidad de calor considerable. La siguiente imagen muestra el tipo de ranura PCI-E 16X, la cual es usada mayormente para conectar tarjetas gráficas. PCI Express en 2006 es percibido como un estándar de las placas base para PC, especialmente en tarjetas gráficas. Marcas como ATI Technologies y nvidia entre otras trabajan sobre esta arquitectura. Figura 2. Placa Madre con ranura de tipo PCIe x 16, la cual es el puerto estándar para muchas tarjetas de video. 7

9 5. La Tecnología GPU y CUDA Los GPU's de la empresa nvidia tienen dos características esenciales: por un lado son Multicore, esto es que en la misma tarjeta GPU tenemos varios (incluso cientos) de procesadores; por otro lado son Multihilo, es decir, se pueden lanzar cientos o miles de hilos, trabajando en un mismo instante para lograr un objetivo común. Dependiendo de las características del GPU, se puede tener hasta un rendimiento de 470 Giga Flops 6, es decir 470 billones de operaciones sobre números reales (representados en coma flotante) en un segundo. En noviembre de 2006, nvidia introdujo CUDA, una arquitectura de computación paralela de propósito general que aprovecha el motor de cálculo paralelo de las GPU nvidia para resolver muchos problemas complejos de computación de una manera más eficiente que en una CPU convencional. CUDA viene con un entorno de software que permite a los desarrolladores usar una extensión del lenguaje C y C++ para programar algoritmos que correrán sobre una GPU; además CUDA puede utilizarse también con los lenguajes de programación como: Python, Fortran y Java. La figura 3 muestra algunas características interesantes de la GPU nvidia GTX 460, el objetivo del gráfico es destacar el número de procesadores en la tarjeta y la cantidad de Threads paralelos que se pueden ejecutar en este dispositivo. Figura 3. Tabla de especificaciones técnicas de una tarjeta de procesamiento de gráficos de la marca nvidia. 6 En informática, las operaciones de coma flotante por segundo son una medida del rendimiento de una computadora, especialmente en cálculos científicos que requieren un gran uso de operaciones de coma flotante. Es más conocido su acrónimo, FLOPS, por el inglés floating point operations per second. 8

10 La razón principal detrás de la discrepancia en la capacidad cálculo sobre números representados en punto flotante entre la CPU y la GPU es que la GPU está especializado para realizar cálculo intensivo y paralelo sobre datos de este tipo, que es exactamente lo que está detrás del procesamiento de gráficos y por lo tanto está diseñado de tal forma que un mayor número de transistores se dedican al procesamiento de datos en lugar de caché de datos y control de flujo, como se ilustra esquemáticamente en la figura 4. Figura 4. Diferencia de arquitecturas entre los CPU s convencionales y las GPU s más actuales. Más específicamente, la GPU es especialmente adecuada para abordar los problemas que se pueden expresar como cálculos paralelos sobre muchos datos (el mismo programa se ejecuta en muchos elementos de datos en paralelo, con una intensidad aritmética alta). Muchas aplicaciones que procesan grandes volúmenes de datos pueden utilizar un modelo de programación de datos en paralelo para acelerar los cálculos. En 3D, grandes conjuntos de píxeles y vértices se asignan a los hilos en paralelo, del mismo modo, las imágenes son renderizadas, la codificación y decodificación de video se realiza de esta misma manera, también la ampliación de imágenes, la visión estéreo, y el reconocimiento de patrones trabajan con este modelo paralelo; de hecho, muchos algoritmos fuera del campo del procesamiento de imágenes, son acelerados por el procesamiento paralelo de datos, como el procesamiento de señales en general o simulación de la física, finanzas o biología computacional. 6. GPGPU aplicado Hemos venido destacando las prestaciones de las GPU's, pero cuál es su campo de acción?, a continuación presentamos ejemplos del impacto que ha tenido en diferentes áreas esta nueva forma de hacer cómputo paralelo: Bioinformática y Ciencias de la Vida: La secuenciación y el acoplamiento de proteínas son tareas muy intensivas en cómputo, que ven una mejora del rendimiento general mediante el uso de una GPU y el paradigma GPGPU. 9

11 Dinámica de Fluidos Computacional: Varios proyectos en curso sobre los modelos de Navier-Stokes y métodos Lattice Boltzman han mostrado aceleraciones muy grandes que utilizan GPU s habilitadas con CUDA. Minería de Datos, Análisis y Bases de Datos: Las Bases de datos son el caballo de batalla de las empresas hoy en día. Buscar a través de bases de datos y encontrar información útil se ha convertido en un reto computacional grande. Los investigadores del mundo académico y de Microsoft, Oracle, SAP, y muchas otras corporaciones están buscando en las prestaciones de las GPU's la solución a la búsqueda y recuperación de datos. Imágenes Médicas: El tratamiento de imágenes médicas es una de las primeras aplicaciones para tomar ventaja del poder de cálculo de la GPU para obtener una mayor la aceleración en el procesamiento de información. Dinámica Molecular: Las aplicaciones de dinámica molecular son extremadamente susceptibles a la arquitectura masivamente paralela de las GPU. Se destaca el trabajo realizado en paquetes de software como VMD, NAMD y HOOMD. Criptografía: Se han desarrollado programas paralelos para mejorar la encriptación de datos usando algoritmos como RSA, además mediante el uso de una GPU ha sido posible romper passwords cifrados con MD5 e incluso RAR. 7. Arquitectura Física de una GPU Tiene que ver directamente con el hardware del dispositivo, la placa de la GPU está constituida por circuitos, transistores y otros componentes que forman varias capas denominadas Multiprocessors, cada capa contiene a su vez varios procesadores denominados CUDA Cores. Las capas Multiprocessors están conectadas mediante buses de datos con la memoria global del dispositivo, con una serie de registros y un bloque de memoria compartida por capa Multiprocessors. Es importante mencionar que el número de capas Multiprocessors y CUDA Cores contenidos en cada capa, varía en dependencia del modelo de la GPU. A continuación se muestra un diagrama, que muestra la forma física de una GPU. 10

12 Figura 5. Arquitectura física de una GPU, la cual muestra sus diversas capas de multiprocesadores. 8. Desmenuzando CUDA CUDA significa: Compute Unified Device Architecture, y es un modelo de programación de propósito general, donde el programador podrá manipular lotes de hilos concurrentes que se generan dentro de la GPU, con dichos hilos se podrá realizar calculo intensivo, con lo cual se puede ver a una GPU como un coprocesador masivamente paralelo. El primer SDK se publicó en febrero de 2007 en un principio para Windows, Linux, y más adelante en su versión 2.0 para Mac OS. Actualmente se ofrece para Windows XP/Vista/7, para Linux 32/64 bits y para Mac OS en su versión 4.0 lanzada el 5 de Junio de

13 a. Instalación de CUDA Realizaremos una instalación guiada de CUDA, para ello deberemos tener los siguientes requerimientos: Hardware: Software: o GPU nvidia compatible con CUDA 7. o Sistema Operativo GNU/Linux (Esta guía basada en Centos 5.0) o Driver GPU 8 (NVIDIA-Linux-x86_ run) o CUDA ToolKit (cudatoolkit_4.0.17_linux_64_rhel5.5.run) 9 o CUDA SDK + Examples (gpucomputingsdk_4.0.17_linux.run) b. Instalar dependencias Para que la instalación de todo el entorno CUDA se realizara de forma exitosa es necesario instalar una serie de librerías y aplicaciones a fin de resolver cualquier dependencia, para ello echaremos mano de una terminal: # CUDA necesita una serie de aplicaciones y bibliotecas previamente instaladas $ sudo yum install kernel-devel gcc-c++ freeglut freeglut-devel libx11-devel mesa-libglu-devel libxmu-devel libxi-devel 7 Consultar la sección de Anexo para conocer la lista de GPU's nvidia compatibles con la tecnología CUDA. 8 Para la descarga del driver dirigirse a la página: 9 Para la descarga del SDK y del ToolKit dirigirse a la página: 12

14 c. Instalar el controlador del dispositivo Iniciaremos con la instalación del controlador de la tarjeta GPU, para ello necesitamos realizarlo con el entorno X deshabilitado por completo y posteriormente instalar el driver, para ello realizaremos el siguiente procedimiento desde una terminal: # Cambiar el runlevel del entorno X al nivel 3. $ sudo vi /etc/inittab # Cambiar: id:5:initdefault a id:3:initdefault # Guardar los cambios, salir y reiniciar el equipo. # Cuando el equipo se reinicie, aparecerá el símbolo del sistema. # Logearse como root e instalar el driver. $ sh NVIDIA-Linux-x86_ run # El sistema realizara la copia del controlador en el sistema. d. Instalación de CUDA Toolkit Ahora se instalara CUDA ToolKit, se modificaran algunos archivos y se exportaran algunas variables de entorno. # Instalar CUDA. Usando las configuraciones por default. $ sh cudatoolkit_4.0.17_linux_64_rhel5.5.run # Crear el archivo: cuda.conf en el directorio: /etc/ld.so.conf.d $ vi /etc/ld.so.conf.d/cuda.conf # Poner en dicho archivo: /usr/local/cuda/lib # Crear un script cuda.sh in the folder /etc/profile.d con: # export LD_LIBRARY_PATH=/usr/local/cuda/lib # export PATH=/usr/local/cuda/bin:${PATH # Guardar los cambios en dicho archivo. # Cambiar el nivel de ejecución del entorno X a 5 y reiniciar. 13

15 e. Instalar CUDA SDK Ahora tendremos que instalar el kit de desarrollo, compilar los ejemplos y comprobar que el compilador de CUDA (nvcc), funciona. # Instalar CUDA SDK $ sh gpucomputingsdk_4.0.17_linux.run # Compilar. $ cd ~/NVIDIA_GPU_Computing_SDK/C $ make # Al terminar los binarios se encontraran en: ~/NVIDIA_GPU_Computing_SDK/C/bin/linux/release # Verificar la instalación: $ nvcc version # La salida en pantalla deberá ser algo similar a: $ nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) NVIDIA Corporation Built on Thu_May_12_11:09:45_PDT_2011 Cuda compilation tools, release 4.0, V f. Prueba de la instalación Si todo fue bien estamos listos para ejecutar alguno de los ejemplos incluidos con CUDA, para ello deberemos dirigirnos a la carpeta donde se encuentran, si se realizó la instalación con los valores por defecto dicho directorio se encuentra en la siguiente ruta: $ cd /~/NVIDIA_GPU_Computing_SDK/C/bin/linux/release/ 14

16 Aquí proponemos ejecutar al menos dos aplicaciones ejemplo, el primero llamado: DeviceQuery (Figura 6), el cual realiza la consulta de las GPU's instaladas en el sistema y despliega las características de cada GPU; el segundo programa recomendado es Ocean, el cual muestra en pantalla la simulación del océano, este último programa es impresionante ya que la simulación del agua es bastante realista (Figura 7). Figura 6. Salida en pantalla de la aplicación de ejemplo: Device Query Figura 7. Ocean, un ejemplo que demuestra la potencia de las GPU s nvidia en el procesamiento de gráficos. 15

17 9. CUDA, un modelo de programación de propósito general A continuación se presenta los conceptos principales detrás del modelo de programación CUDA, los cuales ayudaran a entender en profundidad dicha tecnología. a. Host y Device En el ámbito de CUDA, llamaremos HOST a la unidad de procesamiento central o CPU alojada en nuestro sistema, mientras que llamaremos DEVICE a nuestro dispositivo GPU instalado en el mismo. b. Los CUDA Threads y su Jerarquía dentro de la GPU La unidad mínima de ejecución en CUDA es el Thread (al cual le llamaremos CUDA Thread para hacer distinción del Thread generado por una CPU.), Los Cuda Thread son agrupados por eficiencia en un lotes denominados Block; cuando tenemos un conjunto de Bloques a este se le denomina Grid. La figura 8 muestra esta jerarquía: Figura 8. Jerarquía y organización de los hilos con CUDA. 16

18 c. Jerarquía de Memoria Un CUDA Thread puede acceder a distintos espacios de memoria durante su ejecución; como se muestra en la figura 9, cada CUDA Thread tiene una memoria local que es privada. Cada Block tiene a su vez una porción de memoria compartida visible para todos los CUDA Threads del mismo Block y con el mismo tiempo de vida que el Block. Todos los hilos además, tienen acceso a la memoria global del Device. Existen además dos espacios de memoria que son de solo lectura, accesibles por todos los hilos: La memoria constante y la memoria de textura las cuales son usadas para propósitos específicos en la generación de gráficos. La memoria global, constante y de textura es persistentes a lo largo de la ejecución de un programa en CUDA. Figura 9. Organización de las diferentes capas de memoria que posee una GPU nvidia y el nivel de acceso que cada Thread posee. 17

19 La siguiente tabla muestra una breve descripción de cada tipo de memoria: Memoria local del Thread Memoria Compartida Memoria Constante Memoria de textura Memoria Global del GPU Esta memoria solo es accedida por el propio thread en ejecución, su ámbito de vida dura lo que dura el propio thread en ejecución, además de ser de rápido acceso. La memoria compartida es usada por los CUDA Threads dentro de un block, y su ámbito de vida dura lo que dura la ejecución del block. Esta memoria es de solo lectura y puede ser accedida por todos los hilos en ejecución además de ser es de rápido acceso. Esta memoria es de solo lectura y puede ser accedida por todos los hilos en ejecución además de ser es de rápido acceso. La memoria global es el agente de comunicación intermediario entre la CPU y la GPU, esto significa que un hilo o proceso de la CPU pude escribir en la memoria de la GPU, además de que todos los hilos de la GPU pueden acceder a ella, es una memoria de lectura y escritura tipo RAM, por lo que su acceso es costoso ya que es más lenta que los otros tipos de memoria ya mencionados por ser de tipo chache. d. Kernels CUDA extiende del lenguaje de programación C, lo cual permite al programador definir funciones especiales que tendrán algún comportamiento en particular; en CUDA se le denomina: Kernel a una función que se ejecuta N veces en paralelo por N diferentes CUDA Threads exclusivamente dentro del GPU (o Device en el ámbito CUDA). Un Kernel se define usando el especificador: global en la declaración de la función; cuando se realiza el llamado a dicha función dentro del cuerpo del programa, esta llamada se realiza junto con una configuración de ejecución o arranque: <<<...>>>, esta nueva sintaxis indica el número de CUDA Threads, que, de forma paralela ejecutaran la función kernel. A cada CUDA Thread en el instante mismo de su creación y ejecución se le es asignado un identificador único de hilo, almacenado en una variable especial llamada threadidx mediante la cual es posible acceder al thread en todo momento durante su periodo de ejecución. 18

20 e. Thread Id: Un paso más allá Por conveniencia threadidx es un vector de 3 dimensiones, por lo que un CUDA Thread se puede identificar de forma unidimensional, bidimensional o tridimensional; formando de tal manera un Block unidimensional, bidimensional o tridimensional, esto proporciona una forma natural a la hora de invocar los CUDA Threads en un problema cuyo dominio sea un vector, una matriz o un volumen. El índice de un hilo dentro de un bloque y su ID se relacionan entre si de una manera sencilla: para un bloque unidimensional son iguales; para un bloque de dos dimensiones de tamaño (D x, D y ), el ID de un CUDA Thread de índice (x, y) es (x + y D x ); para un bloque tridimensional de tamaño (D x, D y, D z ), el ID del CUDA Thread de índice (x, y, z) es (x + y D x + z D x D y ). Existe un límite para el número de CUDA Threads por Block, ya que se espera que todos los hilos de un bloque residan en el núcleo del procesador mismo por lo que deberán compartir los recursos de memoria. En las GPU's actuales, un Block de CUDA Threads puede contener hasta 1024 de estos. Sin embargo, un Kernel puede ser ejecutado por múltiples bloques de CUDA Threads de forma que, por lo que el número total de CUDA Threads en un instante de ejecución es igual al número de CUDA Threads por Bloque, por el número de bloques lanzados. Los bloques también se organizan de manera unidimensional, en dos y hasta en tres dimensiones. Por lo tanto el número de bloques de CUDA Threads en un GRID está generalmente determinado por el tamaño de los datos que están siendo procesados. El número de Hilos (CUDA Threads) por Bloque y el número de Bloques por Grid son especificados en la llamada a la función Kernel dentro de la sintaxis: <<< >>>, estos números puede ser de tipo int o dim3 (tres dimensiones). Cada bloque dentro del GRID pueden ser identificados dentro del Kernel lanzado mediante un índice unidimensional, bidimensional, o tridimensional, este índice se obtiene a través de la variable: blockidx. La dimensión del bloque es accesible dentro del Kernel a través de otra variable llamada: blockdim. f. Sincronizando los Threads Los CUDA Threads dentro de un bloque pueden cooperar entre sí mediante el intercambio de datos a través de la memoria compartida y mediante la sincronización de su ejecución para coordinar los accesos a esta memoria. Para ser más precisos, se puede especificar dentro de un programa puntos de sincronización con el llamando a la función: syncthreads(); dicha función actúa como una barrera, es decir: todos y cada uno de los CUDA Thread que ejecuten esta función dentro de algún programa, deberán detenerse en ese punto (el punto de invocación) sin poder ejecutar las siguientes líneas de código hasta que todos los restantes CUDA Thread's hayan alcanzado esa barrera. 19

21 g. CUDA: Una programación Heterogénea El modelo de programación CUDA asume que los CUDA Threads se ejecutan en un Device separado físicamente, dicho dispositivo funciona como un coprocesador para el Host, el cual se encarga de ejecutar un programa escrito en lenguaje C. Por ejemplo, cuando los Kernel's se ejecutan en una GPU y el resto del programa C se ejecuta en una CPU. El modelo de programación CUDA también asume que tanto el Host y el Device administran sus propios espacios de memoria. Por lo tanto, un programa gestiona los espacios de memoria global, constante, y la textura visible para los Kernel's a través de llamadas en tiempo de ejecución de funciones CUDA. Esto incluye la asignación de memoria del Device y su liberación, así como la transferencia de datos entre el Host y la memoria del Device. La siguiente figura muestra el flujo de ejecución de un programa escrito en CUDA, en dicha aplicación se encuentra código que es ejecutado por el Host (CPU) y código paralelo que es ejecutado por el Device (GPU). Figura 10. Invocación y ejecución de una función kernel. 20

22 h. El proceso de compilación El proceso de compilación de un programa en CUDA se lleva a cabo en dos etapas diferentes; la primera de ellas es independiente de la GPU, en esta etapa se separan las secciones de código que se ejecutaran tanto en el CPU como en la tarjeta gráfica, se genera un código llamado PTX (Parallel Thread execution) el cual es el que contiene el conjunto de instrucciones a ejecutar en el dispositivo GPU. La segunda etapa del proceso de compilación está enfocado directamente a la generación de un código objeto para una GPU concreta. NVCC es e driver de compilación que se encarga de monitorizar la llamada a todos los compiladores y herramientas CUDA: cudac, g++, etc. El ejecutable Cuda usa dos bibliotecas dinámicas: Cudart, el cual es la biblioteca de tiempo de ejecución de CUDA y Cuda, el cual es la biblioteca de control de los núcleos del dispositivo GPU. La siguiente imagen muestra las dos fases del proceso de compilación: La etapa Virtual y la etapa Física. Figura 11. Etapas de compilación de un programa en CUDA y su separación en diferentes códigos objeto. 21

23 10. Programación en CUDA Estamos listos para comenzar a realizar algunos programas en CUDA, y así, aprovechar la capacidad de computo de los dispositivos GPU's. a. Suma de dos Números con CUDA Realicemos un programa sencillo: Necesitamos sumar dos números enteros y almacenarlos en una variable y luego mostrar el resultado, para ello es necesario llevar dos números enteros a y b a la memoria de la GPU y luego realizar ahí la suma almacenándola en una variable: dev_c que deberá vivir en la memoria del GPU, por lo cual será necesario reservar memoria en nuestro dispositivo de gráficos, realizar la suma y posteriormente transferir dicho resultado a la memoria del CPU para mostrarlo. Dicho procedimiento necesita de operaciones para reservar memoria, y para realizar la transferencia de datos entre el CPU y el GPU en ambos sentidos. Analicemos el siguiente código. // Programa: Suma de dos números en una GPU. #include <stdio.h> // Función Kernel, la cual se ejecutara en el GPU global void sumanumeros( int a, int b, int *c ) { // Se realiza la suma y se almacena en una variable. *c = a + b; int main( void ) { int c; // Resultado en el CPU int *dev_c; // Resultado en el GPU // Se reserva memoria en el GPU para almacenar el resultado. cudamalloc ((void**)&dev_c, sizeof(int)); // Se invoca a la función Kernel, pasándole como parámetros: // Los valores a sumar y la variable resultado. sumanumeros<<<1,1>>>( 2, 7, dev_c ); // Transferimos dicho resultado cudamemcpy ( &c, dev_c, sizeof(int), cudamemcpydevicetohost ); // Se imprime el resultado en pantalla printf( "2 + 7 = %d\n", c ); // Se libera la memoria reservada en el GPU. cudafree( dev_c ); return 0; 22

24 Notemos que este código en CUDA no distingue mucho de algún código desarrollado en lenguaje C, sin embargo existen algunas funciones nuevas que describiremos a continuación: cudamalloc(): Reserva memoria en el dispositivo GPU. cudamemcpy(): Realiza la copia o transferencia de datos entre el CPU y el GPU, existen cuatro flujos posibles de transferencia de datos, los cuales son: cudamemcpyhosttohost, cudamemcpyhosttodevice, cudamemcpydevicetohost y cudamemcpydevicetodevice. cudafree(): Libera la memoria reservada en el GPU. Al compilar y ejecutar el código, el resultado es el esperado: Figura 12. Salida en pantalla de una aplicación en CUDA que realiza la suma de dos números. 23

25 b. Suma de dos vectores con CUDA Retomemos el ejemplo anterior, y pensemos en dos arreglos de igual tamaño, cada uno con N elementos del mismo tipo; suponga además que se desea sumar cada elemento del arreglo A con su correspondiente elemento del arreglo B y dejar el resultado en un tercer arreglo C, obviamente del mismo tamaño que los anteriores; todo esto de tal forma que las operaciones de suma elemento a elemento se realicen de forma independiente y concurrentemente. Figura 13. Esquema del programa ejemplo, que realiza la suma de dos vectores con CUDA. Para realizar esta tarea es necesario diseñar una función que pueda ejecutarse de forma independiente, es decir, aislada de todo el flujo del programa; Esta función se ejecutara de forma paralela tomando las direcciones de memoria donde se encuentran alojados los vectores A y B, a continuación se generan tantos Cuda Threads como el tamaño de los vectores. Estos hilos especiales entran en contexto y mediante un índice único dentro de todo del dispositivo GPU, se puede controlar que cada Cuda Thread, acceda de forma independiente a una localidad de memoria del vector A, posteriormente sume el elemento alojado ahí con el elemento que encuentre en la misma posición del vector B, para finalmente dejar la suma de ambos dentro del arreglo C. Cuestiones de sincronización y posible corrupción de datos al manipular información de forma paralela se descartan, ya que se garantiza que cada hilo accede inequívocamente a una y solo una localidad de memoria; el tema de la sincronización se toca más adelante. 24

26 A continuación se muestra el código completo del programa. // Programa: Suma de Dos Vectores en CUDA #include <stdio.h> #define N 100 int main(void){ int a[n], b[n], c[n]; int *dev_a, *dev_b, *dev_c; cudamalloc((void**)&dev_a, N * sizeof(int)); cudamalloc((void**)&dev_b, N * sizeof(int)); cudamalloc((void**)&dev_c, N * sizeof(int)); // Llenar los array: 'a' y 'b' en el CPU. for (int i=0; i<n; i++) { a[i] = i; b[i] = i; // Copiar los array: 'a' y 'b' a la memoria del GPU cudamemcpy( dev_a, a, N * sizeof(int), cudamemcpyhosttodevice ); cudamemcpy( dev_b, b, N * sizeof(int), cudamemcpyhosttodevice ); // EJECUTAR EL KERNEL CON 1 BLOQUE Y N HILOS add<<<1,n>>>( dev_a, dev_b, dev_c); // Después de ejecutado el Kernel. Regresar el Resultado. // Copiar el array: 'c' desde el GPU al CPU. cudamemcpy( c, dev_c, N * sizeof (int), cudamemcpydevicetohost); // Imprimir los resultados. for (int i=0; i<n; i++){ printf( " [ %d + %d = %d ] \n",a[i], b[i], c[i] ); // Liberamos la memoria. cudafree( dev_a ); cudafree( dev_b ); cudafree( dev_c ); return 0; // La función principal Kernel. global void add( int *a, int *b, int *c ){ int tid = threadidx.x; if (tid < N) c[tid]= a[tid] + b[tid]; 25

27 El programa anterior reserva en memoria del CPU tres arreglos de tamaño N, referidos mediante las variables: a, b y c. Posteriormente genera apuntadores a memoria mediante las variables: dev_a, dev_b y dev_c; dichos apuntadores refieren a una porción de memoria de tamaño N * int, Esta porción de memoria se reserva mediante la instrucción cudamalloc. Posteriormente se realiza el llenado de los vectores a, b, c que se encuentran almacenados en la memoria del CPU, una vez realizado esto, se procede al copiado de información del CPU al GPU mediante la instrucción: cudamemcpy. Cuando los datos se encuentran en la memoria del device GPU, es entonces cuando se lanza el Kernel paralelo, la configuración de este Kernel es de 1 Bloque con N Hilos. Cuando el Kernel haya terminado, será necesario copiar el vector resultado en la dirección contraria a la primera transferencia de información, es decir: del GPU hacia el CPU, nuevamente con la instrucción cudamemcpy. Una vez realizado esto, bastara con recorrer cada elemento del arreglo c e imprimirlo en pantalla para corroborar que la suma de los elementos fue correcta. c. Array CudaID En términos simples, el siguiente programa crea e inicializa en ceros un arreglo en la memoria del Host (CPU), transfiere una copia de dicho arreglo a la memoria global del Device (GPU) donde es indexado nuevamente por CudaThreads, es decir: se generan tantos CudaThreads como tamaño del arreglo, cada CudaThread deposita el valor de su identificador en la misma localidad del arreglo. Al final se transfiere una copia de este arreglo hacia la memoria del Host para desplegarlo pantalla y verificar el resultado. Figura 14. Esquema de un programa que inicializa en la GPU un arreglo de tamaño N. 26

28 El programa ilustra una forma de obtener para cada CudaThread de diferente bloque, un identificador unidimensional único. Para poder comprender esta idea se necesita saber que los CudaThreads y Blocks pueden ser identificados en una dimensión. Ver Figura15 y Figura16. Figura 15. Identificación en una dimensión del los Blocks dentro de un Grid Figura 16. Identificación en una dimensión del los CudaThreads dentro de un Block 27

29 De esta forma es muy sencillo sincronizar CudaThreads de diferentes Blocks, veamos el siguiente ejemplo: Supongamos que tenemos 2 Blocks con 15 Cuda Threads en cada uno. Para saber el identificador en una dimensión de cada Block se una la propiedad blockidx.x, análogamente para los Cuda Threads se una threadidx.x Tomando en cuenta que para saber el número de CudaThreads que hay dentro de un Block se conoce la propiedad blockdim La siguiente formula demuestra la forma en que se obtiene un identificador unidimensional para cada Cuda Thread: id = BlockIdx.x * blockdim + threadidx.x De esta forma utilizaremos todos los CudaThreads de forma lineal; comencemos identificando los CudaThreads del primer Block según la fórmula, en este caso blockdim = 15 Para el CudaThread 0: Para el CudaThread 1: Para el CudaThread 14: id = ( 0 ) * ( 15 ) + ( 0 ) = 0 id = ( 0 ) * ( 15 ) + ( 1 ) = 1 id = ( 0 ) * ( 15 ) + ( 14 ) = 14 Para el segundo Block tenemos: Para el CudaThread 0: Para el CudaThread 1: Para el CudaThread 14: id = ( 1 ) * ( 15 ) + ( 0 ) = 15 id = ( 1 ) * ( 15 ) + ( 1 ) = 16 id = ( 1 ) * ( 15 ) + ( 14 ) = 29 Así tenemos una serie lineal de identificadores para cada Cuda Thread independientemente del bloque en el que se encuentren. El código del programa se muestra a continuación. 28

30 // Programa: Array CudaId #include<stdio.h> #define N 1000 //Función Principal int main(void) { int arreglo[n]; int *dev_arreglo; int numbloques; int tambloques; // Inicializamos el arreglo. for (int i=0 ; i<n ; i++) arreglo[i]=0; // Reservamos memoria en el Device(GPU) para almacenar el arreglo. cudamalloc( (void**) &dev_arreglo, N * sizeof(int) ); // Copiamos el arreglo al Device(GPU) cudamemcpy( dev_arreglo, arreglo, N*sizeof(int), cudamemcpyhosttodevice); // Deseamos tener 20 hilos por bloque tambloques = 20; numbloques = N/tamBloques; //Invocamos al kernel. kernel<<<numbloques, tambloques>>>(dev_arreglo); // Copiamos el arreglo indexado por los Threads al Host(CPU) cudamemcpy( &arreglo, dev_arreglo, N * sizeof(int), cudamemcpydevicetohost); // Desplegamos el arreglo printf("el Arreglo es: \n\n"); for (int i=0 ; i<n ; i++) printf(" [ %i ] -> %d \n",i, arreglo[i]); printf("\n"); //Liberamos memoria cudafree(dev_arreglo); return 0; // Definición de la función Kernel que se ejecuta en el Device(GPU) global void kernel(int *array) { int id = blockidx.x * blockdim.x + threadidx.x; array[id]=id; 29

31 d. Reducción de un Vector con Memoria Compartida Suponga que se tiene un vector lineal de tamaño N y que se desea sumar todos y cada uno de los elementos almacenados en dicho arreglo; la primera idea para resolver el problema usando CUDA seria generar tantos hilos como el tamaño del arreglo y que cada hilo sumara el elemento del arreglo que corresponda con su id a una variable, si nos detenemos a pensar un momento esta situación, es probable que exista corrupción de datos al no existir una sincronización correcta en la manipulación de la variable que almacenara el resultado final. Aquí se propone el siguiente método para realizar la tarea de forma concurrente, tomando en cuenta la situación de la probable corrupción de datos. El siguiente programa hace uso de la memoria compartida, a la cual podrán acceder todos los hilos que pertenezcan al mismo bloque; en primera instancia, se genera un arreglo en memoria compartida de tamaño N, posteriormente se generan N hilos, los cuales depositaran el valor de su identificador en la misma localidad de memoria del vector. Es necesario esperar a que todos los hilos depositen el valor de su identificador antes de comenzar a realizar la suma de los elementos, para garantizar esto, se hace uso de la instrucción: syncthreads(). Una vez garantizada la correcta inicialización del vector, nuestro algoritmo propone hacer uso solamente de la mitad de los cuda threads creados, para ello cada cuda thread tendrá que sumar dos posiciones de memoria del arreglo, el que corresponde a su identificador (idthread) y el que corresponde a la posición [ (N/2) + idthread ], dejando el resultado de esta suma parcial en la localidad correspondiente a su identificador. Con este algoritmo en cada vuelta se van prescindiendo de mas cuda Threads, al final se tendrá en la posición 0 del vector el resultado final, solo habrá que trasladarla a la memoria del CPU para mostrarla en pantalla; también en cada vuelta será necesario utilizar la sincronización para garantizar que todos los hilos hayan sumado sus correspondientes localidades. La siguiente imagen muestra el desarrollo del algoritmo, indicando que en cada paso habrá que hacer uso del llamado a syncthreads(), el cual como ya se ha mencionado, actúa como una barrera para los Cuda Threads, impidiendo que se ejecute un bloque de instrucciones hasta que todos los hilos activos lleguen al punto de invocación de la barrera. Figura 17. Esquema del programa que reduce un vector mediante la memoria compartida. 30

32 El código del programa se muestra a continuación. #include<iostream> #define N 256 ////////////////////////////////////////////////// // Función que se ejecutara en el Device (GPU) // ////////////////////////////////////////////////// global void reduce(float* total) { // Arreglo de tamaño N en Memoria Compartida. shared float array[n]; // Obtenemos el Identificador del Hilo. int id = threadidx.x; // Inicializamos el arreglo. if(id < N) array[id]=id; syncthreads(); // Esperamos a que todos los hilos lleguen hasta este punto. // Se suman los elementos del arreglo. int i = N/2; // Solo usaremos la mitad de los hilos. while (i!= 0) { if (id < i) array[id] += array[id + i]; syncthreads(); // Esperamos a todos los threads hasta este punto. i /= 2 if (id == 0) // El hilo cero se encarga de almacenar el resultado final. *total=array[0]; int main( void ) { // Resultado de la suma en el Host (CPU) float host_total=0; // Resultado de la suma en el Device (GPU) float *dev_total; // Reservamos memoria en el Device (GPU) para la suma total. cudamalloc((void**)&dev_total, sizeof(float)); // Invocamos a la función Kernel, con 1 Bloque y N Hilos. reduce<<<1,n>>>(dev_total); // Copiamos la suma total del Device al Host. cudamemcpy( &host_total, dev_total, sizeof(float),cudamemcpydevicetohost ); // Mostramos el resultado en Pantalla. printf("la suma es: %f \n",host_total); // Liberamos la memoria. cudafree(dev_total); return 0; 31

33 e. Inicialización de un arreglo usando memoria de textura A continuación se muestra el uso de la memoria de textura la cual era utilizada originalmente antes de la programación de propósito general, para la declaración de estructuras denominadas texturas que son vectores cuyos datos representan color, profundidad, ancho de línea o tamaño de punto y son utilizados en la programación y manipulación de imágenes que pueden ser referenciados en una, dos o tres dimensiones, mejorando el rendimiento y la velocidad de acceso por los CudaThreads en aplicaciones graficas puesto que como ya hemos mencionado esta memoria de tipo cache. Cuda implementa una serie de funciones con las cuales podemos hacer uso de la memoria de textura: texture<float> : Declaración de una variable que residirá en la memoria de textura. cudabindtexture () : Función que mapea datos en un espacio de memoria de textura. cudabindtexture2d() : Mapea datos en la memoria de textura, son referenciados en espacio de memoria en dos dimensiones. cudaunbindtexture() : Función que des referencia los datos, liberándolo de la memoria. tex1dfetch() : Realiza una búsqueda sin filtros en textura usando coordenadas. El nivel de detalle es proporcionado por el último componente del vector de coordenadas. La siguiente aplicación hace uso de la memoria de textura para realizar una tarea específica; la idea general de este programa es crear e inicializar un arreglo en la memoria del CPU, transferir dicho arreglo a la memoria global de GPU. Crear una variable en la memoria de textura y copiar el valor de la variable en cada casilla del arreglo. En la GPU son lanzados tantos CudaThreads como tamaño del arreglo, cada CudaThread copia el valor de la variable de textura e inicializa cada casilla correspondiente del arreglo en la memoria global de la GPU. Figura 18. Esquema del programa que hace uso de la memoria de textura de la GPU. 32

34 A continuación se muestra el código del programa // Programa: Inicialización de un arreglo usando memoria de textura #include<stdio.h> #define N 10 texture<float> textvar; int main(){ float arreglo[n]; float *dev_arreglo; float *dev_textvar; float valor=111; // Inicializamos el Arreglo. for (int i=0 ; i<n ; i++) arreglo[i]=1; // Reservamos memoria en el GPU para almacenar el arreglo. cudamalloc( (void**) &dev_arreglo, N * sizeof(float) ); // Copiamos el arreglo al device. cudamemcpy(dev_arreglo,arreglo,n*sizeof(float),cudamemcpyhosttodevice); //Reservamos memoria para la variable de textura cudamalloc( (void**) &dev_textvar, sizeof(float) ); //copiamos el valor de la variable de text cudamemcpy(dev_textvar,&valor,sizeof(float),cudamemcpyhosttodevice); //Indicamos q nuestra variable es de textura cudabindtexture( NULL, textvar, dev_textvar, sizeof(float)); //Llamada al kernel textura<<<1,n>>>(dev_arreglo); //Copiamos el arreglo, de regreso al CPU cudamemcpy(arreglo,dev_arreglo,n*sizeof(float),cudamemcpydevicetohost); //Desplegamos el arreglo for (int j=0 ; j<n ; j++) printf("\n[%d] --> %f",j,arreglo[j]); printf("\n"); //Liberamos memoria cudafree(dev_arreglo); cudafree(dev_textvar); cudaunbindtexture( textvar ); //Kernel global void textura(float *array){ int id = threadidx.x; array[id] = tex1dfetch(textvar,0); syncthreads(); 33

35 f. Inicialización de un arreglo usando memoria constante Existe otra memoria dentro de la GPU llamada memoria constante, esta memoria es accedida por todos los CudaThreads en ejecución, sirve como memoria auxiliar en la implementación de técnicas de renderización y aplicaciones gráficas. De igual forma que para la memoria de textura, existen funciones para manipular el uso de la memoria constante. constant : Calificador de tipo de variable, la variable definida reside en la memoria constante, su tiempo de vida es el de la aplicación y es accedida por todos los CudaThreads. cudamemcpytosymbol() : Copia un área de memoria del Host a un espacio de memoria global o constante. El siguiente programa ilustra un posible uso de esta memoria; se crea e inicializa un arreglo de tamaño N en la memoria del CPU que es transferido a la memoria global de la GPU. De igual forma es creado otro arreglo y transferido a la memoria constante de la GPU. En la GPU son lanzados tantos CudaThreads como tamaño del arreglo que se encargan de mapear el valor de las casillas del arreglo en memoria constante al arreglo en memoria global. Figura 19. Uso de la memoria constante en un programa que inicializa un arreglo de tamaño N. 34

36 A continuación se muestra el código del programa // Programa: Inicialización de un arreglo usando memoria constante #include<cuda_runtime.h> #include<cuda.h> #include<stdio.h> #include<stdlib.h> const int N=10; constant float const[n]; int main(){ int arreglo[n]; int *dev_arreglo; float valor[n]; // Inicializamos el Arreglo. for (int i=0 ; i<n ; i++) arreglo[i]=1; // Reservamos memoria en el GPU para almacenar el arreglo. cudamalloc( (void**) &dev_arreglo, N * sizeof(int) ); // Copiamos el arreglo al device. cudamemcpy(dev_arreglo,arreglo,n*sizeof(int),cudamemcpyhosttodevice); //Reservamos memoria en el GPU para el valor de la memoria constante cudamalloc((void**)&dev_valor, sizeof(int)); //Copiamos a la memoria global de la GPU el valor cudamemcpy(dev_valor,&valor,sizeof(int),cudamemcpyhosttodevice); //Copiamos a memoria constante cudamemcpytosymbol(const,valor, N * sizeof(float)); //Llamada al kernel constante<<<1,n>>>(dev_arreglo); //Copiamos el arreglo, de regreso al CPU cudamemcpy(arreglo,dev_arreglo,n*sizeof(int),cudamemcpydevicetohost); //Desplegamos el arreglo for (int j=0 ; j<n ; j++) printf("\n[%d] --> %d",j,arreglo[j]); printf("\n"); //Liberamos memoria cudafree(dev_arreglo); cudafree(const); //Kernel global void constante(int *array){ int id = threadidx.x; array[id] = const[id]; syncthreads(); 35

37 g. Inicialización de un arreglo usando pthreads Los Pthreads son definidos por el estándar POSIX, es un thread (hilo o proceso ligero) que fue normalizado por los estándares y tiene la propiedad de ser dinámico, ya que se puede crear en cualquier instante de tiempo. Su funcionalidad es ejecutar una tarea o sección de código independiente de la aplicación, es decir que lleva un nivel de paralelismo donde con múltiples pthreads se puede hacer más de una tarea a la vez. Pthread es una estructura transparente al usuario y su particularidad puede ser manipulada por un conjunto de funciones; a continuación se muestran varias de ellas. Pthread_create : Crea un pthread con una tarea determinada. Pthread_exit: Causa la finalización del pthread que lo invoca. Pthread_join: Hace que el pthread que invoca esta función espere a que termine un pthread determinado. Pthread_cancel: Solicita la terminación de otro pthread. Haciendo uso de algunas funciones que manipulan la funcionalidad de los pthreads y de las herramientas que nos proporciona Cuda para controlar la GPU, se desarrolla este programa que lleva a cabo el lanzamiento de 2 Pthreads por el flujo (proceso) principal de la aplicación. Cada Pthread ejecuta un segmento de código independiente e invoca a la GPU. Los Pthreads crean e inicializan un arreglo. El Pthread 1 lo inicializa en 1 s, es transferida una copia del arreglo a la memoria global del Device donde varios CudaThreads lo reinicializan en 0 s, finalmente es transferida una copia de regreso a la memoria del Host. Análogamente se realiza el mismo proceso para el Pthread 2 pero con valores inversos. Figura 20. Uso de los Pthreads con CUDA para inicializar dos arreglos. 36

38 A continuación mostramos el código del programa // Programa: Inicialización de un arreglo usando Pthreads #include <stdio.h> #include <cuda.h> #include <pthreads.h> #define N 10 //KERNEL 1 (esta función se ejecuta en la GPU) global void Gpu1(int *array){ int id = threadidx.x; array[id] = id * 0; syncthreads(); //KERNEL 2 (esta función se ejecuta en la GPU) global void Gpu2(int *array){ int id = threadidx.x; array[id] = id * 0 + 1; syncthreads(); //Procedimiento que ejecutara el primer pthread void Hilo1Gpu1(){ int arreglo[n]; int *dev_arreglo; // Inicializamos el Arreglo. for (int i=0 ; i<n ; i++) arreglo[i]=1; // Reservamos memoria en el GPU para almacenar el arreglo. cudamalloc( (void**) &dev_arreglo, N * sizeof(int) ); // Copiamos el arreglo al device cudamemcpy( dev_arreglo, arreglo, N*sizeof(int), cudamemcpyhosttodevice); // Deseamos tener N CudaThread por bloque tambloques = N; numbloques = 1; //Invocamos al kernel. Gpu1<<<numBloques, tambloques>>>(dev_arreglo); // Copiamos el resultado cudamemcpy( &arreglo, dev_arreglo, N * sizeof(int), cudamemcpydevicetohost); // Desplegamos el arreglo printf("el Arreglo es: \n\n"); for (int i=0 ; i<n ; i++) printf(" [ %i ] -> %d \n",i, arreglo[i]); printf("\n"); pthread_exit(0); 37

39 Continuación //Procedimiento que ejecutara el segundo pthread void Hilo2Gpu2(){ int arreglo[n]; int *dev_arreglo; // Inicializamos el Arreglo. for (int i=0 ; i<n ; i++) arreglo[i]=0; // Reservamos memoria en el GPU para almacenar el arreglo. cudamalloc( (void**) &dev_arreglo, N * sizeof(int) ); // Copiamos el arreglo al device cudamemcpy( dev_arreglo, arreglo, N*sizeof(int), cudamemcpyhosttodevice); // Deseamos tener N CudaThread por bloque tambloques = N; numbloques = 1; //Invocamos al kernel. Gpu2<<<numBloques, tambloques>>>(dev_arreglo); // Copiamos el resultado cudamemcpy( &arreglo, dev_arreglo, N * sizeof(int), cudamemcpydevicetohost); // Desplegamos el arreglo printf("el Arreglo es: \n\n"); for (int i=0 ; i<n ; i++) printf(" [ %i ] -> %d \n",i, arreglo[i]); printf("\n"); pthread_exit(0); //Funcion principal int main(){ //definición de variables de tipo pthreads pthread_t hilo1,hilo2; //creación de los pthread's pthread_create(&hilo1, NULL, Hilo1Gpu1, NULL); pthread_create(&hilo2, NULL, Hilo2Gpu2, NULL); //espera de los hilos pthread_join(hilo1, NULL); pthread_join(hilo2, NULL); return 0; 38

40 Compilación: # Compilación $ nvcc -lrt lpthread oregen.cu o destino 11. Controlando más de un GPU (Programación MultiGPU) Hasta este momento, todos los ejemplos presentados muestran una característica en particular: son lineales y bloqueantes, es decir, una vez que se ejecuta el programa y la función Kernel es llamada, la aplicación se suspende hasta que dicha función termina, regresando el control a la aplicación que la invoco y ejecutando las siguientes líneas de código. Supongamos que se cuenta con más de un dispositivo GPU instalado en el mismo ordenador, es deseable poder sumar las capacidades de computo de cada uno de estos dispositivos para realizar computo masivamente paralelo y poder resolver problemas que requieran realizar cálculos intensivos sobre una gran cantidad de información. Pero no solo es deseable poder utilizar los dispositivos GPU s, sino también poder incrementar la capacidad de cálculo haciendo uso de los CPU s del sistema. Presentamos a continuación dos posibles formas de lograr esto; la primera opción es utilizar las prestaciones de los llamados Pthreads y la segunda es usando las características de OpenMp. a. OpenMP + Cuda OpenMp es una interfaz de programación de aplicaciones y el estándar actual de la programación paralela; permite añadir concurrencia a programas escritos en lenguaje C, C++ y Cuda trabajando sobre un modelo de ejecución conocido como: Fork-Join, este modelo es un paradigma de programación en el cual una tarea extensa se divide en N hilos (Fork), cada hilo realizara una porción de tarea de menor peso, para luego unificar el resultado final en uno solo (Join); esto se logra mediante pragmas, directivas, llamadas a funciones y variables, que le indican al compilador que porción debe paralelizar en hilos. 39

41 Figura 21. El modelo de programación: Fork-Join. El esquema general de un programa en OpenMP + Cuda, sería el siguiente: Figura 22. Esquema básico de un programa paralelizado con Open MP y CUDA. 40

42 En primera instancia, es necesario incluir la librería que aporta los pragmas de openmp, dichas funciones se encuentran en: omp.h. Una vez iniciado el cuerpo del programa, será necesario detectar el número de GPU s instalados en el sistema así como el número de núcleos de procesador, posteriormente se tendrían que inicializar los datos a tratar dentro de la memoria del CPU para luego establecer el número de threads de la sección paralela de openmp. Una vez realizado todo esto la aplicación llega a su bloque paralelo (pragma_openmp), en esta sección se generan tantos threads como se haya establecido previamente, cierta cantidad de threads (el mismo número que GPU s) toman control de los dispositivos gráficos, copiando una porción de los datos a tratar y llevándola hacia la memoria del dispositivo para lanzar su respectivo Kernel, los threads restantes toman una porción de datos y trabajan sobre ellos mediante el uso de los núcleos del CPU. Al finalizar todas las subtareas, se busca unificar el resultado a fin de completar la tarea principal en su totalidad. Existen un conjunto de funciones en CUDA para la administración de los GPU s, con estas funciones es posible determinar el número de dispositivos instalados en el sistema, así como tomar el control de cualquiera de estas tarjetas gráficas. cudagetdevicecount(int numdevice): Devuelve en numdevice el numero de GPU's instalados. cudasetdevice(int Device): Selecciona el GPU referido por Device [0-x]. cudagetdevice(int gpuid): Devuelve en gpuid el número de dispositivo usado actualmente. cudagetdeviceproperties(cudadeviceprop dev_prop, int gpu_id): Almacena en dev_prop, toda la información del gpu con id: gpu_id. cudadevicereset(): Destruye y limpia todos los recursos asociados con el dispositivo actual en el proceso actual. También tenemos un conjunto de pragmas para OpenMp, mediante el cual podemos administrar los núcleos del CPU y el número de Hilos de la sección paralela: omp_get_num_procs(): Devuelve el número de procesadores en el sistema. omp_set_num_threads(num_threads): Establece el número de threads en la sección paralela. omp_get_thread_num(): Devuelve el identificador del hilo [0, num_threads-1] La compilación de un programa con OpenMP y CUDA es un poco distinta ha como se venía manejando, ya que habrá que indicarle al compilador de CUDA: nvcc, que se incluirá la librería de open MP: # Compilación Hibrida OpenMp + Cuda $ nvcc -Xcompiler fopenmp origen.cu -o destino # Xcompiler: Especifica directivas de compilación para nvcc. # fopenmp: Especifica que el archivo fuente contiene pragmas de openmp 41

43 A continuación se muestra un ejemplo de un programa hibrido con OpenMP y CUDA; Se verifican mediante funciones de CUDA y Open MP el número de GPU s y CPU s Instalados, posteriormente se genera un número determinado de hilos; al entrar en la sección paralela el hilo K toma control del GPU K; como los gpu s pueden ejecutar funciones kernel diferentes o la misma (en nuestro ejemplo la función kernel no efectúa ninguna tarea.), trabajando sobre una sección de datos, los demás hilos ejecutan funciones diferentes. Un diagrama que ejemplifica el flujo de la aplicación es el siguiente, se destaca la sección paralela que existe mediante la implementación del estándar openmp. Figura 23. Esquema del programa ejemplo sobre la combinación de las tecnologías Open MP y CUDA. 42

44 El código del programa se muestra a continuación, // Programa: EasyMp.cu // Se incluye la librería omp.h, para trabajar con los pragmas de openmp #include <omp.h> #include <stdio.h> // Función Kernel. NO realiza ninguna tarea. global void kernel ( ) { // Función que ejecutaran los threads que no tengan GPU. void sub_rutina ( ) { // Función para imprimir en pantalla el número de CPU s y GPU s instalados. void infocpu_gpu (int num_gpus, int num_cpus) { // Mostramos en Pantalla el numero de Procesadores Físicos // y el número de GPU's instalados en el sistema. printf("\n\n=============== [ INFO ] ===============\n\n"); printf(" CPU's en el sistema: [ %i ] \n", num_cpus); printf(" GPU's en el sistema: [ %i ] \n", num_gpus); printf("\n========================================\n\n"); // El programa termina al no existir GPU s instalados. void checkgpu(int num_gpus) { if (num_gpus<1) { printf("\n\n No se detectaron GPUS en el sistema. \n\n"); exit (0); // Inicio del cuerpo principal del programa. int main(){ int num_gpus=0; int num_cpus=0; int num_threads=0; // Obtenemos el numero de CPU's y GPU's num_cpus = omp_get_num_procs(); cudagetdevicecount(&num_gpus); // Verificamos GPUS Instalados. checkgpu(num_gpus); // Mostramos información infocpu_gpu(num_gpus, num_cpus); // Establecemos el numero de threads a crear. (num_cpus<num_gpus)? num_threads = num_gpus : num_threads = num_cpus; // Arreglo para administrar el control de las GPU s int gpulist[num_threads]; for(int i=0; i<num_threads;i++) gpulist[i]=-1; for(int i=0; i<num_gpus;i++) gpulist[i]=i; // Creamos tantos openmp Threads como CPU's en el sistema. omp_set_num_threads(num_threads); 43

45 ///////////////////////////////////////// // Comienza la sección paralela openmp // ///////////////////////////////////////// #pragma omp parallel { unsigned int cpu_thread_id = omp_get_thread_num(); int gpu_id=-1; // Los hilos seleccionan un GPU if(gpulist[cpu_thread_id]!=-1) { cudasetdevice(cpu_thread_id); cudagetdevice(&gpu_id); // Los hilos con GPU obtienen información de su GPU. if (gpu_id!= -1) { cudadeviceprop dev_prop; cudagetdeviceproperties(&dev_prop, gpu_id); printf ( "CPU Thread [ %i ], está usando el GPU [ %i ]: %s, Ejecutando un Kernel\n", cpu_thread_id, gpu_id, dev_prop.name ); kernel<<<1,1>>>(); cudadevicereset(); else { printf("cpu Thread [ %i ], No tengo GPU, Ejecuto otra subrutina \n",cpu_thread_id ); printf("\n\n"); return 0; Para compilar este programa es necesario ejecutar desde la línea de comandos: # Compilación $ nvcc -Xcompiler fopenmp EasyMp.cu -o EasyMp 44

46 Un ejemplo más completo será el siguiente: suponga que se tiene un arreglo en la memoria del CPU de tamaño: N x 16,777,216 enteros, donde N es igual al número de GPU s instalados en el sistema. Se genera en la sección paralela tantos threads como dispositivos gráficos se tengan y a continuación cada uno de estos threads toma el control de su respectivo GPU, copiando una porción equitativa de datos del array inicial a la memoria global de del dispositivo gráfico que controla; una vez realizado esto, cada GPU lanza el mismo Kernel, el cual incrementa cada elemento de su porción de arreglo en una constante establecida, terminada esta tarea se copia la sección de datos modificada de vuelta al vector original. Los Threads generados en la sección paralela terminan, el hilo inicial despliega el array modificado en pantalla. El siguiente esquema muestra la idea completa del flujo del programa. Figura 24. Esquema de la aplicación que realiza el uso de Open MP para lanzar Threads que tomen control de GPU s para realizar una tarea en común. 45

47 El código del programa se muestra a continuación. #include <omp.h> #include <stdio.h> //Incrementa un valor constante en cada elemento del arreglo global void addconstante(int *gpu_array, int incremento) { int id_thread = blockidx.x * blockdim.x + threadidx.x; gpu_array[id_thread] += incremento; // Verifica el trabajo de los GPU's; Retorna 0 si fallo, 1 en otro caso. int checkresultado(int *array, int tam_array, int incremento) { for (int i=0; i<tam_array; i++) { if (array[i]!= i + incremento) return 0; return 1; int main(int argc, char *argv[]) { int num_gpus = 0; // Numero de GPU's cudagetdevicecount(&num_gpus); // Obtenemos el Número total de GPU's if (num_gpus<1){ printf("no se encontraron GPU's en el sistema."); return 1; // Mostramos en Pantalla el numero de Procesadores Físicos // y el número de GPU's instalados en el sistema. printf("\n\n=============== [ INFO ] ===============\n\n"); printf(" CPU's en el sistema: [ %i ] \n", omp_get_num_procs()); printf(" GPU's en el sistema: [ %i ] \n", num_gpus); for(int i = 0; i<num_gpus; i++) { cudadeviceprop dev_prop; cudagetdeviceproperties(&dev_prop, i); printf(" GPU[ %i ]: %s \n", i, dev_prop.name); printf("\n========================================\n\n"); // Aquí se comienza a trabajar con los datos en el CPU unsigned int t_datos = num_gpus * ; unsigned int num_bytes = t_datos * sizeof(int); int *cpu_array; int incremento = 5; // Reservamos memoria para el arreglo en el cpu. cpu_array = (int *) malloc(num_bytes); if(cpu_array==null) { printf("error: No se puede reservar memoria en el CPU. "); return 1; // Inicializamos los datos del arreglo en el cpu. for(int i=0; i<t_datos; i++) cpu_array[i]=i; // Ejecutaremos tantos openmp Threads como GPU's en el sistema. omp_set_num_threads(num_gpus); 46

48 // Comienza la sección paralela openmp #pragma omp parallel { unsigned int cpu_thread_id = omp_get_thread_num(); unsigned int total_cpu_threads = omp_get_num_threads(); // Asociamos cada Thread del CPU con un GPU int gpu_id=-1; cudasetdevice(cpu_thread_id % num_gpus); cudagetdevice(&gpu_id); printf("cpu Thread [ %i ], esta usando el GPU [ %i ] \n", cpu_thread_id, gpu_id); // Apuntador a un arreglo int *device_array; // Apuntador a la porción del arreglo correspondiente int *porcion_array = cpu_array + cpu_thread_id * t_datos / total_cpu_threads; int nbytes_por_kernel = num_bytes / total_cpu_threads; // Numero de CUDA threads por Bloque int gpu_threads=1024; // Se calculan los bloques por cada GPU. int gpu_bloques=t_datos / (gpu_threads*total_cpu_threads); //Se reserva memoria en el GPU cudamalloc((void **)&device_array,nbytes_por_kernel); cudamemset(device_array, 0, nbytes_por_kernel); cudamemcpy ( device_array, porcion_array, nbytes_por_kernel, cudamemcpyhosttodevice ); addconstante<<<gpu_bloques, gpu_threads>>>(device_array, incremento); cudamemcpy ( porcion_array, device_array, nbytes_por_kernel, cudamemcpydevicetohost ); cudafree(device_array); cudadevicereset(); // Termina la sección paralela printf("\n Se procesaron: [%i] Datos. \n", t_datos); printf(" \n"); if(cudasuccess!= cudagetlasterror()) printf("%s\n", cudageterrorstring(cudagetlasterror())); if(checkresultado(cpu_array, t_datos, incremento)) printf("exito!!\n\n"); else printf("fallo!!\n"); free(cpu_array); return 0; 47

49 b. Pthreads + Cuda A continuación se muestra un ejemplo de la implementación de estas tecnologías. El programa hace uso de 2 GPU s manipuladas por un Pthread cada una, cada Pthread inicializa un arreglo y lo transfiere a la memoria global de la GPU donde es indexado por los CudaThreads y reinicializado. Al final los arreglos son transferidos de regreso a la memoria del CPU y mostrados en pantalla. Cabe señalar que se lleva a cabo un paralelismo tanto en hilos (Pthreads) del Host como en los hilos (CudaThreads) del Device. Figura 25. Esquema del programa que demuestra el control de dos GPU s usando Pthreads. A continuación se muestra el código del programa. 48

50 /*############################################################################## ### - - PROGRAMA QUE LANZA 2 PTHREADS Y CADA PTHREAD CONTROLA 1 GPU - - ### ##############################################################################*/ #include <stdio.h> #include <cuda.h> #include <pthread.h> #define N 10 #define numbloques 1 #define tambloques 10 //KERNEL 1 (esta función se ejecuta en la 1Â GPU) global void Gpu1(int *array){ int id = threadidx.x; array[id] = id * 0; //KERNEL 2 (esta función se ejecuta en la 2Â GPU) global void Gpu2(int *array){ int id = threadidx.x; array[id] = id * 0 + 1; void *hilo1gpu1(void *uno){ int arreglo[n]; int *dev_arreglo; int gpu_id=-9; //SELECCIONAMOS EL GPU CON EL QUE TRABAJAREMOS. cudasetdevice(0); // INICIALIZAMOS EL ARREGLO. for (int i=0 ; i<n ; i++) arreglo[i]=1; //RESERVAMOS MEMORIA EN EL GPU PARA ALMACENAR EL ARREGLO. cudamalloc( (void**) &dev_arreglo, N * sizeof(int) ); //COPIAMOS EL ARREGLO A LA MEMORIA DEL GPU. cudamemcpy( dev_arreglo, arreglo, N*sizeof(int), cudamemcpyhosttodevice); //INVOCAMOS AL KERNEL. Gpu1<<<numBloques, tambloques>>>(dev_arreglo); //COPIAMOS EL ARREGLO QUE ESTA EN LA MEMORIA DEL GPU A LA MEMORIA DEL CPU. cudamemcpy( &arreglo, dev_arreglo, N * sizeof(int), cudamemcpydevicetohost); //OBTENEMOS EL IDENTIFICADOR DEL GPU QUE ESTA EN EJECUCION cudagetdevice(&gpu_id); //OBTENEMOS LAS PROPIEDADES DEL GPU. cudadeviceprop dev_prop; cudagetdeviceproperties(&dev_prop, gpu_id); printf("\nsoy el hilo --%d-- controlando ",uno); printf("gpu[ %d ]: %s \n",gpu_id, dev_prop.name); //DESPLEGAMOS EL ARREGLO. for (int i=0 ; i<n ; i++) printf(" [ %i ] -> %d \n",i, arreglo[i]); printf("\n"); //INDICAMOS QUE EL HILO HA TERMINADO. pthread_exit(0); 49

51 Continuación void *hilo2gpu2(void *dos){ int arreglo[n]; int *dev_arreglo; int gpu_id=-9; //SELECCIONAMOS EL GPU CON EL QUE TRABAJAREMOS. cudasetdevice(1); //INICIALIZAMOS EL ARREGLO. for (int i=0 ; i<n ; i++) arreglo[i]=0; //RESERVAMOS MEMORIA EN EL GPU PARA ALMACENAR EL ARREGLO. cudamalloc( (void**) &dev_arreglo, N * sizeof(int) ); //COPIAMOS EL ARREGLO A LA MEMORIA DEL GPU. cudamemcpy( dev_arreglo, arreglo, N*sizeof(int), cudamemcpyhosttodevice); //INVOCAMOS AL KERNEL. Gpu2<<<numBloques, tambloques>>>(dev_arreglo); //COPIAMOS EL ARREGLO QUE ESTA EN LA MEMORIA DEL GPU A LA MEMORIA DEL CPU. cudamemcpy( &arreglo, dev_arreglo, N * sizeof(int), cudamemcpydevicetohost); //OBTENEMOS EL IDENTIFICADOR DEL GPU QUE ESTA EN EJECUCION cudagetdevice(&gpu_id); //OBTENEMOS LAS PROPIEDADES DEL GPU. cudadeviceprop dev_prop; cudagetdeviceproperties(&dev_prop, gpu_id); printf("\nsoy el HILO --%d-- controlando ",dos); printf("gpu[ %d ]: %s \n",gpu_id, dev_prop.name); //DESPLEGAMOS EL ARREGLO. for (int i=0 ; i<n ; i++) printf(" [ %i ] -> %d \n",i, arreglo[i]); printf("\n"); //INDICAMOS QUE EL HILO HA TERMINADO. pthread_exit(0); int main(){ int uno=0,dos=1,num_gpus = 0; //DEFINICION DE VARIABLES DE TIPO THREADS pthread_t hilo1,hilo2; //OBTENEMOS EL NUMERO TOTAL DE GPU's cudagetdevicecount(&num_gpus); printf("el numero de GPU's es : %d\n",num_gpus); //CREAMOS LOS HILOS pthread_create(&hilo1, NULL,hilo1gpu1, (void *)uno); pthread_create(&hilo2, NULL,hilo2gpu2, (void *)dos); //ESPERAMOS POR LOS HILOS pthread_join(hilo1, NULL); pthread_join(hilo2, NULL); return 0; 50

52 12. Implementación de un Clúster de GPU s a. Clúster de Computadoras Podríamos definir a un clúster como un conjunto de computadoras interconectadas entre sí mediante hardware de red y que se comportan como si fueran una única computadora. En la actualidad los clúster de computadoras son utilizados mayormente en la resolución de problemas de las ciencias e ingenierías. La tecnología en clúster ha evolucionado de tal modo que su ámbito de aplicación es cada vez más grande, desde aplicaciones de supe computo, servidores web, servidores de bases de datos de alto rendimiento, servidores de correo electrónico, entre muchas otras. El desarrollo de la tecnología en clúster, surge como resultado de la disponibilidad de microprocesadores cada vez más económicos y con mayor rendimiento, redes de alta velocidad, el desarrollo de software para cómputo distribuido de alto rendimiento y la creciente necesidad de potencia de cálculo para cierto tipo de aplicaciones. Los clústeres son empleados principalmente para mejorar el rendimiento y la disponibilidad de ciertos servicios que regularmente es provisto por una sola computadora, de un clúster se espera presente cualquiera o una combinación de los siguientes servicios: Alto Rendimiento Alta Disponibilidad Balanceo de Carga Escalabilidad La construcción de un clúster es muy flexible, ya que pueden todas las computadoras que lo componen pueden tener la misma configuración de hardware y sistema operativo, a este tipo de clúster se le llama: Homogéneo; diferente rendimiento pero con arquitecturas y sistemas operativos similares: clúster semihomogéneo y por ultimo pueden tener diferente hardware y sistema operativo y se le llamaría clúster heterogéneo. Para que un clúster funcione como tal, será necesario proveer un sistema de manejo de clúster, el cual se encargara de interactuar con el usuario, administrar los procesos que corre en él y lograr la comunicación eficiente de cada nodo (computadora) que compone el clúster. El término clúster tiene diferentes connotaciones para diferentes grupos de personas. Los tipos de clústeres, establecidos de acuerdo con el uso que se dé y los servicios que ofrecen, determinan el significado del término para el grupo que lo utiliza. Los clústeres pueden clasificarse según sus características: HPCC (High Performance Computing Clústers: clústeres de alto rendimiento). HA o HACC (High Availability Computing Clústers: clústeres de alta disponibilidad). 51

53 HT o HTCC (High Throughput Computing Clústers: clústeres de alta eficiencia). Alto rendimiento: Son clústeres en los cuales se ejecutan tareas que requieren de gran capacidad computacional, grandes cantidades de memoria, o ambos a la vez. El llevar a cabo estas tareas puede comprometer los recursos del clúster por largos periodos de tiempo. Alta disponibilidad: Son clústeres cuyo objetivo de diseño es el de proveer disponibilidad y confiabilidad. Estos clústeres tratan de brindar la máxima disponibilidad de los servicios que ofrecen. La confiabilidad se provee mediante software que detecta fallos y permite recuperarse frente a los mismos, mientras que en hardware se evita tener un único punto de fallos. Alta eficiencia: Son clústeres cuyo objetivo de diseño es el ejecutar la mayor cantidad de tareas en el menor tiempo posible. Existe independencia de datos entre las tareas individuales. El retardo entre los nodos del clúster no es considerado un gran problema. b. Requisitos o Componentes de un Clúster Requisitos en Hardware: Nodos: Elementos que forman parte de una red. Un nodo u ordenador es definido como punto de intersección o unión de varios elementos que confluyen en el mismo lugar.se recomienda que lo nodos que conforman un clúster sean idénticos o muy similares en cuanto arquitectura y que contengan los mismos componentes (procesador, memoria, tarjeta madre, disco duro etc.) Adaptadores de red: Interface, dispositivo o placa ya sea alámbrica o inalámbrica que se integra a un nodo y le permite la comunicación entre varios ordenadores dentro de una red. Requisitos en Software: Sistema Operativo: Conjunto de programas que administra los recursos de un equipo de cómputo, gestiona políticas para un funcionamiento óptimo de los servicios proporcionados por el mismo. De preferencia todos los nodos deben de tener el mismo Sistema Operativo. Librerías de Procesamiento Paralelo: Programas que permiten al programador dividir una tarea entre un grupo de nodos conectados en red; MPI (Message Passing Interface) es un estándar de paso de mensajes como Parallel Virtual Machine (PVM), algunas implementaciones de estos estándares son OpenMPI ó MPICH. Recurso compartido (Directorio ó Memoria): Espacio virtual que tiene la característica de poder ser accedido por múltiples nodos de una red con diferentes permisos para cada uno de ellos. Controladores y Actualizaciones: Las versiones de los programas, las actualizaciones, los drivers instalados, deben de ser idénticos ya que es recomendable para una uniformidad en la estructura y configuración de un cluster. 52

54 Infraestructura de red: Un clúster puede ser implementado en distintas redes. Dentro de una LAN (Red de Área Local) debe de existir una configuración adecuada en términos de cableado estructurado, configuración de Ip s estáticas, nodos esclavos y maestro (servidor). c. Clúster de GPU s Un GPU Clúster, es un clúster de computadoras donde cada nodo está equipado con una unidad de procesamiento de gráficos. De tal forma que se pueda aprovechar la capacidad de cálculo de cada dispositivo GPU a fin de utilizarlo para la programación de propósito general sobre unidades de procesamiento de gráficos (GPGPU); de esta forma la potencia de cálculo de cada nodo es aprovechada en tareas que requieran una alta actividad de cómputo de datos. La interconexión de los nodos del clúster de GPU s requiere de un hardware que sea capaz de transmitir una gran cantidad de datos en poco tiempo, ya que esto podría aumentar el tiempo de operación sobre los datos; el tipo de interconexión depende también del número de nodos presentes en el clúster. Algunos ejemplos de marcas de dispositivos de interconexión entre nodos son: Gigabit Ethernet e Infiniband. En cuanto a nivel software cabe decir que cada elemento del clúster de GPU s deberá tener instalado un sistema operativo, el driver de cada GPU presente en el nodo y de un API de clúster como MPI, el cual proveerá de la comunicación entre los elementos del clúster. Figura 26. Diagrama de un Clúster de GPU s. 53

55 d. Interfaz de paso de mensajes: Open MPI MPI ("Message Passing Interface", Interfaz de Paso de Mensajes) es un estándar que define la sintaxis y la semántica de las funciones contenidas en una biblioteca de paso de mensajes diseñada para ser usada en programas que exploten la existencia de múltiples procesadores. El paso de mensajes es una técnica empleada en programación concurrente para aportar sincronización entre procesos y permitir la exclusión mutua, de manera similar a como se hace con los semáforos, monitores, etc. Su principal característica es que no precisa de memoria compartida, por lo que es muy importante en la programación de sistemas distribuidos. Los elementos principales que intervienen en el paso de mensajes son el proceso que envía, el que recibe y el mensaje. Dependiendo de si el proceso que envía el mensaje espera a que el mensaje sea recibido, se puede hablar de paso de mensajes síncrono o asíncrono. En el paso de mensajes asíncrono, el proceso que envía, no espera a que el mensaje sea recibido, y continúa su ejecución, siendo posible que vuelva a generar un nuevo mensaje y a enviarlo antes de que se haya recibido el anterior. Por este motivo se suelen emplear buzones, en los que se almacenan los mensajes a espera de que un proceso los reciba. Generalmente empleando este sistema, el proceso que envía mensajes sólo se bloquea o para, cuando finaliza su ejecución, o si el buzón está lleno. En el paso de mensajes síncrono, el proceso que envía el mensaje espera a que un proceso lo reciba para continuar su ejecución. Por esto se suele llamar a esta técnica encuentro, o rendezvous. Dentro del paso de mensajes síncrono se engloba a la llamada a procedimiento remoto, muy popular en las arquitecturas cliente/servidor. La Interfaz de Paso de Mensajes (conocido ampliamente como MPI, siglas en inglés de Message Passing Interface) es un protocolo de comunicación entre computadoras. Es el estándar para la comunicación entre los nodos que ejecutan un programa en un sistema de memoria distribuida. Las implementaciones en MPI consisten en un conjunto de bibliotecas de rutinas que pueden ser utilizadas en programas escritos en los lenguajes de programación C, C++, Fortran y Ada. La ventaja de MPI sobre otras bibliotecas de paso de mensajes, es que los programas que utilizan la biblioteca son portables (dado que MPI ha sido implementado para casi toda arquitectura de memoria distribuida), y rápidos, (porque cada implementación de la biblioteca ha sido optimizada para el hardware en la cual se ejecuta). OpenMPI es un API de código abierto desarrollada para facilitar la programación paralela y/o distribuida, que implementa el estándar MPI, permite la distribución de procesos de forma dinámica, provee de alto rendimiento y tolerancia a fallos (capacidad de recuperarse de forma transparente de los fallos de los componentes, envío recepción de mensajes, fallo de un procesador o nodo), soporte para redes heterogéneas, es portable ya que funciona en diferentes sistemas operativos (Linux, OS-X y Solaris) y presenta también opciones de configuración durante la instalación, la compilación de programas y su ejecución. 54

56 Por qué usar OpenMPI?, La programación paralela y en el caso, el paradigma de paso de mensajes, carece de una implementación oficial que sea adecuada para todo el mundo. El equipo de Open MPI pretende desarrollar dicha implementación, dando como resultado la mejor librería de paso de mensajes, que esté al alcance de todo el mundo y que pueda funcionar bajo cualquier plataforma y con diferentes redes de ordenadores. Tiene sus raíces en varias implementaciones conocidas de MPI: FT-MPI de la Universidad de Tennessee LA-MPI de Los Alamos National Laboratory LAM/MPI de la Universidad de Indiana Open MPI incorpora lo mejor de los anteriores proyectos como base para el desarrollo de una implementación oficial que sea adecuada en todos los aspectos. e. Laboratorio de Sistemas Distribuidos UAM I Actualmente el laboratorio de sistemas distribuidos de la universidad autónoma metropolitana, unidad Iztapalapa; cuenta con un clúster de GPU s el cual cuenta con tres nodos, cada uno de ellos posee una GPU de la marca Nvidia y se encuentran interconectados mediante la red de datos del laboratorio. Figura 27. Arquitectura actual del Clúster Pacifico, instalado en la UAM-I. 55

57 Los equipos cuentan con un sistema operativo Centos 5.5 de 64 bits instalado; también se tiene instaladas las librerías básicas de compilación: Build Essentials, la plataforma de Nvidia: Cuda Compilation Tools en su versión 4.0, así como OpenMPI 1.4.4, liberado en Octubre del 2011, todos los equipos cuentan con una directorio compartido vía NFS y se comunican mediante el protocolo SSH. f. Paralelismo CUDA + MPI Se pueden utilizar varias estrategias de paralelismo en el clúster de GPU s, por un lado se tiene a CUDA a nivel nodo para realizar el control de las GPU s; también existe la posibilidad de combinar CUDA con Pthreads y OpenMp para trabajar en la dualidad CPU GPU y por ultimo combinar esto con Open MPI, para paralelizar todos los niveles posibles. g. Compilación y Ejecución de un programa CUDA + Open MPI Para poder compilar y ejecutar un programa en un clúster de GPU s es necesario haber instalado y configurado previamente: Infraestructura del clúster y de la red en el cual esta implementado. Integración de los dispositivos GPU en cada nodo. Drivers, actualizaciones y aplicaciones correspondientes Directorio compartido (NFS) Una aplicación en el cluster se compone por tres archivos básicos necesarios para poder combinar código Cuda con código MPI. Es importante mencionar que estos archivos se encuentran alojados en el directorio compartido del nodo maestro, los nodos restantes acceden al directorio ya mencionado para leer compilar y ejecutar los códigos. cuda.cu (contiene código cuda que implementa la funcionalidad de la GPU, reservación de memoria y se establece la transferencia de datos entre Host y Device, invocación del Kernel). mpi.c (contiene código mpi que implementa el envió y recepción de mensajes entre procesos que viven en cada nodo del cluster). machinefile.txt (contiene el hostname de cada nodo que conforma el clúster). 56

58 Para realizar un programa en CUDA y Open Mpi, habrá que considerar cada parte del código como una entidad separada la una de la otra. Por un lado se tiene el código que se encarga de inicializar la estructura de comunicación de MPI entre los procesos y por otro la porción de código que se ejecuta en la GPU, dentro de la cual se cargan los datos a la memoria global del dispositivo y se invoca a la función kernel que realizara el tratamiento o cálculo de los datos. Figura 28. Dos archivos diferentes que controlan secciones diferentes del programa y que se compilan de forma independiente. La compilación se compone de dos etapas ya que al ser entidades separadas es de esperarse que las compilaciones se realicen por separado y con compiladores diferentes, también es de esperarse que se deban de incluir directivas de compilación distintas en cada paso para al final obtener un solo archivo ejecutable. La primera de estas etapas corresponde a la compilación de del archivo cuda.cu, para compilar código fuente cuda usamos el compilador NVCC. La opción: -c le indica al compilador de CUDA, que deberá compilar el archivo fuente y generar un archivo con extensión:.o de tipo objeto, el cual será ligado posteriormente con la compilación del segundo archivo. La compilación para el archivo: cuda.cu, se realiza de la siguiente manera: $ nvcc c cuda.cu La segunda etapa de compilación consta de compilar el archivo mpi.c, que hace uso del archivo objeto generado por la primera etapa de compilación, se agrega explícitamente una librería propia del lenguaje cuda y se efectúa un ligado y vinculación de librerías necesarias para la correcta creación de un archivo objeto que se usara en la etapa de ejecución, esto se indica con la opción: -lcudart la opción: -L y la opción: -I 57

59 La compilación para el archivo: mpi.c, se realiza de la siguiente forma: $ mpicc o mpi mpi.c cuda.o lcudart L /usr/local/cuda/lib64 I /usr/local/cuda/include La ejecución hace uso de MPIRUN integrando el archivo objeto generado por la segunda etapa de compilación e involucra el archivo que contiene los nombres de los nodos que integra el clúster que es invocado mediante la opción -- hostfile, mientras que la opción: -np le indica a mpirun el número de mpi procesos que deberá de lanzar de forma paralela, en este ejemplo el número de procesos es 10. El resultado de dicha compilación es un archivo binario, el cual se puede ejecutar mediante la siguiente sentencia: $ mpirun --hostfile machinefile.txt np 10./mpi Un aspecto que es importante mencionar, es que dentro del archivo cuda.cu hay una palabra reservada que indica al compilador que una función definida en el código se implementa de manera externa. La Fig. 7 ilustra esta idea. Figura 29 Uso de la palabra reservada extern. 58

60 h. Ejemplo: simplecudampi El siguiente ejemplo muestra la estructura básica de una aplicación en CUDA, y Open MPI. Suponga que se tiene un clúster de GPU s con N nodos, se lanzan N mpi procesos, cada proceso es repartido a los nodos del clúster, cada nodo lee un dato compartido que tiene un valor establecido (en el ejemplo10) y le suma su número de proceso (rank), esta operación se realiza dentro de la GPU; una vez realizada la suma de los elementos, cada mpi proceso detecta la GPU que tiene instalado el nodo y obtiene el hostname (nombre del nodo en el clúster) mostrando en pantalla el nombre del nodo, el modelo de GPU que controla y el resultado de la suma. Cuando se ejecuta esta aplicación se obtiene una salida como la mostrada en la imagen siguiente. Figura 30. Salida en pantalla del programa: simplecudampi Habrá que notar que los procesos al ser lanzados concurrentemente no se despliegan en un orden secuencial, si no de forma aleatoria como es de esperarse. Cada nodo procesa un mpi proceso distinto, toma en contexto su respectiva GPU, realiza la suma y luego muestra el resultado en pantalla. La imagen mostrada fue ejecutada usando solo dos nodos del clúster de gpu s (pacifico5 y pacifico7) se lanzaron 10 mpi procesos. La siguiente imagen muestra el objetivo de la aplicación: 59

61 Figura 31. Un esquema que intenta explicar el proceso de nuestra aplicación. El código mostrado a continuación se encuentra separado, por un lado se tiene el código que se ejecuta en la GPU (archivo con extensión:.cu) y por otro lado se tiene la sección de código que inicializa la comunicación entre los nodos mediante la API de open MPI y en la cual se hace la invocación a la función kernel del GPU. /** ** Archivo: cuda.cu, en este archivo se coloca el código que realiza la ** Transferencia de datos del cpu al gpu, y se define además la función ** Kernel, así como su llamado y configuración de lanzamiento. **/ #include <stdio.h> #include <unistd.h> #include <cuda.h> global void kernel(int dato,int rank, int *gpu_dato) { *gpu_dato= dato + rank; extern "C" void run_kernel(int dato, int rank) { char host[20]; int cpu_dato; int *gpu_dato; cudamalloc( (void**)&gpu_dato, sizeof(int)); kernel<<<1,1>>>(dato, rank, gpu_dato); cudamemcpy( &cpu_dato,gpu_dato, sizeof(int), cudamemcpydevicetohost); cudadeviceprop dev_prop; cudagetdeviceproperties(&dev_prop, 0); gethostname(host,20); printf("proceso [ %d ] : %s Tiene un GPU modelo: %s. Leyendo dato = %d \n", rank, host, dev_prop.name, dato); printf("proceso [ %d ] : Cambiando dato a: %d \n\n", rank, cpu_dato); 60

62 /** ** Archivo: mpi.c, en este archivo se coloca el código que inicializa el entorno ** de comunicación de Open MPI se inicializan los datos a tratar y se invoca a ** función run_kernel( ) declarada tanto en este archivo como en cuda.cu. **/ #include <mpi.h> #include <stdio.h> #include <stdlib.h> void run_kernel(int dato, int rank); int main(int argc, char *argv[]) { int rank, size; printf("\n"); MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &rank); MPI_Comm_size (MPI_COMM_WORLD, &size); int dato=10; run_kernel(dato, rank); MPI_Finalize(); printf("\n"); return 0; Se detalla a continuación la forma de compilación y ejecución de este ejemplo: # Compilación del archivo: cuda.cu # Compilación y ligado del archivo: mpi.c # Ejecución $ nvcc c cuda.cu $ mpicc o mpi mpi.c cuda.o lcudart L /usr/local/cuda/lib64 I /usr/local/cuda/include $ mpirun hostfile machinefile.txt np 3./mpi 61

63 i. Ejemplo: Cuda + Pthreads + OpenMPI La siguiente aplicación implementa código cuda que permite manipular la GPU de un nodo encargándose de la transferencia de datos entre Host y Device, también hace uso de procesos ligeros pthreads que utilizan a la GPU para determinada actividad, openmpi se encarga de lanzar procesos que comparten mensajes entre sí en cada nodo del cluster. Específicamente la aplicación realiza lo siguiente: openmpi lanza 3 procesos que son repartidos entre los 3 nodos del clúster, cada proceso lanza un pthread que ejecuta código indicando su identificador y porque proceso fue lanzado, crea e inicializa un arreglo que transfiere a memoria global de la GPU invocando un kernel definido, dicho kernel ejecutado por los CudaThreads reinicializa el arreglo y es transferido de regreso a la memoria del CPU. Al final se manda un mensaje que en términos prácticos es un número entero mediante openmpi a otro proceso que espera dicho número para inicializar su propio arreglo. El esquema siguiente ilustra esta idea. Figura 32. Esquema que muestra la integración de tres tecnologías: CUDA, Open MPI y Pthreads. A continuación se muestra el código del programa cuda.cu 62

64 ///////////////////////////////////////////////////////////////////// ///// PROGRAMA HIBRIDO >>> CUDA + PTHREADS + OPENMPI //////////// ///////////////////////////////////////////////////////////////////// //librerias #include <pthread.h> #include <semaphore.h> #include <stdio.h> #include <cuda.h> #include <stdlib.h> #include <unistd.h> #include <mpi.h> //constante #define N 10 //kernel que se ejecuta en Pacifico5 global void kernel1(int *array){ array[threadidx.x] = 1; //kernel que se ejecuta en Pacifico6 global void kernel2(int *array){ array[threadidx.x] = 2; //Kernel que se ejecuta en Pacifico7 global void kernel3(int *array){ array[threadidx.x] = 3; //Rutina que ejecuta el Hilo void *HiloProc(void *rank){ //definicion de variables MPI_Status status; int i,arr[n],aux,*devarr; char nombre[20]; aux = *(int *)rank; //casteamos de (void * ) a int cudadeviceprop dev_prop; cudagetdeviceproperties(&dev_prop, 0); gethostname(nombre,20); if (aux == 0){ printf("\nsoy un Hilo de %s, el ID del proceso que me lanzo es -- %d --\n",nombre,aux); printf("\n\teste es el arreglo original: "); for(i = 0; i < N; i++){ arr[i]=0; printf("%d ", arr[i]); //se transfiere el arreglo a la gpu cudamalloc((void**) &devarr, sizeof(int)*n); cudamemcpy(devarr, arr, sizeof(int)*n, cudamemcpyhosttodevice); kernel1<<<1, N>>>(devarr); cudamemcpy(arr, devarr, sizeof(int)*n, cudamemcpydevicetohost); printf("\n\tse esta utilizando la GPU :: [ %s ]",dev_prop.name); printf("\n\tvector Resultado >> [ "); for(i = 0; i < N; i++) printf("%d ", arr[i]); printf("]\n"); cudafree(devarr); //se manda un mensaje al siguiente nodo MPI_Send(arr, N, MPI_INT,aux+1,0, MPI_COMM_WORLD); 63

65 if ( aux == 1){ sleep(2); printf("\nsoy un Hilo de %s, el ID del proceso que me lanzo es -- %d --\n",nombre,aux); //recibe el mensaje del nodo anterior MPI_Recv(arr, N, MPI_INT, aux-1, 0, MPI_COMM_WORLD, &status); //se pasa el arreglo la gpu cudamalloc((void**) &devarr, sizeof(int)*n); cudamemcpy(devarr, arr, sizeof(int)*n, cudamemcpyhosttodevice); kernel2<<<1, N>>>(devarr); cudamemcpy(arr, devarr, sizeof(int)*n, cudamemcpydevicetohost); printf("\n\tse esta utilizando la GPU :: [ %s ]",dev_prop.name); printf("\n\tvector Resultado >> [ "); for(i = 0; i < N; i++) printf("%d ", arr[i]); printf("]\n"); cudafree(devarr); //se manda un mensaje al nodo siguiente MPI_Send(arr, N, MPI_INT,aux+1,0, MPI_COMM_WORLD); if ( aux == 2){ sleep(4); printf("\nsoy un Hilo de %s, el ID del proceso que me lanzo es -- %d --\n",nombre,aux); //se recibe el mensaje del nodo anterior MPI_Recv(arr, N, MPI_INT, aux-1, 0, MPI_COMM_WORLD, &status); //se pasa el arreglo a la gpu cudamalloc((void**) &devarr, sizeof(int)*n); cudamemcpy(devarr, arr, sizeof(int)*n, cudamemcpyhosttodevice); kernel3<<<1, N>>>(devarr); cudamemcpy(arr, devarr, sizeof(int)*n, cudamemcpydevicetohost); printf("\n\tse esta utilizando la GPU :: [ %s ]",dev_prop.name); printf("\n\tvector Resultado >> [ "); for(i = 0; i < N; i++) printf("%d ", arr[i]); printf("]\n"); cudafree(devarr); pthread_exit(0); extern "C" void run_kernel(int rank){ pthread_t Hilo; //****variable de tipo hilo //se crea un hilo y ejecuta la rutina correspondiente pthread_create(&hilo,null, HiloProc, (void *)&rank); //se espera al a que el hilo termine pthread_join(hilo, NULL); printf("\n"); 64

66 A continuación se muestra el código del programa mpi.c #include <mpi.h> #include <stdio.h> #define N 15 void run_kernel(int aux); int main(int argc, char *argv[]) { int rank, size; MPI_Init (&argc, &argv); /* starts MPI */ MPI_Comm_rank (MPI_COMM_WORLD, &rank); /* get current process id */ MPI_Comm_size (MPI_COMM_WORLD, &size); /* get number of processes */ run_kernel(rank); MPI_Finalize(); return 0; 65

67 j. Ejemplo: SendReceiveCUDAMPI A continuación se muestra un ejemplo de un programa que encadena el envío y recepción de un mensaje, Se inicializa una variable compartida al valor 0, posteriormente se lanzan N MPI Procesos, el proceso 0, toma el dato y le suma su identificador de proceso (Rank = 0), los demás procesos (N-1) se detienen en espera de la recepción de un mensaje; el proceso 1 recibe un mensaje del proceso 0 con el valor del dato actualizado, que ese momento vale: = 1; toma este valor y le añade su identificador de proceso y envía ese valor al proceso siguiente; esto continua de forma consecutiva hasta que el proceso N, recibe del proceso N-1 un valor y le añade su identificador de proceso, Finalmente el proceso N, le envía un mensaje al proceso 0, y este solo imprime el valor final de la variable. La salida en ejecución de este programa en el clúster de GPU s lanzando 12 MPI Procesos, es la siguiente: Figura 33. Salida en pantalla del programa: SendReceiveCUDAMPI En este ejemplo se hace uso de las funciones de Open MPI para el envío y recepción de mensajes, el código completo del programa se muestra a continuación, cabe mencionar que todos los Kernels de estos ejemplos son lanzados con la configuración de un bloque con un hilo, ya que la cantidad de datos a tratar no es excesivo y solo se intenta mostrar la forma en que se pueden combinar ambas tecnologías, el cuarto ejemplo que se mostrará más adelante utiliza de lleno las prestaciones de la GPU y es un ejemplo claro de que se puede utilizar la combinación de tecnologías: CUDA y Open MPI, para implementar programas paralelos de forma relativamente sencilla. A continuación se muestran ambas secciones de código para que el lector pueda analizarlas a detalle. 66

68 /** ** Archivo: cuda.cu **/ #include <stdio.h> #include <unistd.h> #include <cuda.h> global void kernel(int dato,int rank, int *gpu_dato) { *gpu_dato= dato + rank; extern "C" int run_kernel(int dato, int rank) { int *gpu_dato; cudamalloc( (void**)&gpu_dato, sizeof(int)); kernel<<<1,1>>>(dato, rank, gpu_dato); cudamemcpy(&dato, gpu_dato, sizeof(int), cudamemcpydevicetohost); return dato; /** ** Archivo: mpi.c **/ #include <mpi.h> #include <stdio.h> #include <unistd.h> int run_kernel(int dato, int rank); int main(int argc, char *argv[]) { int rank, size, dato=0; char host[20]; MPI_Status estado; MPI_Init (&argc, &argv); // Inicio de la comunicación entre procesos. MPI_Comm_size (MPI_COMM_WORLD, &size);// Se obtiene el número total de procesos. MPI_Comm_rank (MPI_COMM_WORLD, &rank); // Se obtiene el ID del proceso. // Todos Obtienen su hostname. gethostname(host, 20); if(rank > 0 && rank < (size-1)) { MPI_Recv ( &dato, // Referencia al vector donde se almacenara lo recibido 1, // tamaño del vector a recibir MPI_INT, // Tipo de dato que recibe rank-1, // pid del proceso origen de la que se recibe 0, // etiqueta MPI_COMM_WORLD, // Comunicador por el que se recibe &estado // estructura informativa del estado ); printf ( "\nproceso[ %d ] desde %s : Recibi Dato = %d. Envio %d + %d \n", rank, host, dato, dato, rank ); dato=run_kernel(dato,rank); MPI_Send(&dato, 1,MPI_INT,rank+1, 0,MPI_COMM_WORLD); 67

69 else if(rank == size-1){ MPI_Recv ( &dato, // Referencia al vector donde se almacenara lo recibido 1, // tamaño del vector a recibir MPI_INT, // Tipo de dato que recibe rank-1, // pid del proceso origen de la que se recibe 0, // etiqueta MPI_COMM_WORLD, // Comunicador por el que se recibe &estado); // estructura informativa del estado printf ( \nprocess[ %d ] desde %s : Recibi Dato = %d. Envio %d + %d \n", rank, host, dato, dato, rank ); dato=run_kernel(dato,rank); MPI_Send(&dato, 1,MPI_INT,0, 0,MPI_COMM_WORLD); else if(rank == 0){ printf ( "\nproceso[ %d ] desde %s : Enviando Dato = %d \n", rank, host, dato ); MPI_Send ( &dato, //referencia al vector de elementos a enviar 1, // tamaño del vector a enviar MPI_INT, // Tipo de dato que envias rank+1, // pid del proceso destino 0, //etiqueta MPI_COMM_WORLD //Comunicador por el que se manda ); MPI_Recv ( &dato, // Referencia al vector donde se almacenara lo recibido 1, // tamaño del vector a recibir MPI_INT, // Tipo de dato que recibe size-1, // pid del proceso origen de la que se recibe 0, // etiqueta MPI_COMM_WORLD, // Comunicador por el que se recibe &estado // estructura informativa del estado ); printf ( "\nproceso[ %d ] desde %s : Recibi Dato = %d \n\n", rank, host, dato ); MPI_Finalize(); return 0; 68

70 k. Ejemplo: ReduceCudaMPI Con el siguiente ejemplo se pretende mostrar una forma de modelar un problema de cálculo de información sobre muchos datos, utilizando las prestaciones de un clúster de GPU s. La reducción de un vector consta de realizar la suma de cada elemento almacenado en el arreglo; la solución a este problema utilizando el clúster es la siguiente: se divide el arreglo (el cual debe de tener un tamaño considerable) en tantos MPI Procesos como se desee, cada MPI proceso toma una sección de datos del arreglo y lleva esa información a la memoria global de la GPU, en el dispositivo es lanzado un kernel (128 hilos por bloque, el numero de bloques es igual al tamaño del arreglo entre el numero de hilos por bloque) mediante el cual se realiza la reducción (suma de elementos del vector) de los datos que contiene esa sección de arreglo. Posteriormente todos los MPI procesos al finalizar su trabajo, retornan la suma parcial obtenida mediante el Kernel, se realiza la suma total de cada resultado parcial, finalmente el MPI Proceso Maestro, muestra en pantalla el resultado. EL siguiente diagrama muestra el proceso de la aplicación. Figura 34. Diagrama del procedimiento implementado en el ejemplo: reducecudampi La aplicación fue ejecutada utilizando un arreglo de tamaño: 10240, cada elemento se inicializo al valor 1, por lo tanto, la salida esperada es el valor La reducción del vector se llevo a cabo usando 10 MPI Procesos, por lo tanto, el tamaño de las secciones de vector para cada MPI Proceso fue de 1024; haciendo cálculos, se lanzaron por kernel: 8 Bloques cada uno con 128 Cuda Threads. A continuación se muestran las respectivas secciones de código. 69

CUDA (Compute Unified Device Architecture)

CUDA (Compute Unified Device Architecture) CUDA (Compute Unified Device Architecture) Alvaro Cuno 23/01/2010 1 CUDA Arquitectura de computación paralela de propósito general La programación para la arquitectura CUDA puede hacerse usando lenguaje

Más detalles

Primeros pasos con CUDA. Clase 1

Primeros pasos con CUDA. Clase 1 Primeros pasos con CUDA Clase 1 Ejemplo: suma de vectores Comencemos con un ejemplo sencillo: suma de vectores. Sean A, B y C vectores de dimensión N, la suma se define como: C = A + B donde C i = A i

Más detalles

48 ContactoS 84, (2012)

48 ContactoS 84, (2012) 48 ContactoS 84, 47 55 (2012) Recibido: 31 de enero de 2012. Aceptado: 03 de mayo de 2012. Resumen En los últimos años la tecnología de las unidades de procesamiento de gráficos (GPU-Graphics Processsing

Más detalles

CÓMPUTO DE ALTO RENDIMIENTO EN MEMORIA COMPARTIDA Y PROCESADORES GRÁFICOS

CÓMPUTO DE ALTO RENDIMIENTO EN MEMORIA COMPARTIDA Y PROCESADORES GRÁFICOS CÓMPUTO DE ALTO RENDIMIENTO EN MEMORIA COMPARTIDA Y PROCESADORES GRÁFICOS Leopoldo N. Gaxiola, Juan J. Tapia Centro de Investigación y Desarrollo de Tecnología Digital Instituto Politécnico Nacional Avenida

Más detalles

Francisco J. Hernández López

Francisco J. Hernández López Francisco J. Hernández López fcoj23@cimat.mx 2 Procesadores flexibles de procesamiento general Se pueden resolver problemas de diversas áreas: Finanzas, Gráficos, Procesamiento de Imágenes y Video, Algebra

Más detalles

Francisco Javier Hernández López

Francisco Javier Hernández López Francisco Javier Hernández López fcoj23@cimat.mx http://www.cimat.mx/~fcoj23 Ejecución de más de un cómputo (cálculo) al mismo tiempo o en paralelo, utilizando más de un procesador. Arquitecturas que hay

Más detalles

INTRODUCCIÓN A LA PROGRAMACIÓN EN CUDA. Francisco Javier Hernández López

INTRODUCCIÓN A LA PROGRAMACIÓN EN CUDA. Francisco Javier Hernández López INTRODUCCIÓN A LA PROGRAMACIÓN EN CUDA Francisco Javier Hernández López http://www.cimat.mx/~fcoj23 Guanajuato, Gto. Noviembre de 2012 Introducción a la Programación en CUDA 2 Qué es el Cómputo Paralelo

Más detalles

Sistema electrónico digital (binario) que procesa datos siguiendo unas instrucciones almacenadas en su memoria

Sistema electrónico digital (binario) que procesa datos siguiendo unas instrucciones almacenadas en su memoria 1.2. Jerarquía de niveles de un computador Qué es un computador? Sistema electrónico digital (binario) que procesa datos siguiendo unas instrucciones almacenadas en su memoria Es un sistema tan complejo

Más detalles

GPU-Ejemplo CUDA. Carlos García Sánchez

GPU-Ejemplo CUDA. Carlos García Sánchez Carlos García Sánchez 1 2 Contenidos Motivación GPU vs. CPU GPU vs. Vectoriales CUDA Sintaxis Ejemplo 2 3 Motivación Computación altas prestaciones: www.top500.org 1º: Titan (300mil AMD-Opteron + 19mil

Más detalles

Taxonomía de las arquitecturas

Taxonomía de las arquitecturas Taxonomía de las arquitecturas 1 INTRODUCCIÓN 2 2 CLASIFICACIÓN DE FLYNN 3 2.1 SISD (SINGLE INSTRUCTION STREAM, SINGLE DATA STREAM) 3 2.2 SIMD (SINGLE INSTRUCTION STREAM, MULTIPLE DATA STREAM) 4 2.2.1

Más detalles

Con estas consideraciones, Flynn clasifica los sistemas en cuatro categorías:

Con estas consideraciones, Flynn clasifica los sistemas en cuatro categorías: Taxonomía de las arquitecturas 1 Introducción Introducción En este trabajo se explican en detalle las dos clasificaciones de computadores más conocidas en la actualidad. La primera clasificación, es la

Más detalles

FUNDAMENTOS DE COMPUTACIÓN PARA CIENTÍFICOS. CNCA Abril 2013

FUNDAMENTOS DE COMPUTACIÓN PARA CIENTÍFICOS. CNCA Abril 2013 FUNDAMENTOS DE COMPUTACIÓN PARA CIENTÍFICOS CNCA Abril 2013 6. COMPUTACIÓN DE ALTO RENDIMIENTO Ricardo Román DEFINICIÓN High Performance Computing - Computación de Alto Rendimiento Técnicas, investigación

Más detalles

Programación Concurrente y Paralela. Unidad 1 Introducción

Programación Concurrente y Paralela. Unidad 1 Introducción Programación Concurrente y Paralela Unidad 1 Introducción Contenido 1.1 Concepto de Concurrencia 1.2 Exclusión Mutua y Sincronización 1.3 Corrección en Sistemas Concurrentes 1.4 Consideraciones sobre el

Más detalles

CDI Arquitecturas que soportan la concurrencia. granularidad

CDI Arquitecturas que soportan la concurrencia. granularidad granularidad Se suele distinguir concurrencia de grano fino es decir, se aprovecha de la ejecución de operaciones concurrentes a nivel del procesador (hardware) a grano grueso es decir, se aprovecha de

Más detalles

Sistemas Complejos en Máquinas Paralelas

Sistemas Complejos en Máquinas Paralelas Sistemas Complejos en Máquinas Paralelas Clase 1: OpenMP Francisco García Eijó Departamento de Computación - FCEyN UBA 15 de Mayo del 2012 Memoria compartida Las mas conocidas son las máquinas tipo Symmetric

Más detalles

MULTIPROCESADORES TIPOS DE PARALELISMO

MULTIPROCESADORES TIPOS DE PARALELISMO Todos los derechos de propiedad intelectual de esta obra pertenecen en exclusiva a la Universidad Europea de Madrid, S.L.U. Queda terminantemente prohibida la reproducción, puesta a disposición del público

Más detalles

Tema 1: PROCESADORES SEGMENTADOS

Tema 1: PROCESADORES SEGMENTADOS Tema 1: PROCESADORES SEGMENTADOS Tema 1: PROCESADORES SEGMENTADOS 1.1. Procesadores RISC frente a procesadores CISC. 1.2. Clasificación de las arquitecturas paralelas. 1.3. Evaluación y mejora del rendimiento

Más detalles

PARTE II PROGRAMACION CON THREADS EN C

PARTE II PROGRAMACION CON THREADS EN C PARTE II PROGRAMACION CON THREADS EN C II.1 INTRODUCCION Una librería o paquete de threads permite escribir programas con varios puntos simultáneos de ejecución, sincronizados a través de memoria compartida.

Más detalles

Sistemas Operativos. Procesos

Sistemas Operativos. Procesos Sistemas Operativos Procesos Agenda Proceso. Definición de proceso. Contador de programa. Memoria de los procesos. Estados de los procesos. Transiciones entre los estados. Bloque descriptor de proceso

Más detalles

CUDA Overview and Programming model

CUDA Overview and Programming model Departamento de Ciencias de la computación Universidad de Chile Modelado en 3D y sus Aplicaciones en Realidad Virtual CC68W CUDA Overview and Programming model Student: Juan Silva Professor: Dr. Wolfram

Más detalles

Modelo de aplicaciones CUDA

Modelo de aplicaciones CUDA Modelo de aplicaciones CUDA Utilización de GPGPUs: las placas gráficas se utilizan en el contexto de una CPU: host (CPU) + uno o varios device o GPUs Procesadores masivamente paralelos equipados con muchas

Más detalles

TAREA 1. INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS.

TAREA 1. INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS. 1 TAREA 1. INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS. 1- Cuáles son las principales funciones de un sistema operativo? Los Sistemas Operativos tienen como objetivos o funciones principales lo siguiente; Comodidad;

Más detalles

PARADIGMA y LENGUAJES DE PROGRAMACIÓN

PARADIGMA y LENGUAJES DE PROGRAMACIÓN CATEDRA CARRERA: PARADIGMA y LENGUAJES DE PROGRAMACIÓN LICENCIATURA EN SISTEMAS DE INFORMACION FACULTAD DE CIENCIAS EXACTAS QUIMICAS Y NATURALES UNIVERSIDAD NACIONAL DE MISIONES Año 2017 2do Cuatrimestre

Más detalles

Evolución del software y su situación actual

Evolución del software y su situación actual Evolución del software y su situación actual El software es el conjunto de programas que permite emplear la PC, es decir, es el medio de comunicación con la computadora, el control de sus funciones y su

Más detalles

Unidad I: Organización del Computador. Ing. Marglorie Colina

Unidad I: Organización del Computador. Ing. Marglorie Colina Unidad I: Organización del Computador Ing. Marglorie Colina Arquitectura del Computador Atributos de un sistema que son visibles a un programador (Conjunto de Instrucciones, Cantidad de bits para representar

Más detalles

Arquitecturas GPU v. 2015

Arquitecturas GPU v. 2015 v. 2015 http://en.wikipedia.org/wiki/graphics_processing_unit http://en.wikipedia.org/wiki/stream_processing http://en.wikipedia.org/wiki/general-purpose_computing_on_graphics_processing_ units http://www.nvidia.com/object/what-is-gpu-computing.html

Más detalles

CUDA 5.5 y Visual Studio Express 2012 para Escritorio. Primeros pasos.

CUDA 5.5 y Visual Studio Express 2012 para Escritorio. Primeros pasos. CUDA 5.5 y Visual Studio Express 2012 para Escritorio. Primeros pasos. La nueva versión de CUDA 5.5 es completamente compatible con la plataforma de desarrollo Visual Studio Express 2012 para escritorio

Más detalles

Recopilación presentada por 1

Recopilación presentada por 1 Aula Aula de de Informática Informática del del Centro Centro de de Participación Participación Activa Activa para para Personas Personas Mayores Mayores de de El El Ejido Ejido (Almería). (Almería). Consejería

Más detalles

Velocidades Típicas de transferencia en Dispositivos I/O

Velocidades Típicas de transferencia en Dispositivos I/O Entradas Salidas Velocidades Típicas de transferencia en Dispositivos I/O Entradas/Salidas: Problemas Amplia variedad de periféricos Entrega de diferentes cantidades de datos Diferentes velocidades Variedad

Más detalles

Tema 18: Memoria dinámica y su uso en C

Tema 18: Memoria dinámica y su uso en C Tema 18: Memoria dinámica y su uso en C M. en C. Edgardo Adrián Franco Martínez http://www.eafranco.com edfrancom@ipn.mx @edfrancom edgardoadrianfrancom Estructuras de datos (Prof. Edgardo A. Franco) 1

Más detalles

Unidad 2: Taller de Cómputo. Estructura y Componentes de la Computadora UNIDAD DOS: INTRODUCCIÓN

Unidad 2: Taller de Cómputo. Estructura y Componentes de la Computadora UNIDAD DOS: INTRODUCCIÓN UNIDAD DOS: INTRODUCCIÓN Una computadora es una máquina electrónica diseñada para manipular y procesar información de acuerdo a un conjunto de ordenes o programas. para que esto sea posible se requiere

Más detalles

Qué es un programa informático?

Qué es un programa informático? Qué es un programa informático? Un programa informático es una serie de comandos ejecutados por el equipo. Sin embargo, el equipo sólo es capaz de procesar elementos binarios, es decir, una serie de 0s

Más detalles

Ejecución serial: las tareas/instrucciones de un programa son ejecutadas de manera secuencial, una a la vez.

Ejecución serial: las tareas/instrucciones de un programa son ejecutadas de manera secuencial, una a la vez. Paralelismo Conceptos generales Ejecución serial: las tareas/instrucciones de un programa son ejecutadas de manera secuencial, una a la vez. Ejecución paralela: varias tareas/instrucciones de un programa

Más detalles

Tema III: Componentes de un Sistema Operativo

Tema III: Componentes de un Sistema Operativo Tema III: Componentes de un Sistema Operativo Concepto de proceso Jerarquía de memoria: Concepto de memoria cache Memoria virtual Partición Sistema de ficheros Sistema de entrada/salida: Driver y controladora

Más detalles

TEMA 1. PROGRAMACIÓN DE UN COMPUTADOR

TEMA 1. PROGRAMACIÓN DE UN COMPUTADOR Tema 1. Programación de un computador TEMA 1. CIÓN DE UN COMPUTADOR 1. CONCEPTO DE 2. LENGUAJES DE CIÓN 2.1. LENGUAJE MÁQUINA 2.2. LENGUAJE ENSAMBLADOR 2.3. LENGUAJE DE ALTO NIVEL 3. ALGORITMOS. REPRESENTACIÓN

Más detalles

INTRODUCCIÓN A LA COMPUTACIÓN PARALELA CON GPUS

INTRODUCCIÓN A LA COMPUTACIÓN PARALELA CON GPUS INTRODUCCIÓN A LA COMPUTACIÓN PARALELA CON GPUS Sergio Orts Escolano sorts@dtic.ua.es Vicente Morell Giménez vmorell@dccia.ua.es Universidad de Alicante Departamento de tecnología informática y computación

Más detalles

Cómputo paralelo con openmp y C

Cómputo paralelo con openmp y C Cómputo paralelo con openmp y C Sergio Ivvan Valdez Peña Guanajuato, México. 13 de Marzo de 2012 Sergio Ivvan Valdez Peña Cómputo Guanajuato, paralelo conméxico. openmp y () C 13 de Marzo de 2012 1 / 27

Más detalles

Tema 1: Arquitectura de ordenadores, hardware y software

Tema 1: Arquitectura de ordenadores, hardware y software Fundamentos de Informática Tema 1: Arquitectura de ordenadores, hardware y software 2010-11 Índice 1. Informática 2. Modelo de von Neumann 3. Sistemas operativos 2 1. Informática INFORMación automática

Más detalles

Arquitectura de Computadoras. Clase 9 Procesamiento paralelo

Arquitectura de Computadoras. Clase 9 Procesamiento paralelo Arquitectura de Computadoras Clase 9 Procesamiento paralelo Introducción al procesamiento paralelo Sea cual sea el nivel de prestaciones, la demanda de máquinas de mayor rendimiento seguirá existiendo.

Más detalles

Montaje y Reparación de Sistemas Microinformáticos

Montaje y Reparación de Sistemas Microinformáticos Montaje y Reparación de Sistemas s Es uno de los componentes más imprescindible del equipo informático. Al igual que el resto de tarjetas de expansión, la tarjeta gráfica se conecta al bus PCIe. Algunas

Más detalles

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA Propuesta de trabajo de investigación Maestría en Ciencias y Tecnologías de la Información DEPURADOR DE APLICACIONES GD-MP GRÁFICAS EN México, 2015 1. Responsables

Más detalles

Introducción a OpenGL Shading Language (GLSL)

Introducción a OpenGL Shading Language (GLSL) a OpenGL Shading Language (GLSL) November 20, 2007 a OpenGL Shading Language (GLSL) Fixed Pipeline Programmable Pipeline Características de GLSL Por qué escribir un Shader? Vertex Processor Fragment Processor

Más detalles

PROGRAMA: COMPUTACION I

PROGRAMA: COMPUTACION I UNIVERSIDAD NACIONAL EXPERIMENTAL DEL TACHIRA VICERECTORADO ACADÉMICO DECANATO DE DOCENCIA DEPARTAMENTO DE INGENIERÍA INFORMÁTICA 1 PROGRAMA: COMPUTACION I Código 0415102T Carrera: Ingeniería Informática

Más detalles

Intel lanza su procesador Caballero Medieval habilitado para Inteligencia Artificial

Intel lanza su procesador Caballero Medieval habilitado para Inteligencia Artificial Intel lanza su procesador Caballero Medieval habilitado para Inteligencia Artificial Intel ha lanzado su procesador Xeon Phi en la Conferencia Internacional de Supercomputación de Alemania. El procesador

Más detalles

Informática Electrónica Manejadores de Dispositivos (Device Drivers)

Informática Electrónica Manejadores de Dispositivos (Device Drivers) Informática Electrónica Manejadores de Dispositivos (Device Drivers) DSI-EIE-FCEIA 2015 Que es un DD? Es una pieza de software que interactúa con (entre) el sistema operativo y con uno o mas dispositivos

Más detalles

EVOLUCIÓN DE LOS PROCESADORES

EVOLUCIÓN DE LOS PROCESADORES EVOLUCIÓN DE LOS PROCESADORES Lecturas recomendadas: * Tanembaum, A. Organización de computadoras. Cap. 1 * Stallings, W. Organización y arquitectura de computadores. Cap. 2 Arquitectura de una computadora

Más detalles

Segunda Parte: TECNOLOGÍA CUDA

Segunda Parte: TECNOLOGÍA CUDA Segunda Parte: (compute unified device architecture) 12 I. CUDA CUDA es una arquitectura de cálculo paralelo de NVIDIA que aprovecha la potencia de la GPU (unidad de procesamiento gráfico) para proporcionar

Más detalles

Es un lenguaje estructurado, tiene una abundante cantidad de operadores y tipos de datos.

Es un lenguaje estructurado, tiene una abundante cantidad de operadores y tipos de datos. Lenguaje C Un poco de historia C es un lenguaje de propósito general, es decir, se pueden desarrollar aplicaciones de diversas áreas. Dentro de sus principales características podemos mencionar que: Es

Más detalles

Computación de Propósito General en Unidades de Procesamiento Gráfico GPGPU

Computación de Propósito General en Unidades de Procesamiento Gráfico GPGPU Computación de Propósito General en Unidades de Procesamiento Gráfico () E. Dufrechou, P. Ezzatti, M. Pedemontey J.P. Silva Clases 4 Programación Contenido Modelo de programación Introducción Programación

Más detalles

cuevogenet Paralelización en CUDA de la Dinámica Evolutiva de Redes Génicas Dirigido por: Fernando Díaz del Río José Luis Guisado Lizar

cuevogenet Paralelización en CUDA de la Dinámica Evolutiva de Redes Génicas Dirigido por: Fernando Díaz del Río José Luis Guisado Lizar cuevogenet Paralelización en CUDA de la Dinámica Evolutiva de Redes Génicas Realizado por: Raúl García Calvo Dirigido por: Fernando Díaz del Río José Luis Guisado Lizar Objetivos Implementar un algoritmo

Más detalles

6. Enumere tres ventajas de los ULT frente a los KLT.

6. Enumere tres ventajas de los ULT frente a los KLT. 1 Tarea 3 Hilos 1. Cuales bloques de control de proceso deberían pertenecer a un bloque de control de hilo y cuáles a un bloque de control de proceso en un sistema multihilo? Para modelos monohilo deben

Más detalles

PROGRAMACIÓN AVANZADA DE GPUs PARA APLICACIONES CIENTÍFICAS

PROGRAMACIÓN AVANZADA DE GPUs PARA APLICACIONES CIENTÍFICAS Grupo de Ing. Electrónica aplicada a Espacios INteligentes y TRAnsporte Área Audio-Visual PROGRAMACIÓN AVANZADA DE GPUs PARA APLICACIONES CIENTÍFICAS Torrevieja (Alicante) Del 19 al 22 de Julio Álvaro

Más detalles

Concurrencia. Concurrencia

Concurrencia. Concurrencia Concurrencia Procesos y hebras Concurrencia Programación concurrente Por qué usar hebras y procesos? Ejecución de procesos Ejecución de hebras Hebras vs. Procesos Creación y ejecución de hebras La prioridad

Más detalles

Capítulo 3 CICLO DE VIDA DE UN PROGRAMA. Presentación resumen del libro: "EMPEZAR DE CERO A PROGRAMAR EN lenguaje C"

Capítulo 3 CICLO DE VIDA DE UN PROGRAMA. Presentación resumen del libro: EMPEZAR DE CERO A PROGRAMAR EN lenguaje C Presentación resumen del libro: "EMPEZAR DE CERO A PROGRAMAR EN lenguaje C" Autor: Carlos Javier Pes Rivas (correo@carlospes.com) Capítulo 3 CICLO DE VIDA DE UN PROGRAMA 1 OBJETIVOS Saber qué es la Ingeniería

Más detalles

Plan 95 Adecuado DEPARTAMENTO: ELECTRÓNICA CLASE: ELECTIVA DE ESPECIALIDAD ÁREA: TÉCNICAS DIGITALES HORAS SEM.: 4 HS. HORAS / AÑO: 64 HS.

Plan 95 Adecuado DEPARTAMENTO: ELECTRÓNICA CLASE: ELECTIVA DE ESPECIALIDAD ÁREA: TÉCNICAS DIGITALES HORAS SEM.: 4 HS. HORAS / AÑO: 64 HS. Plan 95 Adecuado ASIGNATURA: COMPUTACIÓN PARALELA CON PROCESADORES GRÁFICOS CODIGO: 95-0409 DEPARTAMENTO: ELECTRÓNICA CLASE: ELECTIVA DE ESPECIALIDAD ÁREA: TÉCNICAS DIGITALES HORAS SEM.: 4 HS. HORAS /

Más detalles

TEMA 10 INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS DISTRIBUIDOS. Introducción Hardware Software Aspectos de diseño

TEMA 10 INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS DISTRIBUIDOS. Introducción Hardware Software Aspectos de diseño TEMA 10 INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS DISTRIBUIDOS Introducción Hardware Software Aspectos de diseño 1 Introducción Aparecen en los 80 Desarrollo de Microprocesadores LAN Sistemas Distribuidos:

Más detalles

TEMA 9. SISTEMAS OPERATIVOS DISTRIBUIDOS

TEMA 9. SISTEMAS OPERATIVOS DISTRIBUIDOS TEMA 9. SISTEMAS OPERATIVOS DISTRIBUIDOS Introducción Hardware Software Aspectos de diseño 1 Introducción Aparecen en los 80 Desarrollo de Microprocesadores LAN Sistemas Distribuidos: Gran nº de procesadores

Más detalles

Lenguaje C. República Bolivariana de Venezuela Fundación Misión Sucre Aldea Fray Pedro de Agreda Introducción a la Programación III

Lenguaje C. República Bolivariana de Venezuela Fundación Misión Sucre Aldea Fray Pedro de Agreda Introducción a la Programación III República Bolivariana de Venezuela Fundación Misión Sucre Aldea Fray Pedro de Agreda Introducción a la Programación III Lenguaje C 1 Puntos previos Los códigos fuentes generados en C requieren ser compilados

Más detalles

Seminario II: Introducción a la Computación GPU

Seminario II: Introducción a la Computación GPU Seminario II: Introducción a la Computación GPU CONTENIDO Introducción Evolución CPUs-Evolución GPUs Evolución sistemas HPC Tecnologías GPGPU Problemática: Programación paralela en clústers heterogéneos

Más detalles

DESARROLLO DE APLICACIONES EN CUDA

DESARROLLO DE APLICACIONES EN CUDA DESARROLLO DE APLICACIONES EN CUDA Curso 2014 / 15 Procesadores Gráficos y Aplicaciones en Tiempo Real Alberto Sánchez GMRV 2005-2015 1/30 Contenidos Introducción Debugging Profiling Streams Diseño de

Más detalles

Arquitecturas de Altas Prestaciones y Supercomputación

Arquitecturas de Altas Prestaciones y Supercomputación Arquitecturas de Altas Prestaciones y Supercomputación Presentación del itinerario Julio de 2014 Arquitecturas de Altas Prestaciones y Supercomputación Julio de 2014 1 / 15 Agenda Introducción 1 Introducción

Más detalles

EL ORDENADOR HARDWARE SOFTWARE

EL ORDENADOR HARDWARE SOFTWARE EL ORDENADOR HARDWARE Y SOFTWARE Profesor: Julio Serrano Qué es y cómo funciona un Ordenador? Es un máquina electrónica que se encarga del tratamiento digital de la información de una forma rápida. Cómo

Más detalles

Java para no Programadores

Java para no Programadores Java para no Programadores Programa de Estudio Java para no Programadores Aprende a programar con una de las tecnologías más utilizadas en el mercado de IT. Este curso está orientado a quienes no tienen

Más detalles

TEMA 2: Organización de computadores

TEMA 2: Organización de computadores TEMA 2: Organización de computadores Procesadores Memorias Dispositivos de E/S 1 Computador Procesador, memoria, dispositivos de E/S CPU Unidad de control Unidad aritmética y lógica Registros Dispositivos

Más detalles

Nociones básicas de computación paralela

Nociones básicas de computación paralela Nociones básicas de computación paralela Javier Cuenca 1, Domingo Giménez 2 1 Departamento de Ingeniería y Tecnología de Computadores Universidad de Murcia 2 Departamento de Informática y Sistemas Universidad

Más detalles

Sistemas Operativos y Software Computacional Sistemas operativos y software computacional

Sistemas Operativos y Software Computacional Sistemas operativos y software computacional Sistemas operativos y software computacional 1 de 57 EL SISTEMA OPERATIVO Y LOS DISPOSITIVOS DE ALMACENAMIENTO 2 de 57 Definición de sistema operativo El sistema operativo es el programa (o software) más

Más detalles

INFORME MEMORIA CACHE Y MEMORIA VIRTUAL.

INFORME MEMORIA CACHE Y MEMORIA VIRTUAL. AIEP PROGRAMACIÓN COMPUTACIONAL FUNDAMENTOS DE PROGRAMACIÓN INFORME MEMORIA CACHE Y MEMORIA VIRTUAL. Por:Diego Menéndez Introducción. Ante la inmensa velocidad de los procesadores que a medida del tiempo

Más detalles

FUNCIONES. Identificador valido. Tipo-Funcion Identificador_de_la_funcion (Tipo par1,tipo par2 )

FUNCIONES. Identificador valido. Tipo-Funcion Identificador_de_la_funcion (Tipo par1,tipo par2 ) FUNCIONES Las funciones son el medio básico de que se vale C para construir programas. Un Programa es, básicamente, una colección de funciones entre las que se incluye una especial llamada main(), la función

Más detalles

Desarrollo de aplicaciones para dispositivos móviles (5)

Desarrollo de aplicaciones para dispositivos móviles (5) 1 Desarrollo de aplicaciones para dispositivos móviles (5) M.C. Ana Cristina Palacios García 3 Kernel de Linux: Incluye drivers del hardware, manejo de procesos y de memoria, seguridad, red y manejo de

Más detalles

También denominada adaptador de vídeo, es uno de los componentes más básicos e importantes del ordenador, ya que nos va a permitir visualizar toda la

También denominada adaptador de vídeo, es uno de los componentes más básicos e importantes del ordenador, ya que nos va a permitir visualizar toda la Conrado Perea También denominada adaptador de vídeo, es uno de los componentes más básicos e importantes del ordenador, ya que nos va a permitir visualizar toda la información con la que se trabaja. Antiguamente

Más detalles

Principios de Computadoras II

Principios de Computadoras II Departamento de Ingeniería Electrónica y Computadoras Ing. Ricardo Coppo rcoppo@uns.edu.ar Qué es un Objeto? Un objeto es una instancia de una clase Las clases actuán como modelos que permiten la creación

Más detalles

1. Computadores y programación

1. Computadores y programación 1. Computadores y programación Informática y computadora (RAE) Informática (Ciencia de la computación) Conjunto de conocimientos científicos y técnicos que hacen posible el tratamiento automático de la

Más detalles

Clústeres y procesamiento en paralelo XE1GNZ J O R G E F BARBOSA J ACOBO F E B R E R O DE 20 17

Clústeres y procesamiento en paralelo XE1GNZ J O R G E F BARBOSA J ACOBO F E B R E R O DE 20 17 Clústeres y procesamiento en paralelo XE1GNZ J O R G E F BARBOSA J ACOBO F E B R E R O DE 20 17 Al escuchar la palabra clúster se piensa en grandes maquinas exclusivas de los grandes de la computación

Más detalles

Tipos de datos y Operadores Básicos

Tipos de datos y Operadores Básicos Módulo I: Conceptos Básicos Tema 1. Qué es un ordenador? Tema 2. Cómo se representan los datos en un ordenador? Tema 3. Qué es un lenguaje de programación? Tema 4. Cómo se hace un programa informático?

Más detalles

Introducción a los sistemas de Multiprocesamiento Prof. Gilberto Díaz

Introducción a los sistemas de Multiprocesamiento Prof. Gilberto Díaz Universisdad de Los Andes Facultad de Ingeniería Escuela de Sistemas Introducción a los sistemas de Multiprocesamiento Prof. Gilberto Díaz gilberto@ula.ve Departamento de Computación, Escuela de Sistemas,

Más detalles

Tema 1: Introducción a los Sistemas Operativos

Tema 1: Introducción a los Sistemas Operativos Tema 1: Introducción a los Sistemas Operativos Yolanda Blanco Fernández yolanda@det.uvigo.es Qué es un Sistema Operativo (SO)? Un programa que actúa como intermediario entre el usuario y el hardware del

Más detalles

El Computador y sus Partes INTRODUCCIÓN A LAS TECNOLOGÍAS INFORMÁTICAS

El Computador y sus Partes INTRODUCCIÓN A LAS TECNOLOGÍAS INFORMÁTICAS El Computador y sus Partes INTRODUCCIÓN A LAS TECNOLOGÍAS INFORMÁTICAS Contenido El Sistema de Cómputo Software y Licencias Soporte Físico 2010 EISC - Introducción a las Tecnologías Informáticas 2 El Sistema

Más detalles

MINUTA: Taller en UAEMEX, Toluca. Construcción de Tecnología HPC

MINUTA: Taller en UAEMEX, Toluca. Construcción de Tecnología HPC MINUTA: Taller en UAEMEX, Toluca Construcción de Tecnología HPC de MESA: Taller DE construcción de Tacnología HPC Sesión: # 1 a la 5 INFORMACIÓN GENERAL FECHA: 213 al 17 de julio 2015 Construcción de Tecnología

Más detalles

Características Ventajas Desventajas Tipo de Núcleo Shell Gui. Para algunas cosas se debe de saber usar UNIX, muchos juegos no corren en Linux.

Características Ventajas Desventajas Tipo de Núcleo Shell Gui. Para algunas cosas se debe de saber usar UNIX, muchos juegos no corren en Linux. Nombre Sistema Operativo del Características Ventajas Desventajas Tipo de Núcleo Shell Gui Linux Unix Multitarea, multiusuario, redes y telecomunicaciones, internet, interconectividad, programación, portabilidad,

Más detalles

Guillermo Román Díez

Guillermo Román Díez Concurrencia Creación de Procesos en Java Guillermo Román Díez groman@fi.upm.es Universidad Politécnica de Madrid Curso 2016-2017 Guillermo Román, UPM CC: Creación de Procesos en Java 1/18 Concurrencia

Más detalles

Hilos. Módulo 4. Departamento de Informática Facultad de Ingeniería Universidad Nacional de la Patagonia San Juan Bosco. Hilos

Hilos. Módulo 4. Departamento de Informática Facultad de Ingeniería Universidad Nacional de la Patagonia San Juan Bosco. Hilos Hilos Módulo 4 Departamento de Informática Facultad de Ingeniería Universidad Nacional de la Patagonia San Juan Bosco Hilos Revisión Modelos Multihilados Librerías de Hilos Aspectos sobre Hilos Ejemplos

Más detalles

Arquitecturas GPU v. 2013

Arquitecturas GPU v. 2013 v. 2013 Stream Processing Similar al concepto de SIMD. Data stream procesado por kernel functions (pipelined) (no control) (local memory, no cache OJO). Data-centric model: adecuado para DSP o GPU (image,

Más detalles

Threads, SMP y Microkernels. Proceso

Threads, SMP y Microkernels. Proceso Threads, SMP y Microkernels Proceso Propiedad de los recursos a un proceso se le asigna un espacio de dirección virtual para guardar su imagen Calendarización/ejecución sigue una ruta de ejecución la cual

Más detalles

Introducción a la Computación. Herramientas Informáticas. Omar Ernesto Cabrera Rosero Universidad de Nariño

Introducción a la Computación. Herramientas Informáticas. Omar Ernesto Cabrera Rosero Universidad de Nariño Introducción a la Computación Omar Ernesto Cabrera Rosero Universidad de Nariño 6 de Julio 2010 Esquema Terminología Informática 1 Terminología Informática Computación e Informática Dato e Información

Más detalles

Repaso 02: Apuntadores y manejo de memoria dinámica

Repaso 02: Apuntadores y manejo de memoria dinámica Repaso 02: Apuntadores y manejo de memoria dinámica Solicitado: Ejercicios 02: Programación con memoria dinámica M. en C. Edgardo Adrián Franco Martínez http://www.eafranco.com edfrancom@ipn.mx @edfrancom

Más detalles

Programación Orientada a Objetos

Programación Orientada a Objetos Programación Orientada a Objetos PROGRAMACIÓN ORIENTADA A OBJETOS 1 Sesión No. 8 Nombre: El Modelo de diseño con UML Contextualización Los modelos que podemos crear con UML son varios, por lo que debemos

Más detalles

PROGRAMACIÓN CONCURRENTE

PROGRAMACIÓN CONCURRENTE PROGRAMACIÓN CONCURRENTE Lenguajes de Programación - Progr. Concurrente 1 Introducción El concepto fundamental de la programación concurrente es la noción de Proceso. Proceso: Cálculo secuencial con su

Más detalles

El Archivo. Concepto y finalidad 1

El Archivo. Concepto y finalidad 1 UF0347 Sistemas de archivo y clasificación de documentos El Archivo. Concepto y finalidad 1 Qué? Es importante saber aplicar las diferentes técnicas de archivo que hay, ya sea de modo convencional o informático,

Más detalles

UNIVERSIDAD DE GUADALAJARA

UNIVERSIDAD DE GUADALAJARA UNIVERSIDAD DE GUADALAJARA CENTRO UNIVERSITARIO DE LOS ALTOS DIVISIÓN DE ESTUDIOS EN FORMACIONES SOCIALES LICENCIATURA: INGENIERÍA EN COMPUTACIÓN UNIDAD DE APRENDIZAJE POR OBJETIVOS ARQUITECTURA DE COMPUTADORAS

Más detalles

V. OPTIMIZACIÓN PARA COMPUTACIÓN GPU EN CUDA

V. OPTIMIZACIÓN PARA COMPUTACIÓN GPU EN CUDA V. OPTIMIZACIÓN PARA COMPUTACIÓN GPU EN CUDA La arquitectura de una GPU es básicamente distinta a la de una CPU. Las GPUs están estructuradas de manera paralela y disponen de un acceso a memoria interna

Más detalles

Hilos. Módulo 4. Departamento de Ciencias e Ingeniería de la Computación Universidad Nacional del Sur

Hilos. Módulo 4. Departamento de Ciencias e Ingeniería de la Computación Universidad Nacional del Sur Hilos Módulo 4 Departamento de Ciencias e Ingeniería de la Computación Universidad Nacional del Sur Chapter 4: Threads Revisión Modelos Multihilados Librerías de Hilos Aspectos sobre Hilos Ejemplos de

Más detalles

Página 1 de 12 CONCEPTOS INFORMÁTICOS BÁSICOS

Página 1 de 12 CONCEPTOS INFORMÁTICOS BÁSICOS Página 1 de 12 CONCEPTOS INFORMÁTICOS BÁSICOS CONTENIDOS a. CONCEPTOS INFORMÁTICOS i. Informática ii. Sistema informático iii. Ordenador iv. El sistema binario v. Medidas de almacenamiento de la información

Más detalles

Apuntadores (Punteros)

Apuntadores (Punteros) Apuntadores (Punteros) x9ff10 X int 209 SESION 7 *ptr Definición Llamados también punteros. Un Apuntador es una variable que contiene una dirección de memoria, la cual corresponderá a un dato o a una variable

Más detalles

Tile64 Many-Core. vs. Intel Xeon Multi-Core

Tile64 Many-Core. vs. Intel Xeon Multi-Core Tile64 Many-Core vs. Intel Xeon Multi-Core Comparación del Rendimiento en Bioinformática Myriam Kurtz Francisco J. Esteban Pilar Hernández Juan Antonio Caballero Antonio Guevara Gabriel Dorado Sergio Gálvez

Más detalles

Tema: Microprocesadores

Tema: Microprocesadores Universidad Nacional de Ingeniería Arquitectura de Maquinas I Unidad I: Introducción a los Microprocesadores y Microcontroladores. Tema: Microprocesadores Arq. de Computadora I Ing. Carlos Ortega H. 1

Más detalles

Tarjeta PCI Express de 4 Puertos USB con Puertos USB-A y 2 Canales Dedicados

Tarjeta PCI Express de 4 Puertos USB con Puertos USB-A y 2 Canales Dedicados Tarjeta PCI Express de 4 Puertos USB 3.1 - con Puertos USB-A y 2 Canales Dedicados Product ID: PEXUSB314A2V Esta tarjeta USB 3.1 PCIe le permite agregar a su computadora cuatro puertos USB Type-A, a través

Más detalles

Tema: Funciones Virtuales y Polimorfismo.

Tema: Funciones Virtuales y Polimorfismo. Programación II. Guía No. 10 1 Facultad: Ingeniería Escuela: Computación Asignatura: Programación II Tema: Funciones Virtuales y Polimorfismo. Objetivos Comprender que es ligadura e identificar sus tipos.

Más detalles

Tema: Funciones Virtuales y Polimorfismo.

Tema: Funciones Virtuales y Polimorfismo. Programación II. Guía 10 1 Facultad: Ingeniería Escuela: Computación Asignatura: Programación II Tema: Funciones Virtuales y Polimorfismo. Objetivos Específicos Comprender que es ligadura e identificar

Más detalles

FUNCIONAMIENTO DEL ORDENADOR

FUNCIONAMIENTO DEL ORDENADOR FUNCIONAMIENTO DEL ORDENADOR COMPUTACIÓN E INFORMÁTICA Datos de entrada Dispositivos de Entrada ORDENADOR PROGRAMA Datos de salida Dispositivos de Salida LOS ORDENADORES FUNCIONAN CON PROGRAMAS Los ordenadores

Más detalles

Memoria. Organización de memorias estáticas.

Memoria. Organización de memorias estáticas. Memoria 1 Memoria Organización de memorias estáticas. 2 Memoria En memoria físicas con bus de datos sea bidireccional. 3 Memoria Decodificación en dos niveles. 4 Necesidad de cantidades ilimitadas de memoria

Más detalles