You are on page 1of 8

UJAT

Universidad Juárez Autónoma de Tabasco

DACYTi

División Académica de Ciencias y Tecnologías de la Información;

Carrera:
Ing. Sistemas computacionales

Materia:
Sistemas distribuidos

Catedrático:
Juan Roberto Hernández Garibay

Alumno:
Ricardo Custodio García

Fecha:
05/09/2023
Consideraciones de programación paralela
Ken Kennedy • Jack Dongarra • Geoffrey Fox • William Gropp • Dan Reed
El objetivo principal de este capítulo es presentar los problemas comunes que enfrenta un
programador al implementar una aplicación paralela. El tratamiento asume que el lector
está familiarizado con la programación de un uniprocesador utilizando un lenguaje
convencional, como Fortran. El principal desafío de la programación paralela es
descomponer el programa en subcomponentes que se pueden ejecutar en paralelo. Sin
embargo, para entender algunos de los problemas de bajo nivel de descomposición, el
programador debe tener una vista simplificada de la arquitectura de máquina paralela.
Por lo tanto, comenzamos nuestro tratamiento con una revisión de este tema, con el
objetivo de identificar las características que son más importantes para que el
programador paralelo entienda. Esta discusión, que se encuentra en la Sección 3.1, se
centra en dos organizaciones principales de máquinas paralelas -memoria compartida y
memoria distribuida- que caracterizan a la mayoría de las máquinas actuales. La sección
también trata los híbridos de los dos diseños principales de memoria.
Las arquitecturas paralelas estándar soportan una variedad de estrategias de
descomposición, como la descomposición por tarea (paralelismo de tareas) y la
descomposición por datos (paralelismo de datos). Nuestro tratamiento introductorio se
concentrará en el paralelismo de datos porque representa la estrategia más común para
programas científicos en máquinas paralelas. En el paralelismo de datos, la aplicación se
descompone subdividiendo el espacio de datos sobre el que opera y asignando diferentes
procesadores al trabajo asociado con diferentes subespacios de datos. Por lo general, esta
estrategia implica compartir algunos datos en los límites, y el programador es
responsable de asegurar que este intercambio de datos se maneje correctamente, es decir,
los datos computados por un procesador y utilizados por otro se sincronizan
correctamente.
Una vez elegida una estrategia de descomposición específica, debe ser implementada.
Aquí, el programador debe elegir el modelo de programación a utilizar. Los dos modelos
más comunes son los siguientes:
• El modelo de memoria compartida, en el que se asume que todas las
estructuras de datos se asignan en un espacio común accesible desde cada
procesador.
• El modelo de paso de mensajes, en el que se asume que cada procesador (o
proceso) tiene su propio espacio de datos privado, y los datos deben
moverse explícitamente entre los espacios según sea necesario.

En el modelo de paso de mensajes, las estructuras de datos se distribuyen a través de las


memorias del procesador; si un procesador necesita usar un elemento de datos que no se
almacena localmente, el procesador propietario de ese elemento de datos debe "enviarlo"
explícitamente al procesador solicitante. Este último debe ejecutar una operación
explícita de "recepción", que se sincroniza con el envío, antes de poder utilizar el
elemento de datos comunicado. Estas cuestiones se examinan en la sección 3.2.

Para lograr un alto rendimiento en máquinas paralelas, el programador debe preocuparse


porla escalabilidad y el equilibrio de carga. Generalmente, se cree que una aplicación es
escalable si las configuraciones paralelas más grandes pueden resolver problemas
proporcionalmente mayores en el mismo tiempo de ejecución que los problemas más
pequeños en configuraciones más pequeñas. Para entender este problema, en la Sección
3.3.1 se introduce una fórmula que define velocidad paralela y explora sus implicaciones.
El equilibrio de carga generalmente significa que los procesadores tienen
aproximadamente la misma cantidad de trabajo, por lo que ningún procesador sostiene la
solución completa. Para equilibrar la carga computacional en una máquina con
procesadores de igual potencia, el programador debe dividir el trabajo y las
comunicaciones uniformemente. Esto puede ser difícil en aplicaciones aplicadas a
problemas que son desconocidos en tamaño hasta el tiempo de ejecución.
Un cuello de botella particular en la mayoría de las máquinas paralelas es el rendimiento
de la jerarquía de memoria, tanto en un solo nodo como en toda la máquina. En la
Sección 3.4, discutimos varias estrategias para mejorar la reutilización de datos por un
solo procesador. Estas estrategias típicamente involucran algún tipo de "bloqueo" de
bucle o "minería a cielo abierto", por lo que las subcomputaciones completas encajan en
la caché.
Los problemas irregulares o adaptativos presentan desafíos especiales para las máquinas
paralelas porque es difícil mantener el equilibrio de carga cuando se desconoce el tamaño
de los subproblemas hasta el tiempo de ejecución o si el tamaño del problema puede
cambiar después de que comience la ejecución. Se requieren métodos especiales que
impliquen la reconfiguración en tiempo de ejecución de un cálculo para hacer frente a
estos problemas. Estos métodos se examinan en la sección 3.3.3.
Varios aspectos de la programación de máquinas paralelas son mucho más complicados
que sus contrapartes para sistemas secuenciales. La depuración paralela, por ejemplo,
debe abordar las posibilidades de condiciones de carrera o ejecución fuera de orden (ver
Sección 3.5). El análisis del rendimiento y el ajuste deben abordar los problemas
especialmente difíciles de detectar desequilibrios de carga y cuellos de botella de
comunicación (véase la sección 3.6). Además, debe presentar información de diagnóstico
al usuario en un formato relacionado con la estructura del programa y el modelo de
programación. Finalmente, la entrada/salida en máquinas paralelas, particularmente
aquellas con memoria distribuida, presenta problemas de cómo leer archivos que se
distribuyen a través de discos en un sistema en memorias que se distribuyen con los
procesadores (ver Sección 3.7).
Estos temas no representan todas las cuestiones de la programación paralela. Esperamos,
sin embargo, que un debate sobre ellos transmita parte de la terminología y la intuición
de la programación paralela. Al hacerlo, preparará el escenario para el resto de este
libro.
Consideraciones Arquitectónicas
El capítulo 2 proporcionó una revisión detallada de las arquitecturas informáticas
paralelas. En este capítulo, proporcionamos una introducción simple a estos temas que
cubre la mayoría de las cuestiones importantes necesarias para entender la programación
paralela.
En primer lugar, como se explica en el capítulo 2, observamos que la mayoría de las
máquinas paralelas modernas se dividen en dos categorías básicas:
Máquinas de memoria compartida, que tienen un solo espacio de direcciones
compartidas al que puede acceder cualquier procesador.
Máquinas de memoria distribuida, en las que la memoria del sistema se empaqueta con
nodos individuales de uno o más procesadores y se requiere comunicación para
proporcionar datos de la memoria de un procesador a un procesador diferente.
Memoria Compartida
La organización de una máquina de memoria compartida se muestra en la Figura 2.5. La
Figura 3.1 muestra un diagrama ligeramente más detallado de un sistema de memoria
compartida con cuatro procesadores, cada uno con una caché privada, interconectado a
una memoria compartida global a través de un solo bus del sistema. Esta organización se
denomina típicamente multiprocesador simétrico (SMP).
En un multiprocesador simétrico, cada procesador puede acceder a todas las ubicaciones
de la memoria global mediante operaciones de carga estándar. El hardware asegura que
los caches son "coherentes" al ver el bus del sistema e invalidar copias en caché de
cualquier bloque en el que se escriba. Este mecanismo es generalmente invisible para el
usuario, excepto cuando diferentes procesadores están tratando simultáneamente de
escribir en la misma línea de caché, lo que puede causar que la línea de caché al ping-
pong entre dos caches diferentes, una situación conocida como thrashing. Para evitar este
problema, el programador y el sistema de programación deben tener cuidado con las
estructuras de datos compartidos y las estructuras de datos no compartidos que se pueden
ubicar en el mismo bloque de caché, una situación conocida como intercambio falso. La
sincronización de los accesos a las estructuras de datos compartidos es un problema
importante en los sistemas de memoria compartida: corresponde al programador
asegurarse de que las operaciones de diferentes procesadores en una estructura de datos
compartida dejen esa estructura de datos en un estado consistente. En la sección 2.2.1 se
examinan varios modelos de coherencia de la memoria.
El principal problema con el sistema de memoria compartida descrito anteriormente es
que no es escalable a un gran número de procesadores. La mayoría de los sistemas
basados en bus están limitados a 32 o menos procesadores debido a la contención en el
bus. Si el bus es reemplazado por un interruptor de barra transversal, los sistemas pueden
escalar hasta 128 procesadores, aunque el costo del interruptor aumenta a medida que el
cuadrado del número de procesadores, lo que hace que esta organización sea poco
práctica para un número verdaderamente grande de procesadores. Los interruptores
multietapa se pueden hacer para escalar mejor a costa de latencias más largas a la
memoria.
Memoria Distribuida
Las limitaciones de escalabilidad de la memoria compartida han llevado a los
diseñadores a utilizar organizaciones de memoria distribuida como la que se muestra en
la Figura 3.2. Aquí la memoria compartida global ha sido reemplazada por una memoria
local más pequeña adjunta a cada procesador. La comunicación entre las configuraciones
procesador-memoria es a través de una red de interconexión. Estos sistemas se pueden
hacer escalables si se utiliza una red de interconexión escalable. Por ejemplo, un
hipercubo tiene un costo proporcional a nlg(n) donde n es el número de procesadores.
La ventaja de un diseño de memoria distribuida es que el acceso a los datos locales puede
ser bastante rápido. Por otro lado, el acceso a memorias remotas requiere mucho más
esfuerzo. La mayoría de los sistemas de memoria distribuida admiten un modelo de
programación de pasiónde mensajes, en el que el procesador que posee un dato debe
enviarlo a cualquier procesador que lo necesite. Estos pasos de comunicación "enviar-
recibir" suelen incurrir en largos tiempos de inicio, aunque el ancho de banda después de
la puesta en marcha puede ser alto. Por lo tanto, en los sistemas de transmisión de
mensajes, normalmente vale la pena enviar menos mensajes, más largos.
El principal problema de programación de los sistemas de memoria distribuida es la
gestión de la comunicación entre procesadores. Por lo general, esto significa la
consolidación de mensajes entre el mismo par de procesadores y la comunicación y el
cómputo de superposición para que las largas latencias se ocultan. Además, la colocación
de datos es importante para que el menor número posible de referencias de datos requiera
comunicación.
Hybrid Systems
Como se ve en el Capítulo 2, hay varias maneras en que los dos paradigmas de la
memoria se combinan. Algunas máquinas de memoria distribuida permiten que un
procesador acceda directamente a un dato en una memoria remota. En estos sistemas de
memoria compartida distribuida (DSM), la latencia asociada con una carga varía con la
distancia a la memoria remota. La coherencia de caché en los sistemas DSM es un
problema complejo que suele ser manejado por una sofisticada unidad de interfaz de red.
Dado que los sistemas DSM tienen tiempos de acceso más largos a la memoria remota, la
colocación de datos es una consideración de programación importante.
Para sistemas paralelos muy grandes, una arquitectura híbrida llamada clústeres SMP
común. Un clúster SMP parece un sistema de memoria distribuida en el que cada uno de
los componentes individuales es un multiprocesador simétrico en lugar de un solo nodo
de procesador. Este diseño permite una alta eficiencia paralela dentro de un nodo
multiprocesador, al tiempo que permite que los sistemas se escalen a cientos o incluso
miles de procesadores. La programación para clústeres SMP proporciona todos los
desafíos de los sistemas de memoria compartida y distribuida. Además, requiere una
cuidadosa reflexión sobre cómo dividir el paralelismo dentro y entre los nodos
computacionales.
Jerarquía de la Memoria
Como se discutió en el Capítulo 2, el diseño de jerarquías de memoria es una parte
integral del diseño de sistemas de computación paralelos porque la jerarquía de memoria
es un factor determinante en el rendimiento de los nodos individuales en la matriz de
procesadores. La Figura 3.3 muestra una jerarquía típica de memoria. Aquí el procesador
y una memoria caché de nivel 1 (LI) se encuentran en el chip, y una mayor caché de
nivel 2 (L2) se encuentra entre el chip y la memoria.
Cuando un procesador ejecuta una instrucción de carga, la caché de LI se interroga
primero para determinar si el dato deseado está disponible. Si lo es, el dato se puede
entregar al procesador en dos a cinco ciclos de procesamiento. Si el dato no se encuentra
en la caché LI, el procesador se detiene mientras se interroga la caché L2. Si el dato
deseado se encuentra en L2, entonces la pérdida puede durar solo de 10 a 20 ciclos. Si el
dato no se encuentra en cualquier caché, un missis cache completo tomado con un retraso
de posiblemente 100 ciclos o más. Cada vez que ocurre un error, el dato se guarda en
cada caché en la jerarquía, si no está ya allí. Tenga en cuenta que, en las máquinas
modernas, las caché transfieren datos en un bloque de caché de tamaño mínimo, de modo
que cada vez que se carga un dato a ese caché, todo el bloque que contiene ese dato viene
con él.
El rendimiento de la jerarquía de memoria está determinado por dos parámetros de
hardware: latencia, que es el tiempo necesario para obtener un dato deseado de la
memoria, y ancho de banda, que es el número de bytes por unidad de tiempo que se
pueden entregar desde la memoria a toda velocidad. Las latencias largas aumentan el
costo de los errores de caché, lo que ralentiza el rendimiento, mientras que el ancho de
banda limitado puede hacer que las aplicaciones se conviertan en "limitadas a la
memoria", es decir, se detengan continuamente a la espera de datos. Estos dos factores se
complican por la naturaleza multinivel de las jerarquías de memoria, porque cada nivel
tendrá un ancho de banda y latencia diferentes al siguiente nivel. Por ejemplo, el SGI
Origin 2000 puede entregar unos 4 bytes por ciclo de máquina desde la caché LI al
procesador y 4 bytes por ciclo desde la caché L2 a la caché LI, pero solo puede entregar
unos 0,8 bytes por ciclo desde la memoria a la caché LI [272],
Otro parámetro importante que afecta el rendimiento de la memoria en un procesador
único es la longitud del bloque de caché estándar (o línea de caché). La mayoría de los
sistemas de caché solo transferirán bloques de datos entre niveles de la jerarquía de
memoria. Si se utilizan todos los datos transferidos en un bloque, no se desperdicia ancho
de banda. En ese caso, el costo de la pérdida de caché se puede amortizar sobre todos los
datos del bloque. Si solo se utilizan uno o dos elementos de datos, la latencia promedio
es mucho mayor y el ancho de banda efectivo mucho menor.
Existen dos tipos de estrategias para superar los problemas de latencia. Latencia oculta
intenta superponer la latencia de un error con la computación. Prefetching de la memoria
caché
es una estrategia de ocultación de latencia. La tolerancia a la latencia, por otro lado, intenta
reestructurar un cálculo para hacerlo menos sujeto a problemas de rendimiento debido a las
latencias largas. La técnica de tolerancia de latencia más importante es el bloqueo de caché,
que acerca los accesos a las mismas ubicaciones en el tiempo para que los accesos después
de la primera puedan encontrar los datos deseados en caché.
Las estrategias que mejoran la reutilización en caché también mejoran la utilización
efectiva del ancho de banda. Tal vez la forma más importante de asegurar una buena
utilización del ancho de banda es organizar los datos y los cálculos para usar todos los
elementos en una línea de caché cada vez que se obtiene de la memoria. Asegurar que los
cálculos accedan a los arrays de datos a pasos agigantados es un ejemplo de cómo se podría
hacer esto.
Las jerarquías de memoria en máquinas paralelas son más complicadas debido a la
existencia de múltiples caches en sistemas de memoria compartida y las largas latencias a
memorias remotas en configuraciones de memoria distribuida. También puede haber
interferencias entre las transferencias de datos entre memorias y de memoria local a un
procesador.
Programas de descomposición para el paralelismo
Dado que ha decidido implementar un programa para una máquina paralela, hay cuatro
problemas principales que debe tratar. En primer lugar, debe tener una forma de identificar
los componentes de la computación que pueden ejecutarse de forma segura en paralelo. En
segundo lugar, es necesario adoptar una estrategia para descomponer el programa en
componentes paralelos. En tercer lugar, debe escribir el programa paralelo, que requiere
que elija un modelo de programación y una interfaz para la implementación. Por último,
debe elegir una aplicación stylethat es eficaz para la aplicación dada y que funciona bien
con el modelo de programación elegido. En esta sección, discutimos cada uno de estos
temas e ilustrarlos con un ejemplo extendido al final.

You might also like