HPC

La computación de alto rendimiento implica usar la potencia de cálculo para resolver problemas complejos en ciencia, intengiería y gestión. Para lograr este objetivo, la computación de alto rendimiento se apoya en tecnologías computacionales como los clusters, los supercomputadores o la computación paralela.

Primero veamos las diferencias entre la computación serial y paralela:

Ejemplo de problema paralelo: los partidos de la copa del mundo.

En cada etapa (octavos, cuartos, semi y final) los partidos pueden ser ejecutados en forma paralela, mientras que la ejecución de etapas debe ser en forma serial. Es decir este problema es serial en el sentido horizontal, y paralelo en el sentido vertical.

Por qué es importante aprender HPC?

Ley de Moore: El número de transistores en un circuito integrado se duplica cada 2 años.

Entre 1968 a 2003 la performance de los microprocesadores aumentaron en promedio más del 50% por año. Gran parte de este incremento se debe al aumento de la densidad de transistores en los sistemas integrados. A medida que disminuye el tamaño de los transistores también aumenta su velocidad pero junto a esto el consumo de energía aumenta también. Mucho de la energía consumida se disipa como calor y cuando un circuito se calienta mucho puede empezar a tener fallos. Los circuitos refrigerados por aire ya ha llegado a su limite físico de discipación de calor. Es por esto que el número de ciclos que una CPU puede ejecutar por segundo (clock-speed) no aumenta desde el año 2000 (Intel Pentum 4).

Por lo tanto, desde el 2005 los productores de microprocesadores han decidido mejorar la performance a base de sistemas multi-core. En vez de desarrollar procesadores monolíticos más rápdidos han empezado a poner múltiple procesadores completos en los sistemas integrados.

Límites y costos de la computación paralela

Ley de Amdahl

Establece que la velocidad de un programa paralelo viene dada por:

v= (1 - p)-1

donde p: fracción de código paralelizable.

Considerando el número de procesadores:

v = (p/n + s)-1

donde n: nuemero de procesadores, s:fración serial.

Esto muestra que por más que nos forcemos en paralelizar al máximo un programa, este siempre tendrá un asíntota con respecto al numero de procesadores que usemos. Por ejemplo para un código que se puede paralelizar al 95\% nunca vamos a alcanzar una velocidad mayor a 20 veces la velocidad en serial.

Paralelismo automatico

Muchos compiladores cuando analizan el código identifican oportunidades para paralelizar, particularmente en loops. En tal caso uno podría analizar el código e identificar inhibidores e intentar eliminarlos de forma tal que el compilador pueda identificarlo y paralelizar.

De todas formas aún no existen implementaciones de compiladores que paralelizen automáticamente de forma eficiente y lo más común es que la paralelización se realice manualmente en base a directivas realizadas por el programador.


Diseño de programas paralelos

Sin duda, el primer paso a desarrollar un programa paralelo es entender en primer lugar el problema que se quiere resolver en paralelo. Y analizar antes si el problema es realmente paralelizable.

En caso que que sea paralelizable, es importante identificar:

En caso de que no sea un algoritmo paralelizable se puede buscar otros algoritmos posibles (si existen).

Ejemplo: Un ejemplo interesante para ilustrar esto es la serie de Fibonacci, esta seríe se construye algorítmicamente según:

fn+1 = fn + fn-1

donde los primeros dos elementos (f0 f1) son igual a 1. Este problema no es paralelizable por la dependencia del calculo de los elementos previos para la construcción de un nuevo elemento. Sin embargo existe un resultado matemático que podemos utilizar conocido como Fórmula de Binet:

fn= [ φn - (-φ-n) ] / √5

donde φ= (1 + √5) / 2 = 1.6180339887…

este resultado es altamente paralelizable ya que no hay dependencia alguna entre elementos de la serie. Cada procesador puede computar uno distinto, solo sería necesario sincronizar y ordenar los numeros computados al final.

Partición

El primer paso en todo programa paralelo es descomponer el problema en pedazos discretos de trabajo (particionar).

Hay dos formas básicas de particionar un problema:

Ejemplo: Imaginemos que tenemos 5 profesores para corregir los examenes de una clase de 100 estudiantes. La corrección se puede organizar de dos formas: Cada profesor corrige un ejercicio de los 100 examenes (paralelización de tareas); la otra alternativa es que todos los profesores corrijan la totalidad del examen pero cada uno corrige 20 examenes (paralelización de datos). Acá las unidades de procesamientos serían los 5 profesores, los “datos” serían los estudiantes y las tareas (ó tasks) serian los ejercicios a corregir.

Hay varios elementos involucrados en la coordinación de un problema paralelo:

Comunicación

La comunicación sólo es necesaria cuando los tasks comparten datos. Si el problema a tratar se puede descomponer en muchos pedazos autónomos, entonces no es necesario hacer eso de ella. Algunos factores a considerar:

Sincronización

Tipos de sincronización:

Balance de carga

El balance de carga (load balancing) se refiera a la forma en que se distribuye la cantidad de trabajo entre tasks de forma tal de reducir la capacidad osciosa. Las estrategias posibles son:

Granularidad

Como ya se dijo, la granularidad hace referencia a la relación entre computación y comunicación.

Inhibidores de paralelismo

Dependencia

Decimos que hay dependencia entre instrucciones cuando el orden de ejecución de estos afecta el resultado del programa. También existe la dependencia de datos, esta resulta del uso múltiple de la misma locación de datos por distintos tasks. La dependencia es importante ya que es el principal inhibidor de paralelismo.

Input/Output

Las operaciones I/O son generalmente inhibidores de paralelismo. sin embargo existen sistemas de archivos adaptados a paralelización, por ejemplo: GPFS, Lustre, HDF


Nociones del hardware

Arquitectura de Von Neuman

Para entender la computación paralela es necesario tener algunos conocimientos básicos de cuales son los componentes de una computadora y como funciona. La mayoría de las computadoras electrónicas siguen la arquitectura de John Von Neuman también conocida como computadora de almacenamiento de programas, en ella tanto los programas, las instrucciones y los datos se guardan en una memoria electronica.

Los componentes de estas computadoras son:

  1. Memoria
    • Cada espacio en la memoria tiene una dirección identificador llamado memory address.
    • RAM (Random Access Memory) Es una memoria en la que el accesso a cualquier porción de información en ella demora el mismo tiempo (es una idealización).
    • cache es la parte de la memoria de accesso más inmediato para un procesador.
  2. Unidad de Control (CU): se encarga de buscar instrucciones y datos de la memoria, decodificarlos y coordinar operaciones (secuencialmente) para resolver alguna tarea.
  3. Unidad Lógica/Aritmética (ALU). ejecuta operaciones lógicas y aritméticas.
  4. Dispositivo de Input/Output: hace referencia a la interfaz entre el humano (operador) y la computadora.

Comunmente denominámos Unidad de Procesamiento Central (CPU) al conjunto de la CU, ALU y una pequeña porción de memoria muy rápida llamada registro. La CU tiene un registro especial llamado contador que guarda la dirección de la próxima instrucción a ejecutar.

Las instrucciones y los datos son transferidos entre CPU y Memoria a travez de una red interconectada. Esto tradicionalmente se hacia a travez de un bus que consiste en una colección de cables y un hardware que controlaba el acceso a estos. Hoy en día se usa otro sistema.

La separación entre CPU y Memoria genera un importante cuello de botella ya que la velocidad a la que la CPU ejecuta instrucciones es mucho mayor a la velocidad en la que puede tomar datos de la memoria para ejecutarlos.

Procesos, multitasking y threads

El sistema operativo (SO) es el software que maneja el hardware y los recursos en la computadora. Determina que programas ejecutar, cuando y que memoria asignarle. Cuando uno ejecuta un programa el SO crea un proceso, que consise en:

Los SO modernos son multitasking es decis que sportan la ejecución conjunta de varios programas en “simultaneo”. En estos SO si un proceso necesita esperar algún recurso (por ejemplo leer un dato en una memoria externa, este se bloquea, es decir deja de ejecutarse así el SO puede seguir con otros procesos. Sin embargo parte del programa podría continuar ejecutandose aunque parte del programa esté esperando el recurso. La creación de threads proveen de un mecanismo para los progrmadores de dividir sus programas en task independientes con la propiedad que cuando un thread está bloqueado otro puede seguir ejecutandose. Es más rápido switchar entre threads que entre procesos ya que son más “livianos”. Los threads están contenidos dentro de un proceso, de forma que comparten ejecutable, memoria y otros recursos.

Modificaciones al modelo de Von Neumann

Caching

El cache es una colección de ubicaciones de memoria de más rapido acceso que otras ubicaciones de memoria. En particular el cache de la CPU pude estar ubicada en el mismo chip que la CPU o en un chip separado pero de acceso más rapido. En general lo que se guarda en el cache sigue la idea de que los programas tienden a usar datos e instrucciones que están físicamente cerca entre ellos (por ejemplo recorrer un array), esta idea se conoce como cercanía ó locality.

Para explotar este principio de locality los sistemas usan un sistema interconectado más ancho para acceder a datos e instrucciones. Es decir el acceso a memoria se hace en bloques. Estos bloques se conocen como cache blocks ó cache lines, estos almacenan entre 8/16 veces más información que una ubicación individual de memoria. Si bien solemos pensar al cache como un bloque homogeneo, suele tener niveles con distinta velocidad de acceso.

Mapeo del cache

Hay distintas formas de asignar a cada ubicación en memoria su correspondiente cache line:

Memoria virtual

Paralelismo de bajo nivel

Computadoras paralelas (clusters)

Las computadoras paralelas no son más que un conjunto de Memorias y CPUs conectadas en una red de forma inteligente. Basado en como es el acceso a la memoria de las unidades de procesamiento podemos clasificar a las computadores (clusters) en:

  1. Memoria distribuida (shared memory).
  2. Memoria compartida (distributed memory).

Shared Memory

En teoría estas arquitecturas permiten que los procesadores accedan a cualquier porción de la memoria libremente. Aunque los procesadores operen independientemente todos comparten los mismos recursos de memoria. Dentro de las arquitecturas de memoria compartida existen dos tipos básicos:

Ventajas:

Desventajas:

Distributed Memory

En estas los procesadores tienen su propia memoria local. En ellas no existe el concepto de global address space a lo largo de todos los procesadores. Como cada procesador tiene su propia memoria local, opera de forma independiente. Por lo tanto tampoco aplica el concepto de cache cherency.

Ventajas:

Desventajas:

Hibridas

Las grandes supercomputadoras del mundo hoy en día adoptan una arquitectura míxta.

Tipos de paralelismo y taxonomía de Flynn

Flynn clasificó las computadoras en base a dos características: el paralelismo de procesos y paralelismo de datos.

Modelos de programación paralela

Los modelos de programación paralela son abstracciones por encima del hardware, y por lo tanto es importante resaltar que si bien algunos modelos se usan principalmente en determinadas arquitecturas, cualquiera de ellos puede ser usada en teoría en cualquier computadora independientemente de su arqutectura.

Existen varios modelos de programación paralela en uso:


Definiciones y conceptos:


Edit this page.

Licensed MIT.