COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta...

182
COMPUTADORES PARALELOS Computación de Alta Velocidad A. Arruabarrena — J. Muguerza Konputagailuen Arkitektura eta Teknologia saila Informatika Fakultatea — Euskal Herriko Unibertsitatea

Transcript of COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta...

Page 1: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

COMPUTADORES PARALELOS

Computación de Alta Velocidad

A. Arruabarrena — J. Muguerza

Konputagailuen Arkitektura eta Teknologia saila Informatika Fakultatea — Euskal Herriko Unibertsitatea

Page 2: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 3: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

COMPUTADORES PARALELOS

Computación de Alta Velocidad

A. Arruabarrena — J. Muguerza

Konputagailuen Arkitektura eta Teknologia saila Informatika Fakultatea — Euskal Herriko Unibertsitatea

septiembre 2012

Page 4: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 5: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 6: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 7: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

ÍNDICE

Introducción ......................................................................... 1

Capítulo 1. Computadores Vectoriales ............................................... 7

1.1 ¿Qué es un computador vectorial? ................................................................... 7 1.1.1 Algunos problemas ............................................................................................... 10

1.1.1.1 La memoria de un computador vectorial .......................................... 10 1.1.1.2 Unidades funcionales vectoriales ........................................................ 11 1.1.1.3 Registros vectoriales ............................................................................... 11 1.1.1.4 Programas vectoriales ............................................................................ 12

1.1.2 Arquitectura y lenguaje-máquina ...................................................................... 13

1.2 Dependencias de datos ..................................................................................... 15 1.2.1 Encadenamiento (chaining) ........................................................................................... 16

1.2.1.1 Encadenamiento con dos instrucciones ............................................ 17 1.2.2 Tablas de ejecución de las instrucciones ........................................................ 18

1.3 Dependencias estructurales .............................................................................. 20 1.3.1 Buses de memoria (unidades funcionales LV/SV) ........................................ 20 1.3.2 Conflictos en el uso de los módulos de memoria ........................................ 22

1.3.2.1 Una sola operación de memoria ......................................................... 22 1.3.2.2 Varias operaciones de memoria .......................................................... 26

1.3.3 Longitud de los registros vectoriales (strip mining) ...................................... 29

1.4 Velocidad de cálculo de los computadores vectoriales ............................ 30 1.4.1 Velocidad de cálculo en función de la longitud de los vectores .............. 31

1.4.1.1 R∞ y N1/2..................................................................................................... 31

1.4.1.2 Speed-up o factor de aceleración ....................................................... 33 1.4.1.3 Nv ................................................................................................................. 34

1.4.2 Influencia del código escalar. Ley de Amdahl. .............................................. 34

1.5 Técnicas de compilación para generar código vectorial ........................... 37 1.5.1 Dependencias de datos entre instrucciones .................................................. 38 1.5.2 Vectorización ......................................................................................................... 40

1.5.2.1 Vectores de una dimensión .................................................................. 40 1.5.2.2 Vectores de N dimensiones ................................................................. 44 1.5.2.3 Condición para vectorizar un bucle ................................................... 45

1.5.2.4 Test de dependencias ............................................................................ 46 1.5.3 Optimizaciones...................................................................................................... 50

1.5.3.1 Sustitución global hacia adelante (global forward substitution) ................... 50

1.5.3.2 Eliminación de las variables de inducción ......................................... 51 1.5.3.3 Antidependencias (DR, WAR) ...................................................................... 52

Page 8: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ vi ▪ ÍNDICE

1.5.3.4 Dependencias de salida (RR, WAW) ........................................................ 53

1.5.3.5 Intercambio de bucles (loop-interchanging) .......................................... 54 1.5.3.6 Expansión escalar (scalar expansion) ........................................................ 56 1.5.3.7 Fusión de bucles (loop fusion) ..................................................................... 57 1.5.3.8 Colapso de bucles (loop collapsing) ......................................................... 58 1.5.3.9 Otras optimizaciones ............................................................................. 59

1.5.4 Vectores de máscara y vectores de índices ................................................... 60 1.5.4.1 Uso de máscaras ..................................................................................... 60 1.5.4.2 Vectores de índices ................................................................................ 61

1.6 Resumen ................................................................................................................ 64

Capítulo 2. Computadores Paralelos (conceptos básicos) ................. 69

2.1 Introducción ......................................................................................................... 69 2.2 Computadores DM-SIMD ................................................................................. 71 2.3 Computadores MIMD ........................................................................................ 73

2.3.1 Memoria compartida (shared memory) .................................................................. 73 2.3.2 Memoria privada o distribuida (distributed memory) ....................................... 74 2.3.3 Memoria lógicamente compartida pero físicamente distribuida

(distributed shared memory) ......................................................................................... 75 2.3.4 Clusters, constellations... y otros ....................................................................... 76

2.4 Algunos problemas ............................................................................................. 77 2.5 Rendimiento del sistema paralelo (leyes de Amdahl y Gustafson) ......... 79

Capítulo 3. Coherencia de los Datos en los Computadores SMP ...................................................................................... 83

3.1 Presentación del problema y revisión de conceptos .................................. 83 3.1.1 Coherencia de los datos en los sistemas de un solo procesador ............. 84 3.1.2 Coherencia de los datos en los multiprocesadores de memoria

compartida (SMP) ................................................................................................. 85 3.1.3 Falsa compartición ................................................................................................ 86 3.1.4 Definición de la coherencia................................................................................ 87

3.2 Protocolos de coherencia snoopy .................................................................. 88 3.2.1 Estados de los bloques en la memoria cache y señales de control .......... 90 3.2.2 Protocolos de invalidación ................................................................................. 93

3.2.2.1 Un protocolo de tres estados, MSI ..................................................... 94 3.2.2.2 El protocolo Illinois, MESI ...................................................................... 97 3.2.2.3 El protocolo Berkeley, MOSI ................................................................ 99 3.2.2.4 Resumen de los protocolos de invalidación................................... 100

3.2.3 Protocolos de actualización ............................................................................. 101 3.2.3.1 El protocolo Firefly, MSE(I) .................................................................. 101 3.2.3.2 El protocolo Dragon, MOES(I) ........................................................... 103

3.2.4 Resumen de los protocolos de tipo snoopy ................................................ 105

Page 9: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

ÍNDICE ▪ vii ▪

3.3. Implementación de los protocolos snoopy ................................................ 105 3.3.1 Problemas ............................................................................................................. 105

3.3.1.1 Directorio de la memoria cache ........................................................ 106 3.3.1.2 Búferes de escritura .............................................................................. 107 3.3.1.3 Protocolo de petición de bus ............................................................. 108 3.3.1.4 Atomicidad: estado del controlador snoopy .................................. 109

3.3.2 El protocolo Illinois y la atomicidad ................................................................ 110 3.3.2.1 Carreras: estados transitorios, señales BRQ y BGN ..................... 110 3.3.2.2 Deadlock, livelock, starvation ............................................................ 112

3.4 Snoopy jerárquico ............................................................................................. 113 3.4.1 Lecturas .................................................................................................................. 115 3.4.2 Escrituras ................................................................................................................ 116

Capítulo 4. Sincronización de Procesos en los Computado-res SMP .............................................................................. 119

4.1 Introducción ....................................................................................................... 119 4.2 Exclusión mutua (mutual exclusion) ............................................................. 123

4.2.1 Instrucciones Test&Set y Swap ........................................................................ 125 4.2.1.1 Instrucción Test&Set ............................................................................. 125 4.2.1.2 Instrucción Swap ................................................................................... 125 4.2.1.3 Análisis del tráfico ................................................................................. 126 4.2.1.4 Procedimiento Test&Set with backoff .............................................. 127 4.2.1.5 Procedimiento Test-and-Test&Set ...................................................... 128 4.2.1.6 Resumen de características ................................................................ 130

4.2.2 Instrucciones Load Locked / Store Conditional y Compare&Swap ....... 131 4.2.2.1 Instrucciones LL y SC ........................................................................... 132 4.2.2.2 Instrucción Compare&Swap ............................................................... 134 4.2.2.3 Algunos problemas con las instrucciones LL/SC ........................... 135

4.2.3 Instrucciones Fetch&Op .................................................................................... 136 4.2.4 Alternativas para reducir el tráfico .................................................................. 137

4.2.4.1 Tickets ...................................................................................................... 137 4.2.4.2 Vectores de cerrojos ............................................................................ 139

4.3 Sincronización "punto a punto" mediante eventos ................................... 141 4.4 Sincronización mediante barreras ................................................................. 142

4.4.1 Una barrera sencilla ............................................................................................ 142 4.4.2 Barreras reutilizables .......................................................................................... 143 4.4.3 Eficiencia ................................................................................................................ 145

4.5 Resumen .............................................................................................................. 146

Capítulo 5. Consistencia de la Memoria en los Computa-dores Paralelos ................................................................ 149

5.1 Introducción ....................................................................................................... 149

Page 10: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ viii ▪ ÍNDICE

5.1.1 Sistemas de un solo procesador ...................................................................... 149 5.1.2 Sistemas multiprocesador ................................................................................. 150 5.1.3 Semántica de los programas y orden de ejecución de las

instrucciones ......................................................................................................... 151 5.1.4 Atomicidad de las instrucciones ...................................................................... 153 5.1.5 Modelos de consistencia ................................................................................... 154

5.2 Consistencia secuencial (SC, sequential consistency) .............................. 155 5.2.1 Orden y atomicidad de las instrucciones de memoria .............................. 156 5.2.2 Efectos en el hardware y en el compilador .................................................. 158

5.3 Modelos relajados (relaxed) ........................................................................... 159 5.3.1 Total Store Ordering (TSO) / Processor Consistency (PC) ....................... 160 5.3.2 Partial Store Ordering (PSO) ............................................................................ 162 5.3.3 Modelos más relajados ...................................................................................... 163

5.3.3.1 Weak Ordering (WO) .......................................................................... 164 5.3.3.2 Release Consistency (RC) ................................................................... 164

5.4 Resumen y perspectivas .................................................................................. 166

Capítulo 6 La Red de Comunicación de los Computadores Paralelos. Comunicación mediante Paso de Mensajes. .......................................................................... 169

6.1 Introducción ....................................................................................................... 169 6.2 Topología de la red ........................................................................................... 171 6.3 Redes formadas por conmutadores .............................................................. 173

6.3.1 El conmutador (switch) .................................................................................................. 174 6.3.2 Red crossbar ......................................................................................................... 175 6.3.3 Redes multietapa (multistage) .................................................................................... 176

6.3.3.1 La red Omega ........................................................................................ 176 6.3.3.2 Encaminamiento en la red Omega ................................................... 178 6.3.3.3 Conflictos de salida y bloqueos ......................................................... 179 6.3.3.4 Otro patrón de comunicación: broadcast. ..................................... 181 6.3.3.5 Otras redes ............................................................................................. 181 6.3.3.6 Resumen .................................................................................................. 183

6.4 Redes formadas por encaminadores de mensajes .................................... 184 6.4.1 Encaminadores de mensajes ............................................................................ 184 6.4.2 Topologías de red más utilizadas .................................................................... 185

6.4.2.1 Redes de una dimensión: la cadena y el anillo .................................. 186 6.4.2.2 Mallas y Toros (mesh, torus) ....................................................................... 187 6.4.2.3 Hipercubos (hypercube) ............................................................................... 188 6.4.2.4 Árboles y árboles densos (fat tree) ........................................................... 190 6.4.2.5 Resumen de topologías ....................................................................... 191 6.4.2.6 Los enlaces físicos ................................................................................. 193

6.5 La comunicación a través de la red en los sistemas paralelos ............... 193 6.5.1 Los mensajes ........................................................................................................ 194

Page 11: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

ÍNDICE ▪ ix ▪

6.5.2 Patrones de comunicación: con quién y cuándo hay que efectuar la comunicación. ................................................................................. 195

6.5.3 Construcción del camino (switching strategy) ................................................... 198 6.5.4 Encaminamiento de los mensajes (routing) ......................................................... 199

6.5.4.1 El registro de encaminamiento .......................................................... 200 6.5.4.2 Elección del camino: estático o adaptativo .................................... 203

6.5.5 Control del flujo de información ..................................................................... 206 6.5.5.1 Avance de los paquetes: Store-and-forward,

Wormhole y Cut-through .................................................................... 206 6.5.5.2 Conflictos en el uso de recursos: los búferes ................................. 210

6.5.6 Eficiencia de la comunicación: latencia y throughput ............................... 214 6.5.6.1 Tiempo de comunicación en la red .................................................. 215 6.5.6.2 Considerando el tráfico en la red ...................................................... 217 6.5.6.3 Cálculo del throughput máximo ........................................................ 219 6.5.6.4 Análisis global ......................................................................................... 221

6.5.7 Problemas de la comunicación ....................................................................... 222 6.5.7.1 Deadlock (interbloqueos) Canales virtuales. Giros

controlados (Turn model). Control de la inyección de paquetes. Utilización de caminos seguros ..................................... 222

6.5.7.2 Problemas de livelock y starvation.................................................... 229 6.5.8 Protocolos de comunicación ........................................................................... 230

6.6 Evolución de los computadores paralelos ................................................... 232

Apéndice. Cálculo de las distancias medias en diferentes topologías .......... 235

Capítulo 7. Coherencia de los Datos en los Computadores DSM .................................................................................... 241

7.1 Introducción ....................................................................................................... 241 7.2 Directorios de coherencia ............................................................................... 243

7.2.1 Introducción y clasificación ................................................................................ 243 7.2.1.1 Problemas................................................................................................ 245

7.2.2 Estructura de los directorios ............................................................................. 246 7.2.2.1 Directorios implementados en memoria principal ....................... 246 7.2.2.2 Directorios implementados en memoria cache ............................ 251

7.2.3 Optimización del tráfico de coherencia ........................................................ 254 7.2.4 Atomicidad de las operaciones: carreras ...................................................... 257

7.3 Implementación de los protocolos de coherencia: dos ejemplos .............................................................................................................. 259 7.3.1 Protocolo de coherencia de los multicomputadores SGI Origin ............ 259

7.3.1.1 Lecturas .................................................................................................... 260 7.3.1.2 Escrituras .................................................................................................. 263 7.3.1.3 Actualización de la memoria principal ............................................ 268

7.3.2 El protocolo de coherencia estándar SCI en la máquina NUMA-Q de Sequent. ........................................................................................................... 269

Page 12: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ x ▪ ÍNDICE

7.3.2.1 SCI: estados y operaciones ................................................................. 270 7.3.2.2 Lecturas .................................................................................................... 272 7.3.2.3 Escrituras .................................................................................................. 273 7.3.2.4 Actualización de la memoria principal ............................................ 277 7.3.2.5 Atomicidad y carreras .......................................................................... 277

7.4 Resumen .............................................................................................................. 279

Capítulo 8. Paralelización de Bucles y Planificación de Tareas ................................................................................. 281

8.1 Introducción ....................................................................................................... 281 8.1.1 Ideas básicas sobre paralelización de bucles ............................................... 287

8.2. Estructuras básicas para expresar el paralelismo de los bucles .............. 290 8.2.1 Bucles sin dependencias entre iteraciones: bucles doall .......................... 290 8.2.2 Bucles con dependencias entre iteraciones ................................................. 291

8.2.2.1 Bucles forall (sincronización global .................................................. 292 8.2.2.2 Bucles doacross (sincronización punto a punto) .......................... 293

8.2.3 Efecto de las antidependencias y de las dependencias de salida ........... 298 8.2.4 Atención con las instrucciones if ..................................................................... 299

8.3 Implementación de la sincronización........................................................... 300 8.3.1 Sincronización mediante contadores ............................................................. 301 8.3.2 Un único contador por procesador................................................................ 303

8.4 Optimizaciones para paralelizar bucles de manera eficiente ................. 304 8.4.1 Eliminación del efecto de las dependencias que no son esenciales ...... 304 8.4.2 Fisión de bucles ................................................................................................... 305 8.4.3 Ordenación de las dependencias ................................................................... 306 8.4.4 Alineación de las dependencias (peeling) .................................................... 307 8.4.5 Extracción de threads independientes (switching) ..................................... 309 8.4.6 Minimización de las operaciones de sincronización ................................. 310 8.4.7 Tratamiento de bucles (reordenación...) ........................................................ 311

8.4.7.1 Intercambio de bucles.......................................................................... 311 8.4.7.2 Cambio de sentido................................................................................ 314 8.4.7.3 Desplazamientos (skew) ....................................................................... 314 8.4.7.4 Colapso y coalescencia de bucles .................................................... 315

8.5 Planificación de bucles (scheduling) ............................................................... 316 8.5.1 Reparto de las iteraciones: consecutivo o entrelazado ............................. 317 8.5.2 Planificación estática o dinámica .................................................................... 318

8.5.2.1 Planificación estática ............................................................................ 319 8.5.2.2 Planificación dinámica: autoplanificación (self/chunk

scheduling), autoplanificación guiada (GSS) y trapezoidal (trapezoid self scheduling) ........................................................................... 319

8.6 Secciones paralelas: Fork / Join ..................................................................... 323 8.7 Análisis del rendimiento................................................................................... 325

Page 13: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

ÍNDICE ▪ xi ▪

Capítulo 9. Computadores Paralelos de Alto Rendimiento. Programación Paralela: OpenMP y MPI (introducción). ..................................................................... 327

9.1 Computadores paralelos de alto rendimiento ............................................ 328

9.2 Programación Paralela: OpenMP y MPI (introducción) .......................... 332 9.2.1 OpenMP ................................................................................................................ 334 9.2.2 MPI ......................................................................................................................... 337

Page 14: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 15: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

Introducción

¿Qué tiempo hará mañana en esta ciudad? ¿Cómo evolucionan las galaxias? ¿Cómo interaccionan los electrones en una molécula de clorofila? ¿Se comportarán de manera adecuada las alas de un avión en una turbulencia? Para dar respuesta adecuada a esas y otras muchas preguntas, científico/as e ingeniera/os utilizan potentes computadores, la herramienta principal de cualquier laboratorio en la actualidad. Las aplicaciones técnico-científicas requieren de grandes cantidades de cálculo, casi de manera ilimitada, y además hay que obtener resultados en el menor tiempo posible, (prever mañana las lluvias torrenciales de hoy no sirve para mucho). A pesar del espectacular incremento en la velocidad de cálculo de los procesadores, las necesidades van siempre muy por delante. A lo largo de la evolución de los computadores tres han sido las líneas principales que han permitido

Page 16: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 2 ▪ INTRODUCCIÓN

aumentar de manera continuada la velocidad de los mismos: los avances en la tecnología electrónica, el desarrollo de nuevas estructuras o arquitecturas de computadores, y el uso de tecnologías del software (compiladores, etc.) cada vez más eficientes.

Mediante la tecnología electrónica se ha conseguido integrar en un sólo chip una cantidad ingente de transistores: hoy en día por encima de 1000 millones (y cada vez más). A consecuencia de este avance, cada vez son más las "partes" del computador que se van integrando en un solo chip junto con el procesador: unidades funcionales específicas, registros, memoria cache, e incluso múltiples núcleos (core). Del mismo modo, la frecuencia del reloj del procesador es cada vez más alta (aunque la carrera para usar relojes cada vez más rápidos está detenida en este momento), actualmente en el intervalo 1-4 GHz, lo que quiere decir que el tiempo de ciclo está por debajo del nanosegundo (F = 1 GHz → T = 1 ns) y, como consecuencia, se pueden hacer más operaciones por unidad de tiempo.

Desde el punto de vista de la arquitectura del sistema, todos los procesadores actuales son superescalares o de tipo VLIW (la ejecución de las instrucciones es segmentada y se intenta ejecutar más de una instrucción cada ciclo); la jerarquía de memoria cache permite accesos más rápidos, los registros se organizan para optimizar el uso de los datos, etc.

Las técnicas de compilación también han avanzado mucho. El objetivo principal es eliminar el efecto de las dependencias existentes entre las instrucciones, y ocultar la latencia de la unidades funcionales (aprovechando ese tiempo para realizar trabajo útil).

Sin embargo, a pesar de que tenemos procesadores superescalares muy rápidos —que llegan a superar la velocidad de cálculo de 10 Gflop/s— para muchas aplicaciones, tales como previsiones meteorológicas, simulaciones de procesos físicos y químicos, diseños de aeronáutica, prospecciones geológicas, diseño de nuevos materiales, desarrollos diversos en ingeniería, avances en biología, genética y farmacia, etc., dicha velocidad no es suficiente. En el periodo 1986-2002, la tasa de crecimiento del rendimiento de los procesadores fue de un %52 anual (!), pero dicho crecimiento se ha reducido notablemente estos últimos años, situándose en torno al 20%: la velocidad que se puede conseguir con un procesador está llegando a sus límites físicos (y económicos). Por tanto, se necesita de desarrollar otro tipo de estrategias para conseguir las velocidades de cálculo —Teraflop/s, Petaflop/s, es decir, 1012, 1015 operaciones de coma flotante por segundo— que demandan las aplicaciones citadas.

Page 17: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

INTRODUCCIÓN ▪ 3 ▪

El paso que hay que dar es bastante claro: utilizar muchos procesadores, para repartir la ejecución de un programa entre ellos; es decir, utilizar sistemas paralelos. Además, las tecnologías de fabricación facilitan esta posibilidad: construido un procesador (chip), se hacen fácilmente miles de ellos y de manera relativamente barata. Por tanto, ¿por qué no utilizar 100, 1000, 10 000... procesadores para resolver un problema? Teóricamente, y si supiéramos cómo hacerlo, utilizando P procesadores podríamos ejecutar un programa P veces más rápido. Por desgracia, esto no va a ser así, ya que van a aparecer importantes problemas nuevos: ¿cómo se reparte el trabajo entre los procesadores? ¿son independientes los procesos o hay que sincronizarlos? ¿cómo se implementa la comunicación entre procesadores?...

Existen muchas maneras de estructurar un computador de P procesadores. Algunas características serán comunes en todos ellos, y otras, en cambio, no. Existen diferentes formas de clasificar estas arquitecturas o estructuras. De entre ellas, la más conocida o utilizada es, seguramente, la de Flynn (1966), quizás por lo simple que es. En esta clasificación se tienen en cuenta dos parámetros: el número de flujos de instrucciones (es decir, el número de PCs o contadores de programa) y el número de flujos de datos que operan simultáneamente. La siguiente figura recoge dicha clasificación:

flujos de datos uno muchos

flujos de instrucciones

uno SISD SIMD

muchos MIMD

• Computadores de tipo SISD (Single-Instruction-Single-Data) Se ejecuta un único programa sobre un único conjunto de datos; por

tanto, a esta clase pertenecen los sistemas clásicos de un sólo procesador (ordenadores personales, estaciones de trabajo…). Aunque en algunos casos dispongan de más de un procesador, éstos realizan el trabajo de manera independiente.

Como ya hemos comentado, las instrucciones se ejecutan de manera segmentada, dividida en varias fases —búsqueda, descodificación, lectura de operandos, memoria, unidad aritmética, escritura de resultados…—, y en cada fase habrá una instrucción (o varias, en el caso de los procesadores superescalares). Así pues, se utiliza

Page 18: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 4 ▪ INTRODUCCIÓN

paralelismo a nivel de instrucción (ILP, Instruction Level Parallelism). Además, el procesador (con ayuda del hardware o del compilador) es capaz de modificar el orden de ejecución de las instrucciones para conseguir la mayor eficiencia (velocidad) posible.

A lo largo del texto supondremos que todos esos conceptos son conocidos.

• Computadores de tipo SIMD (Single-Instruction-Multiple-Data) En este tipo de computadores se ejecuta simultáneamente el mismo

programa en todos los procesadores, pero sobre diferentes conjuntos de datos; se aprovecha, por tanto, el paralelismo de datos (DLP, Data Level Parallelism). Dentro de este grupo podemos distinguir dos subgrupos: los denominados processor-array (distributed memory SIMD) y los procesadores vectoriales (shared memory SIMD).

En el primer caso, el computador dispone de muchos procesadores normalmente muy "simples" (por ejemplo, 16 k procesadores de un bit); todos los procesadores ejecutan el mismo programa de manera sincronizada, pero sobre datos diferentes. Se han construido muchas máquinas de tipo SIMD, sobre todo en los años 80-95, y para ciertas aplicaciones, tales como cálculo numérico, procesamiento de señal, etc., ofrecen muy buen rendimiento.

Sin embargo, hoy en día no se fabrican computadores de este modelo (aunque ideas similares se utilizan para generar entornos virtuales de dos y tres dimensiones); sí, en cambio, computadores vectoriales.

• Computadores de tipo MIMD (Multiple-Instruction-Multiple-Data) Es el caso general de un sistema paralelo. Se ejecutan muchos

procesos (muchos PCs) sobre diferentes conjuntos de datos. ¡Ojo! no se trata de un conjunto de máquinas SISD, ya que los programas que se ejecutan no son independientes.

Este es el modelo que permite obtener elevadas velocidades de cómputo: computadores de paralelismo masivo, en los que P procesadores (un número alto) colaboran en la resolución de un problema; es decir, se explota el paralelismo a nivel de hilo o proceso (TLP, Thread Level Parallelism). En cualquier caso, surgen muchos problemas nuevos, a los que, si se quiere conseguir un buen rendimiento, habrá que buscar soluciones adecuadas.

Page 19: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

INTRODUCCIÓN ▪ 5 ▪

Tal y como veremos en los próximos capítulos, podemos hacer una subclasificación en el grupo de las máquinas MIMD:

• Sistemas de memoria compartida, en los que todos los procesadores utilizan el mismo espacio de direccionamiento. La memoria puede estar centralizada (SMP, symmetric multiprocessors) o distribuida (DSM, distributed shared memory). La comunicación entre procesos se realiza mediante el uso de variables compartidas.

• Sistemas de memoria privada distribuida, en los que cada uno de los procesadores utiliza su espacio propio de memoria. LA comunicación entre procesos se realiza mediante paso de mensajes.

A lo largo de los capítulos del texto vamos a analizar las máquinas paralelas de tipo MIMD, pero en el primero vamos a tratar sobre un tipo especial de computador SIMD de muy alto rendimiento: los computadores vectoriales. Se trata de una arquitectura de procesador específica, destinada al procesamiento de vectores, que ha conseguido un lugar destacado en la historia de la computación. En el capítulo 2 haremos una breve presentación de los sistemas paralelos: principales modelos y arquitecturas, problemas más importantes, la ley de Amdahl, etc. En el capítulo 3 analizaremos el problema de la coherencia de los datos en sistemas SMP; en el 4, las instrucciones y procedimientos básicos para sincronizar procesos paralelos: T&S, LL, SC...; y en el 5, los modelos de consistencia, secuencial y relajados, de la memoria de un sistema paralelo. En el capítulo 6, analizaremos la topología, estructura y funcionamiento de la red de comunicación de un sistema paralelo, así como la eficiencia de los mecanismos de comunicación entre procesadores. En el capítulo 7 analizaremos nuevamente el problema de la coherencia de los datos, pero en los sistemas DSM: los directorios de coherencia. Dedicaremos el capítulo 8 a presentar las técnicas de paralelización de bucles y el reparto de tareas a los procesadores. Finalmente, en el capítulo 9 haremos un breve resumen de la situación actual de los sistema paralelos, analizando la lista top500, así como una breve presentación de las herramientas básicas para programar aplicaciones en paralelo: OpenMP, para los sistemas de memoria compartida SMP, y MPI, para el caso de paso de mensajes (en sistemas DSM o MPP).

Page 20: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 21: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 1 ▪

Computadores Vectoriales

1.1 ¿QUÉ ES UN COMPUTADOR VECTORIAL?

Como hemos comentado en la introducción, las arquitecturas de tipo MIMD son las más adecuadas para resolver en paralelo aplicaciones de tipo general. Existen, sin embargo, algunos problemas importantes, desde el punto de vista del cálculo requerido, en los que es posible utilizar otro tipo de arquitecturas para lograr ejecuciones con un alto rendimiento.

Como ya se sabe, en los programas de cálculo científico la mayor parte del tiempo de ejecución se invierte en la ejecución de bucles. Por ejemplo:

do i = 0, N-1 C(i) = A(i) + B(i) enddo

Si N es muy grande (N = 109, por ejemplo) el tiempo de ejecución de ese bucle será muy alto, a pesar de su estructura tan simple. Si lo ejecutamos en un procesador escalar, el código ensamblador será, por ejemplo, el siguiente:

Page 22: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 8 ▪ Capítulo 1: COMPUTADORES VECTORIALES

buc: FLD F1,A(R1) FLD F2,B(R1) FADD F3,F2,F1 FST C(R1),F3

ADDI R1,R1,#8 SUBI R2,R2,#1 BNZ R2,buc

En un procesador escalar se ejecutaría, en el mejor de los casos, una instrucción por ciclo1, por lo que para ejecutar una iteración del bucle se necesitarían 7 ciclos; por tanto, el tiempo de ejecución de todo el programa sería de TE = 7N.

El bucle anterior tiene dos características específicas. Por un lado, las estructuras de datos que utiliza —los vectores A, B y C— son muy regulares; y, por otro lado, todas las iteraciones del bucle se pueden ejecutar de manera independiente, ya que no existen dependencias de datos entre ellas.

Para comenzar, definamos qué es, en este contexto, un vector. Un vector es una estructura que se puede definir mediante tres parámetros:

• dirección de comienzo: dirección de memoria del primer elemento del vector.

• longitud: número de elementos del vector. • paso (stride): distancia en memoria entre dos elementos consecutivos

del vector.

Por ejemplo, un vector que esté almacenado en las posiciones 1000, 1002, 1004, 1006, 1008, 1010, 1012 y 1014 de memoria (cada componente ocupa una posición de memoria) se definiría así:

dirección de comienzo = 1000 longitud = 8 paso = 2

Un procesador escalar, como su nombre indica, trabaja con escalares. Sin embargo, en las áreas de Ciencia e Ingeniería es muy común el uso de vectores y el tiempo de ejecución se invierte, principalmente, en la ejecución, una y otra vez, de bucles como el anterior. ¿Por qué no definir una arquitectura y un lenguaje máquina que directamente sean capaces de tratar con vectores? ¿Por qué no escribir el programa anterior de la siguiente manera?

1 Si el procesador fuera superescalar, quizás se podría conseguir algo más de una instrucción por ciclo.

Page 23: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.1 ¿QUÉ ES UN COMPUTADOR VECTORIAL? ▪ 9 ▪

LV V1,A(R1) ; leer el vector A LV V2,B(R1) ; leer el vector B ADDV V3,V1,V2 ; sumar ambos vectores SV C(R1),V3 ; escribir el resultado en el vector C

En este nuevo juego de instrucciones, la instrucción LV V1,A(R1) implicaría lo siguiente (utilizando, a modo de ejemplo, el esquema de segmentación que se muestra2):

LV V1,A(R1) BD L AM M M M E

M M M E

M M M E

... ... ...

M M M E

Podríamos representar la ejecución anterior, de manera simplificada, de la siguiente forma:

LV V1,A(R1) BD L AM M M M E E ... ... E

Así pues, mediante una única instrucción leemos de memoria un vector completo de N elementos. Para que esto sea posible la memoria debe de estar segmentada, con lo que, si no existe algún otro impedimento, en cada ciclo proporcionará un elemento del vector, que se irán escribiendo en un registro vectorial.

El siguiente esquema presenta la ejecución del programa anterior fase a fase (las latencias de las unidades funcionales son un simple ejemplo):

LV V1,A(R1) BD L AM M M M E ... (N ciclos) ...

LV V2,B(R1) BD L AM M M M E ... (N ciclos) ...

ADDV V3,V1,V2 BD . . . . L A A E ... (N ciclos) ...

SV C(R1),V3 BD L AM . . . . L M M M E ... ... E

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ... ... 14+N

ti N

(Por ahora, supongamos que los operandos que necesitan las instrucciones ADDV y SV se pueden obtener en los ciclos 8 y 11, tal como se indica en la tabla). 2 Las fases de ejecución habituales: BD, búsqueda y descodificación de la instrucción; L, lectura de los

operandos; AM, cálculo de la dirección de memoria; A, una operación en una unidad funcional; M, una operación en memoria; E, escritura del resultado en los registros. Cada instrucción utiliza solamente las fases que necesita para su ejecución.

Page 24: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 10 ▪ Capítulo 1: COMPUTADORES VECTORIALES

Si el modelo de ejecución es ese, podemos hacer un análisis sencillo para obtener el tiempo de ejecución del bucle (de manera simplificada; un poco más adelante formalizaremos este cálculo): existe un tiempo de inicio —ti— antes de que la última instrucción comience a escribir, y después, para terminar la ejecución, se necesitan N ciclos, uno por cada elemento del vector. Por tanto:

TV = ti + N

Si comparamos esta expresión con la que hemos obtenido para un procesador escalar, la mejora es clara. Por ejemplo, si el número de elementos de los vectores es N = 128, y si ti = 14 ciclos, tendríamos los siguientes tiempos de ejecución:

TE = 7 N = 896 ciclos TV = ti + N = 142 ciclos (un 16%)

No es ésta la única ventaja. Por un lado, han desaparecido las dependencias de control3 debida al bucle, ya que, por definición, ha desaparecido el propio bucle. Por otro lado, sólo se han ejecutado 4 instrucciones, y no las 7N que componían el bucle escalar. Esto implica que el uso de la cache de instrucciones es mucho más bajo, y, por consiguiente, el tráfico en el bus también.

Pero, por supuesto, todas esas ventajas no salen “gratis”. A decir verdad, tenemos que analizar con más detalle el esquema de ejecución anterior, para conocer los recursos que se necesitan para poder ejecutar de esa manera las instrucciones vectoriales.

1.1.1 Algunos problemas

1.1.1.1 La memoria de un computador vectorial

Un procesador vectorial utiliza la memoria de modo intensivo. Por ejemplo, en el caso anterior tenemos 3 instrucciones, 2 LV y 1 SV, que están utilizando simultáneamente la memoria y, además, cada instrucción realiza N accesos a memoria. Por tanto, hay que solucionar dos aspectos:

3 Las responsables de las dependencias de control son las instrucciones de salto. En general, después

de la instrucción de dirección i se ejecuta la instrucción de dirección i+1, salvo en el caso de los saltos. Cuando ejecutamos un salto no sabemos qué instrucción será la siguiente hasta que el salto termine, por lo que hay que parar al procesador (aunque existen muchas técnicas para evitar esos ciclos "muertos").

Page 25: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.1 ¿QUÉ ES UN COMPUTADOR VECTORIAL? ▪ 11 ▪

1. ¿Cuántos buses hay para acceder a memoria? El procesador y el sistema de memoria se comunican mediante el bus de datos. Una operación vectorial de memoria va a ocupar el bus de datos durante N ciclos (supongamos que se transfiere una palabra por ciclo). Por tanto, si sólo hubiera un bus, sólo una instrucción podría acceder a memoria en cada momento, y todas las demás deberían esperar a que ésta terminara. Por consiguiente, el tiempo de ejecución no sería de orden N, sino de kN (con k = 2, 3, 4..., número de instrucciones de memoria).

2. ¿No habrá conflictos en el uso de los módulos de memoria? A pesar de que el espacio de memoria esté entrelazado entre los diferentes módulos de memoria, puede suceder que en un momento determinado se necesite acceder a elementos de vectores almacenados en el mismo módulo. Si sucede esto, para poder comenzar un acceso habrá que esperar a que termine el anterior acceso al mismo módulo, con lo que aumentará el tiempo de ejecución.

Queda claro que el sistema de memoria de un computador vectorial juega un papel muy importante en el rendimiento final del sistema: hacen falta múltiples buses, y la memoria debe estar entrelazada en muchos módulos, para reducir los conflictos de acceso a los mismos.

1.1.1.2 Unidades funcionales vectoriales

Analizando el esquema de ejecución anterior, queda claro que las unidades funcionales deben estar segmentadas. Una única instrucción (ADDV, por ejemplo) realiza N operaciones en la unidad funcional, una por ciclo. Si no estuviera segmentada, no sería posible generar un dato por ciclo. De la misma manera, parece necesario poder disponer de varias unidades funcionales de cada tipo, ya que una instrucción ocupa cada unidad funcional durante N ciclos.

1.1.1.3 Registros vectoriales

¿Qué es un registro vectorial? ¿De qué tamaño son? ¿Cómo se leen y se escriben? En un registro vectorial se guardan los elementos de un vector. Cada elemento, normalmente, será un escalar representado en coma flotante, por ejemplo en 64 bits. Por tanto, en un registro tendremos 64 × N bits. El tamaño de los registros es, en todo caso, limitado. Es habitual que un registro vectorial permita almacenar 64 o 128 (Lmax) elementos de un vector, con lo que su capacidad sería de 64 (o 128) × 64 = 4 (u 8) kilobits. Si nos fijamos

Page 26: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 12 ▪ Capítulo 1: COMPUTADORES VECTORIALES

en el tamaño, se comprende fácilmente que no se disponga de un número muy elevado de registros vectoriales. Normalmente dispondremos de 8-16 registros (16 × 8 = 128 kilobits). En algunas máquinas, el tamaño de los registros es variable; es decir, el "espacio de memoria" de que se dispone se puede utilizar para definir muchos registros de pocos elementos o unos pocos de muchos elementos.

¿Qué se debe hacer cuando la longitud de los vectores que tenemos que

procesar es mayor que Lmax (64 o 128 elementos)? No hay más remedio que formar un bucle, y en cada iteración del mismo procesar Lmax elementos (strip mining). Por tanto, aparecen de nuevo las dependencias de control, aunque esta vez cada 64 (128) elementos.

En los primeros computadores vectoriales los registros se trataban como una “unidad”, por lo que no era posible leer y escribir sobre el mismo registro a la vez. Hoy en día, los elementos que conforman un registro vectorial se tratan como unidades independientes que pueden direccionarse de manera separada, con lo que es posible acceder a los primeros elementos de un vector ya almacenados en un registro mientras se sigue escribiendo el resto de elementos. Por otro lado, dado que diferentes instrucciones irán produciendo datos para escribir en el banco de registros vectoriales, y que cada una de ellas necesitará muchos ciclos para escribir el vector resultado, serán necesarios varios (muchos) buses de escritura (evidentemente, también se necesitan “muchos” buses de lectura). Con todo ello, el banco de registros de un procesador vectorial resulta ser un dispositivo complejo.

1.1.1.4 Programas vectoriales

¿Qué tipo de programas se pueden ejecutar en un computador vectorial? Los procesadores vectoriales están optimizados para procesar vectores, pero en los programas reales, además de procesar vectores, habrá que procesar código escalar. ¿Cómo se hace eso? ¿Qué influencia tiene en la velocidad de cálculo? (como veremos, el efecto del código escalar puede ser muy grande).

registros vectoriales U.F.

Page 27: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.1 ¿QUÉ ES UN COMPUTADOR VECTORIAL? ▪ 13 ▪

Analicemos de nuevo qué se hace cuando se procesan vectores. Veamos el siguiente ejemplo:

do i = 0, N-1 A(i) = A(i) + 1 enddo

Si se ejecutara escalarmente, y simplificando, el orden de ejecución de las diferentes operaciones sería el siguiente (L = load; S = store; + = suma; i = elemento del vector):

L0 +0 S0 / L1 +1 S1 / L2 +2 S2 / L3 +3 S3 / ... / LN–1 +N–1 SN–1

Si lo ejecutáramos vectorialmente (LV - ADDV - SV), el orden pasaría a ser el siguiente:

L0 L1 L2 ... LN–1 / +0 +1 +2 ... +N–1 / S0 S1 S2 ... SN–1

Esto es, la ejecución vectorial implica desordenar el código original. Y como ya sabemos, esto no siempre es posible, ya que hay que respetar las dependencias de datos entre las instrucciones. Por tanto, para decidir si un programa se puede ejecutar vectorialmente o no, hay que hacer un meticuloso análisis de las dependencias, tarea que, como veremos, va a recaer, en gran medida, en un buen compilador vectorial.

Resumiendo todo lo anterior: aunque hemos definido un modelo de computador con un rendimiento teórico muy elevado, en la realidad tenemos que superar muchos problemas para poder llegar a esa velocidad de cálculo.

1.1.2 Arquitectura y lenguaje máquina

Existen diferentes arquitecturas para los computadores vectoriales, casi tantas como fabricantes. En los primeros diseños, los computadores vectoriales no tenían registros, y todas las operaciones se hacían con los operandos en memoria. A este modelo se le denomina "Memoria-Memoria" (M/M). Pero pronto se añadieron los registros vectoriales; como consecuencia, los operandos de las operaciones vectoriales se obtienen de registros y los resultados se dejan en registros (modelo R/R).

En la siguiente figura se muestra un esquema lógico, muy simple, de un computador vectorial. Podemos distinguir dos secciones: la sección escalar y la vectorial. El procesador escalar se encarga de la búsqueda y descodificación de las instrucciones. Si la instrucción es escalar, la ejecuta él

Page 28: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 14 ▪ Capítulo 1: COMPUTADORES VECTORIALES

mismo, utilizando los registros escalares necesarios; pero si es vectorial, pasa el control al procesador vectorial para que la ejecute. Salvo que especifiquemos alguna otra opción, vamos a suponer que la unidad de control es de tipo Tomasulo (desorden/desorden).

Tal y como hemos comentado, aunque vamos a trabajar con vectores, en

la realidad tendremos una mezcla de código vectorial y escalar. Por tanto, tendremos que utilizar tanto instrucciones vectoriales como escalares. Las instrucciones escalares son las habituales en cualquier procesador RISC. En función del computador, existen diferentes juegos de instrucciones vectoriales y de formatos de instrucciones; las más habituales son las siguientes (más tarde veremos algunas otras):

OPV Vi,Vj,Vk Vi = Vj OP Vk (OP = ADD, SUB, MUL, DIV...) Operación entre dos vectores. El resultado es otro

vector.

OPVS Vi,Vj,Fk Vi = Vj OP Fk OPVI Vi,Vj,#inm Vi = Vj OP #inm (OP = ADD, SUB, MUL, DIV...) Operación entre un vector y un escalar. El

resultado es un vector.

Registros

Unidades

funcionales

Procesador escalar

(completo)

Control del procesador

vectorial

Unidad de direcciones

(datos)

Mem

ori

a

(op.)

Page 29: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.2 DEPENDENCIAS DE DATOS ▪ 15 ▪

LV Vi,A(Rj) Se lee a partir de la dirección de memoria A+Rj un vector, y se deja en el registro Vi (puede haber más modos de direccionamiento).

SV A(Rj),Vi Similar a la anterior, pero, en lugar de leer, escribe un vector en memoria.

Para identificar un vector en memoria, hay que dar tres parámetros:

dirección de comienzo, longitud y paso. La dirección de comienzo se indica en la propia instrucción LV/SV (de acuerdo al modo de direccionamiento que se utilice). La longitud del vector y el paso, en cambio, hay que indicarlos previamente a la operación de lectura o escritura. Para ello utilizaremos dos registros especiales: VL (vector length), para indicar el número de elementos del vector, su longitud, y VS (vector stride), para indicar el paso. Si el contenido de VL es mayor que Lmax (tamaño de los registros vectoriales), sólo se procesarán Lmax elementos.

Así pues, tenemos que ejecutar las siguientes instrucciones para, por ejemplo, leer un vector:

MOVI VL,#64 ; los vectores son de 64 elementos MOVI VS,#8 ; el paso es 8

LV V1,A(R1)

De esta manera se cargarán en el registro V1 64 elementos de un vector, correspondientes a las direcciones A+R1, A+R1+8, A+R1+16…

En algunos computadores es necesario indicar explícitamente el paso de los vectores en la propia instrucción, utilizando para ello un registro de propósito general.

1.2 DEPENDENCIAS DE DATOS

Al igual que sucede con los procesadores (super)escalares, la velocidad de cálculo de los procesadores vectoriales está limitada por las dependencias de datos. Una instrucción depende de otra anterior si uno de sus operandos es el resultado de dicha instrucción, por lo que deberá esperar a que finalice antes de poder ejecutarse. Ya sabemos que, en los procesadores escalares, para

Page 30: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 16 ▪ Capítulo 1: COMPUTADORES VECTORIALES

atenuar la pérdida de rendimiento debida a las dependencias de datos se utilizan cortocircuitos (forwarding) entre las unidades funcionales; una idea similar se aplica también en los procesadores vectoriales.

Para los siguientes ejemplos utilizaremos el siguiente esquema de segmentación (Tomasulo):

LV/SV → BD L AM M M M E

ADDV → BD L A A E

1.2.1 Encadenamiento (chaining)

Se dice que dos instrucciones se encadenan si la segunda utiliza el vector generado por la primera sin esperar a que ésta lo haya guardado en el registro vectorial. Veamos un ejemplo sencillo:

do i = 0, N-1 LV V1,A(R1) A(i) = A(i) + 1 → ADDVI V2,V1,#1 enddo SV A(R1),V2

El bucle presenta dependencias de datos muy claras: LV → ADDVI (V1) y ADDVI → SV (V2). Entonces ¿cómo se ejecutará ese programa? Tenemos dos alternativas: sin realizar encadenamiento entre las dos instrucciones, o encadenándolas.

a. Si no se realiza encadenamiento, la segunda instrucción deberá esperar a que termine la primera, para poder leer el registro vectorial correspondiente (V1). En la figura se muestra un esquema de ejecución, en el que se puede ver cuándo se realizan las lecturas (L).

LV V1,A(R1) BD L AM M M M E ... E

ADDVI V2,V1,#1 BD . . . . . ... . L A A E ... E

SV A(R1),V2 BD L AM . . ... . . . . . ... . L M M M E ...

ciclos ← 6 → ← N → ← 3 → ← N → ← 4 → ← N

Por tanto, el tiempo de ejecución en este caso es TV = 13 + 3N ciclos.

b. En cambio, si se realiza encadenamiento, según se van generando los vectores se van utilizando en la siguiente unidad funcional; es decir, se utiliza el cortocircuito E → L.

Page 31: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.2 DEPENDENCIAS DE DATOS ▪ 17 ▪

LV V1,A(R1) BD L AM M M M E E ... (N cicl.) ...

ADDVI V2,V1,#1 BD . . . . L A A E E ... (N cicl.) ...

SV A(R1),V2 BD L AM . . . . L M M M E ... ... E

ciclos ← 6 → ← 3 → ← 4 → ← N →

En este segundo caso, el tiempo de ejecución es TV = 13 + N ciclos. Podemos analizar el mismo comportamiento de manera cualitativa. Por

ejemplo, la siguiente figura muestra un esquema de ejecución muy simplificado del programa anterior (LV / ADDVI / SV), en función de si se encadenan o no las instrucciones:

sin encadenamiento: T ~ 3N con encadenamiento: T ~ N

La diferencia entre ambas opciones es clara. En el primer caso, el tiempo de ejecución es del orden de 3N; en el segundo, en cambio, es de orden N. Por ejemplo, para N = 64 el tiempo de ejecución bajaría de 13 + 3×64 = 205 ciclos a 13 + 64 = 77 ciclos (un 38%). Así pues, necesitamos poder encadenar las instrucciones para conseguir un buen rendimiento.

1.2.1.1 Encadenamiento con dos instrucciones

En el ejemplo del apartado anterior, el encadenamiento se ha realizado con una única instrucción anterior: la instrucción ADDVI con la instrucción LV, o la instrucción SV con la instrucción ADDVI. En un caso más general, tendríamos que poder encadenar una instrucción con dos instrucciones anteriores. Veamos un ejemplo (C = A + B):

LV V1,A(R1) BD L AM M M M E E ... (N ciclos) ...

LV V2,B(R1) BD L AM M M M E E ... (N ciclos) ...

ADDV V3,V1,V2 BD . . . . L A A E E ... (N ciclos) ...

SV C(R1),V3 BD L AM . . . L M M M E ... ... E

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 14+N

LV

ADDVI

SV

ADDVI

SV

LV

Page 32: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 18 ▪ Capítulo 1: COMPUTADORES VECTORIALES

La tercera instrucción (ADDV) necesita los vectores V1 y V2, que son generados por las dos primeras instrucciones respectivamente. Pero estos dos vectores no se generan sincronizados: el primero se comienza a generar en el ciclo 7 y el segundo en el 8 (y a partir de ahí el resto de elementos). Por tanto, en el ciclo 7 no está preparado el primer elemento del segundo operando (V2), y en el ciclo 8 se pierde la posibilidad de tomar el primer elemento del primer operando (V1) (los datos no se “pierden”, claro ya que se están cargando en el registro vectorial). ¿Qué se puede hacer?

Para poder efectuar el encadenamiento hay que coger un operando según sale de la unidad funcional y leer el otro del registro correspondiente (V1), donde ya se está escribiendo. Para ello es necesario que el banco de registros permita la lectura y escritura simultánea del mismo registro (lo habitual en las máquinas vectoriales actuales, y que se conoce como flexible chaining o encadenamiento flexible); si eso no es posible, se perderá la posibilidad de encadenar (salvo que se aplique alguna otra solución) y habrá que esperar a que finalice la escritura de ambos operandos.

1.2.2 Tablas de ejecución de las instrucciones

Representar los esquemas de ejecución de un conjunto de instrucciones vectoriales fase a fase es un poco pesado. Por ello, en lugar de hacer ese tipo de esquemas, vamos a resumir en una tabla las acciones principales que suceden cuando se ejecutan las instrucciones:

• Inicio de ejecución: cuántos ciclos han pasado, desde el comienzo, hasta el momento previo a iniciar la operación en la UF. El inicio puede ser tras la lectura de los registros, o mediante encadenamiento, en cuyo caso indicaremos el número de ciclos entre [ ].

(La ejecución de instrucciones es segmentada, y las instrucciones se ejecutan de una en una, no es superescalar.)

• Latencia de la unidad funcional. • Ciclo en el que se genera el primer elemento. • Ciclo en el que se genera el último (N) elemento.

Por ejemplo, para una instrucción LV la tabla correspondiente sería:

BD L AM M M M E ... ... E comienzo (3) lat. UF (3) dato 1 (6+1) dato N (6+N)

Page 33: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.2 DEPENDENCIAS DE DATOS ▪ 19 ▪

Las ejecuciones de los dos ejemplos anteriores se pueden resumir así:

sin encadenamiento con encadenamiento A = A + 1 inic. lat. UF dato 1 dato N inic. lat. UF dato 1 dato N

LV V1,A(R1) 3 3 6+1 6+N 3 3 6+1 6+N

ADDVI V2,V1,#1 6+N+1 2 9+N+1 9+2N [7] 2 9+1 9+N

SV A(R1),V2 9+2N+1 3 13+2N+1 13+3N [10] 3 13+1 13+N

Si la ejecución de las instrucciones no se encadena, la instrucción ADDVI

tiene que esperar a que termine la escritura en el registro V1 (ciclo 6+N) y luego leer del registro (+1). Lo mismo le sucede a la instrucción SV: tiene que esperar a que la instrucción ADDVI termine (9+2N), y entonces leer V2 y escribir en memoria.

Si la ejecución de las instrucciones se encadena, la suma puede comenzar en el ciclo 7 (en ese ciclo llega de memoria el primer elemento del vector), y la escritura en memoria puede comenzar en el ciclo 10 (ciclo en que la suma genera el primer dato).

En el segundo ejemplo podemos observar el mismo comportamiento. Si no se puede encadenar, la instrucción ADDV tiene que esperar a tener listos ambos operandos (ciclo 7+N) y entonces leerlos. Cuando se encadena, uno de los operandos (V2) se obtiene directamente de la memoria y el otro (V1) del registro (donde se ha escrito en el ciclo anterior); el ciclo de encadenamiento es, por tanto, el ciclo 8.

sin encadenamiento con encadenamiento

C = A + B inic. lat. UF dato 1 dato N inic. lat. UF dato 1 dato N

LV V1,A(R1) 3 3 6+1 6+N 3 3 6+1 6+N

LV V2,B(R1) 4 3 7+1 7+N 4 3 7+1 7+N

ADDV V3,V1,V2 7+N+1 2 10+N+1 10+2N [8] 2 10+1 10+N

SV C(R1),V3 10+2N+1 3 14+2N+1 14+3N [11] 3 14+1 14+N

Nota: estamos aplicando un modelo “didáctico” de ejecución vectorial, y el objetivo es mostrar el comportamiento general, no los detalles particulares. Lo computadores comerciales utilizan estrategias similares, aunque los detalles de implementación pueden variar.

Page 34: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 20 ▪ Capítulo 1: COMPUTADORES VECTORIALES

1.3 DEPENDENCIAS ESTRUCTURALES

Después de analizar las dependencias de datos, analicemos las dependencias estructurales. Recuerda que un conflicto o dependencia estructural surge cuando se quiere utilizar un recurso mientras está ocupado por otra instrucción. Además de las unidades funcionales, el recurso más importante en un computador vectorial es la memoria. Para poder utilizar la memoria, primeramente hay que disponer de un bus libre. ¿Cuántos buses tenemos para acceder a la memoria? Por otro lado, se utilizan los propios módulos de memoria. ¿Están libres los módulos que hay que utilizar? Si están ocupados, ¿cuánto tiempo hay que esperar?

1.3.1 Buses de memoria (unidades funcionales LV/SV)

La ejecución de las instrucciones LV y SV implica una transferencia con memoria en la que se utilizan los buses. Cuando se ejecuta una instrucción LV o SV, el bus se ocupa durante N ciclos; mientras una instrucción está utilizando el bus, la siguiente deberá esperar hasta que se libere el bus. Por tanto, si el computador no tuviera un número suficiente de buses, la velocidad de cálculo de la máquina no sería muy alta.

Analicemos la influencia del número de buses mediante el ejemplo anterior (A = A + 1; LV / ADDVI / SV). Supongamos que la máquina puede encadenar las instrucciones, pero que sólo posee un bus para trabajar con memoria (LV o SV)4. En estas condiciones, cuando la instrucción SV quiere empezar a escribir en memoria, en el ciclo de encadenamiento, el bus no está disponible, ya que lo ocupa la instrucción LV (y lo mantendrá ocupado muchos ciclos). Por tanto, deberá esperar hasta que termine la primera instrucción (y se libere el bus) y leer entonces el registro en el que se están escribiendo los resultados (V2)5.

Este sería el esquema de ejecución:

4 Los buses de memoria pueden usarse tanto para una lectura como para una escritura; en algunas

máquinas, en cambio, los buses están "dedicados": unos son sólo para leer y otros sólo para escribir. 5 Si no puede leerse un registro mientras se está escribiendo, entonces habrá que esperar a que finalice

la escritura.

Page 35: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.3 DEPENDENCIAS ESTRUCTURALES ▪ 21 ▪

LV V1,A(R1) BD L AM M M M E E ... (N ciclos) ... E

ADDVI V2,V1,#1 BD . . . . L A A E E ... (N ciclos) ... E

SV A(R1),V2 BD L AM . . . . ? . ... ... L M M M E ... (N cicl.)

bus ocupado... libre

o, esquemáticamente:

La tabla correspondiente a la ejecución sería la siguiente:

un bus / encadenamiento A = A + 1 inic. lat. UF dato 1 dato N

LV V1,A(R1) 3 3 6+1 6+N

ADDVI V2,V1,#1 [7] 2 9+1 9+N

SV A(R1),V2 [6+N] 3 9+N+1 9+2N

Repitamos el análisis, pero con el segundo ejemplo que hemos visto antes.

En ambos casos, las instrucciones se encadenan, pero en el primer caso la máquina cuenta con un solo bus de memoria, y en el otro caso cuenta con dos buses.

un bus / encadenamiento dos buses / encadenamiento C = A + B inic. lat. UF dato 1 dato N inic. lat. UF dato 1 dato N

LV V1,A(R1) 3 3 6+1 6+N 3 3 6+1 6+N

LV V2,B(R1) 6+N 3 9+N+1 9+2N 4 3 7+1 7+N

ADDV V3,V1,V2 [10+N] 2 12+N+1 12+2N [8] 2 10+1 10+N

SV C(R1),V3 [9+2N] 3 12+2N+1 12+3N [6+N] 3 9+N+1 9+2N

Cuando sólo hay un bus, la segunda instrucción LV no puede utilizar la

memoria hasta que el primer LV la deje de utilizar, y lo mismo le sucede a la instrucción SV (para cuando se libera el bus, la escritura en el registro V3 está terminando). Por tanto, el tiempo de ejecución es de orden 3N. Si la

ADDVI

SV

LV

T ~ 2N

Page 36: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 22 ▪ Capítulo 1: COMPUTADORES VECTORIALES

máquina tiene dos buses, las instrucciones LV se ejecutarán a la vez, pero la instrucción SV tendrá que esperar.

Esquemáticamente:

un bus / encadenamiento: 3N dos buses / encadenamiento: 2N

La conclusión es sencilla: si no existen suficientes recursos (buses) para poder ejecutar las instrucciones de memoria, a pesar de tener la posibilidad de encadenar las instrucciones el tiempo de ejecución será elevado.

En resumen, los resultados que hemos obtenido con ambos ejemplos son los siguientes:

1. A = A + 1 (N = 64) sin encadenamiento 13 + 3N = 205 ciclos → 3,20 ciclos/dato encadenamiento / 1 bus 9 + 2N = 137 ciclos → 2,14 c/d encadenamiento / 2+ buses 13 + N = 77 ciclos → 1,20 c/d

2. C = A + B (N = 64) sin encadenamiento / 1 bus 16 + 4N = 272 ciclos → 4,25 c/d sin encadenamiento / 3 buses 14 + 3N = 206 ciclos → 3,22 c/d encadenamiento / 1 bus 12 + 3N = 204 ciclos → 3,19 c/d encadenamiento / 2 buses 9 + 2N = 137 ciclos → 2,14 c/d encadenamiento / 3 buses 14 + N = 78 ciclos → 1,22 c/d

Los datos muestran claramente la importancia de disponer de suficientes buses a memoria y de que las instrucciones puedan encadenarse para que las instrucciones se ejecuten eficientemente.

1.3.2 Conflictos en el uso de los módulos de memoria

1.3.2.1 Una sola operación de memoria

Tras haber analizado el problema de los buses en un procesador vectorial, analicemos ahora el uso de la propia memoria. La memoria de cualquier computador está entrelazada en varios módulos; así, las direcciones i e i+1

LV LV

SV ADDV

LV

LV

SV ADDV

Page 37: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.3 DEPENDENCIAS ESTRUCTURALES ▪ 23 ▪

no corresponden al mismo módulo de memoria, sino a módulos consecutivos. De esta manera es posible, por ejemplo, efectuar una operación simultánea en dos (en general nm, el número de módulos) direcciones consecutivas; si estuvieran en el mismo módulo tendríamos que esperar a que finalizara una operación antes de empezar con la siguiente.

Cuando se ejecuta una instrucción LV o SV se efectúan N lecturas o escrituras en memoria, una por ciclo. Para que se haga de manera eficiente, es necesario que se acceda a módulos que estén libres; si no, tendríamos un conflicto estructural, y no lograríamos efectuar una operación por ciclo.

Veamos el problema con un ejemplo. Hay que leer el vector A(A0:A15); la memoria está entrelazada en 4 módulos, y el vector se encuentra en módulos consecutivos (s = 1) a partir de m0. La latencia de la memoria es de 3 ciclos. La situación de la memoria según se lee el vector A es la siguiente:

m0 m1 m2 m3 A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 ...

→ tiempo (ciclos)

m0 M M M M M M ... m1 M M M M M M ... m2 M M M M M M

m3 M M M M M M

La lectura comienza en m0, y sigue en m1, m2, m3, y se vuelve a m0, para seguir leyendo más elementos del vector. En ese momento, m0 está libre, puesto que ya ha terminado el primer acceso, y por tanto no tendremos ningún problema.

Pero si, por ejemplo, la latencia de la memoria fuera de 8 ciclos, al ir a utilizar nuevamente m0 lo encontraríamos ocupado, ejecutando todavía la operación anterior. Tendríamos, por tanto, que esperar a que finalizara antes de poder seguir leyendo el vector. Como consecuencia del conflicto estructural, el tiempo de ejecución de la operación sería más alto.

El problema puede ser grave, en función de la definición del vector. Por ejemplo, si el paso del vector del ejemplo anterior fuera s = 4, entonces todos los elementos del vector estarían en el mismo módulo, m0: todos los accesos significarían un conflicto, ya que cada acceso dura 3 ciclos.

Page 38: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 24 ▪ Capítulo 1: COMPUTADORES VECTORIALES

Para analizar si surgirán o no conflictos en memoria, hay que considerar tres parámetros: el tiempo de acceso o latencia de la memoria —tm—, el número de módulos en que está entrelazada la memoria —nm—, y el paso de los vectores —s—. Dos de esos parámetros, latencia y número de módulos, son decisiones de diseño: se deciden al construir la máquina y no son modificables por el usuario. El tercero, en cambio, el paso de los vectores, corresponde al programa concreto que se ejecuta, y puede modificarse para intentar evitar conflictos.

Cuando s = 1, se utilizan todos los módulos de memoria al acceder a un vector (m0-m1-m2-...). Por tanto, para que no haya conflictos se debe cumplir que:

mtnm ≥

De esa manera, cuando hay que reutilizar un determinado módulo ya han pasado al menos nm ciclos, y por tanto estará libre.

Para el caso general, s > 1, hay que calcular cuántos módulos se utilizan en una determinada operación. Por ejemplo, en el caso anterior, cuando s = 4, sólo se utiliza un módulo de memoria, siempre el mismo (m0). Puede demostrarse fácilmente que el número de módulos que se utilizan en una operación de memoria es:

),( snmMCDnm

(MCD = máximo común divisor) Así pues, y generalizado el resultado anterior, no habrá conflictos en

memoria si el número de módulos que se van a utilizar es mayor o igual que la latencia:

mtsnmMCDnm

≥),(

Analizando la expresión anterior. se observa que la mejor situación se corresponde con el caso MCD(nm,s) = 1, es decir cuando, nm y s son primos entre sí, ya que se utilizan todos los módulos de memoria. En los casos más habituales, nm es una potencia de 2 (8, 16, 32, 64...). En esos casos, y si no hay conflicto cuando s = 1, no habrá conflictos para cualquier vector de paso impar (1, 3, 5...), pero podría haberlos para los casos de s par.

Existe una situación óptima. Si el número de módulos de memoria, nm, es un número primo, entonces cualquier paso s será primo con él (salvo sus múltiplos). Por ejemplo, si nm = 5, no hay problemas con los vectores de

Page 39: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.3 DEPENDENCIAS ESTRUCTURALES ▪ 25 ▪

paso s = 1, 2, 3, 4, 6, 7, 8... Por desgracia, cuando la memoria se entrelaza en un número primo de módulos, 17 por ejemplo, calcular el módulo y la dirección dentro del módulo que corresponden a una palabra dada es una operación “compleja” (operación que debe realizar el controlador de memoria, para efectuar cualquier acceso a memoria), ya que habrá que efectuar una división para obtener el cociente y el resto (cuando nm = 2i, los i bits de menos peso indican el módulo, y el resto la dirección dentro del módulo). Debido a esa división, no se suele entrelazar la memoria en un número primo de módulos.

El valor de s es muy variable en los programas vectoriales. Por ejemplo, al procesar matrices pueden definirse diferentes tipos de vectores: filas, columnas, diagonales... Para multiplicar dos matrices, por ejemplo, hay que usar filas en una y columnas en la otra. En algunos casos, para optimizar el acceso a memoria, las matrices no se guardan en posiciones consecutivas de memoria, sino que se dejan huecos sin ocupar (padding).

Veamos un ejemplo de la utilidad de esta estrategia. Sea una memoria entrelazada en 4 módulos y una matriz de tamaño 4×4, de la que se van a utilizar las filas y las columnas. Como puede verse, no hay problemas en el acceso a una fila (s = 1), ya que los elementos están en módulos de memoria diferentes, pero el acceso a cualquier columna es muy conflictivo, ya que todos los elementos están en el mismo módulo de memoria. Sin embargo, si se dejan huecos en memoria entre los elementos de la matriz (en la tabla se muestra un ejemplo), entonces es posible acceder tanto a filas (s = 1) como a columnas (ahora s = 5) sin conflictos (aunque se generen conflictos en el acceso a las diagonales6).

m0 m1 m2 m3 m0 m1 m2 m3 A00 A01 A02 A03 A00 A01 A02 A03 A10 A11 A12 A13 → - A10 A11 A12 A20 A21 A22 A23 A13 - A20 A21 A30 A31 A32 A33 A22 A23 - A30 A31 A32 A33 -

sf = 1 sin conflictos sf = 1 sin conflictos sc = 4 conflictos (todos en m0) sc = 5 sin conflictos sD = 5 sin conflictos sD = 6 conflictos sd = 3 sin conflictos sd = 4 conflictos

6 Como hemos comentado, el ideal sería que nm fuera un número primo. Por ejemplo, si nm fuera 5,

los cuatro vectores del ejemplo (f, c, D y d) podrían accederse sin problemas si se dejan los correspondientes huecos (lo dejamos como ejercicio para el lector).

Page 40: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 26 ▪ Capítulo 1: COMPUTADORES VECTORIALES

1.3.2.2 Varias operaciones de memoria

Como acabamos de ver, la ejecución de una instrucción vectorial de memoria, LV o SV, puede producir problemas en el acceso a los módulos de memoria. Lo mismo ocurre cuando se están ejecutando más de una instrucción de memoria. Aunque cada una de ellas no tuviera conflictos consigo misma, es posible que existan colisiones entre ellas; es decir, que una segunda instrucción quisiera utilizar un módulo de memoria ocupado en ese instante por otra instrucción.

Analicemos el problema mediante un ejemplo. Supongamos que la memoria está entrelazada en 8 módulos y que la latencia es 3 ciclos (con el mismo esquema de segmentación de los ejemplos anteriores). Hay suficientes buses a memoria y las instrucciones pueden encadenarse. El primer elemento de A esta en el módulo m0 y el paso es s = 1.

2 buses / encadenamiento A = A + 1 inic. lat. UF dato 1 dato N

LV V1,A(R1) 3 3 6+1 6+N

ADDVI V2,V1,#1 [7] 2 9+1 9+N

SV A(R1),V2 [10]

Como hemos visto antes, la instrucción SV puede encadenarse en el ciclo 10, e ir a memoria. Pero, ¿cómo se encuentran en ese momento los módulos de memoria, libres u ocupados? El esquema siguiente muestra el uso de los módulos de memoria ciclo a ciclo. La instrucción LV comienza la lectura en el ciclo 4, en el módulo m0, por ejemplo. Supongamos, por simplificar el problema, que el paso de A es 1. Por tanto, tras el módulo m0 se accederá a m1, m2..., m7, y nuevamente a m0, m1... Dado que la latencia es 3 ciclos, la instrucción no tiene ningún conflicto consigo misma (nm ≥ tm).

t (ciclos) mem. 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

m0 M M M - M M M m m m

m1 M M M - - M M M m m m

m2 M M M M M M

m3 M M M M M M

m4 M M M M M M

m5 M M M M M M

m6 M M M M M

m7 M M M M

Page 41: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.3 DEPENDENCIAS ESTRUCTURALES ▪ 27 ▪

La instrucción SV puede encadenarse en el ciclo 10, para empezar en memoria en el ciclo 11, en el módulo m0 (hay que guardar el mismo vector, A). ¿Cómo se encuentra en ese momento ese módulo? La instrucción LV está en ejecución, y mantiene ocupados varios módulos. Primeramente debemos calcular qué módulo va a acceder LV en ese ciclo, para lo que basta con saber cuántos ciclos lleva ya en memoria y de qué módulo partió. En este ejemplo, la distancia en ciclos es de 10 – 3 = 7, y dado que partió del módulo m0 (y que s = 1), en el ciclo 11 irá a utilizar el módulo m7. Ese módulo, por tanto, no estará disponible.

Pero habrá más módulos ocupados, ya que todavía estarán ejecutándose accesos que comenzaron en ciclos anteriores. Si la latencia de la memoria es tm, se mantiene ocupados tm–1 módulos anteriores. En el ejemplo tm = 3, por lo que tendremos dos módulos ocupados, m6 y m5. Utilizando el mismo razonamiento, es necesario dejar libres por delante otros tm–1 módulos, para que la instrucción LV pueda seguir ejecutándose sin interferencias; en el ejemplo, los módulos m0 y m1 (si no, LV “chocaría” con SV en el siguiente ciclo).

Así pues, en este ejemplo en el ciclo 11 están ocupados o reservados los módulos <m5 – m6 – m7 – m0 – m1>. Si alguna instrucción quiere utilizar esos módulos, deberá esperar a que se liberen. Ése es el caso de la instrucción SV, que tiene que utilizar m0, y que tendrá que esperar. ¿Cuántos ciclos? Tantos como la posición que ocupa el módulo a utilizar en la lista de módulos ocupados. En el ejemplo, 4 ciclos. En el ciclo 11 están ocupados los módulos m5-...-m1; en el siguiente ciclo, por tanto, los módulos m6-...-m2; en el siguiente, m7-...-m3, y, en el siguiente, m0-...-m4. Finalmente, en el siguiente ciclo se liberará m0, que podrá ser utilizado por la instrucción SV para comenzar la escritura del vector A.

En resumen, para analizar los conflictos en memoria entre las instrucciones j y k, el procedimiento es el siguiente (todas las operaciones son módulo nm, siendo nm el número de módulos de memoria):

a. Se calcula qué módulo va a comenzar a utilizar la instrucción j cuando la instrucción k quiere acceder a memoria:

(inik – inij) + módulo_inij (ini = ciclo inicio en memoria)

b. Se crea la lista de módulos ocupados, añadiendo tm–1 módulos por delante y por detrás al módulo anterior (tm, latencia de la memoria).

< tm–1 módulos | (inik – inij) + módulo_inij | tm–1 módulos>

Page 42: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 28 ▪ Capítulo 1: COMPUTADORES VECTORIALES

c. Si el primer módulo de memoria que va a utilizar la instrucción k está ocupado, se calcula el tiempo de espera, que habrá que añadir al tiempo de inicio de la instrucción (antes de la UF).

El procedimiento anterior se puede generalizar para el caso de acceso a vectores con paso s, siempre que los pasos de las instrucciones que están accediendo a memoria sean iguales. En este caso, dado que en cada paso se avanzan s módulos, por un lado, habrá que hacer (inik – inij) × s + módulo_inij y luego habrá que contar tm–1 módulos de s en s. En caso de que no coincida el paso de todas las instrucciones a memoria el análisis es más complejo y, normalmente, en función de la máquina, no se comenzará a ejecutar la segunda instrucción hasta que haya terminado la primera.

Repitamos el ejercicio anterior, pero teniendo en cuenta los conflictos en memoria. Tal y como se muestra en la tabla, la instrucción SV quiere realizar un encadenamiento en el ciclo 10. Dado que la instrucción LV está aún en memoria, no podrá utilizar los siguientes módulos: el módulo (10 – 3) + 0 = 7, los dos anteriores, 6 y 5, y los dos siguientes, 0 y 1. Como necesita acceder al módulo 0, el tiempo de espera será de 4 ciclos. Tras ese tiempo, ciclo 14, el encadenamiento no se podrá realizar directamente del sumador, sino que habrá que realizarlo desde el registro (si esto no fuera posible, habría que esperar a que la suma terminara de escribir en el registro V2).

2 buses / encadenamiento

A = A + 1 inic. mod. ocup. t. esp. lat. UF dato 1 dato N

LV V1,A(R1) 3 - - 3 6+1 6+N

ADDVI V2,V1,#1 [7] - - 2 9+1 9+N

SV A(R1),V2 [10] ?? 5 / 6 –7– 0 / 1 +4 3 17+1 17+N

En general, para calcular el número de ciclos que hay que esperar para

utilizar la memoria, hay que hacer el análisis con todas las instrucciones que estén en memoria, ya que cada una de ellas ocupará tm módulos de memoria. Como consecuencia de ello, no se pueden permitir más de nm div tm operaciones de memoria simultáneamente, ya que con ese número de instrucciones se ocupan todos los módulos de memoria. Por ejemplo, para el anterior caso (nm = 8 y tm = 3) no se pueden procesar simultáneamente más de 8 div 3 = 2; estaría de sobra, por tanto, un hipotético tercer bus a memoria.

Page 43: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.3 DEPENDENCIAS ESTRUCTURALES ▪ 29 ▪

De los párrafos anteriores se pueden deducir dos consecuencias importantes: por un lado, se necesita que el nivel de entrelazado del sistema de memoria sea grande, para que se pueda mantener el flujo de datos sin conflicto; y, por otro, va a ser importante una correcta colocación de los vectores en memoria para evitar colisiones en el acceso a diferentes vectores, para lo que puede ser importante el papel que juegue el compilador.

1.3.3 Longitud de los registros vectoriales (strip mining)

Otro factor que limita el rendimiento de la ejecución vectorial es el tamaño de los registros vectoriales. Los registros vectoriales se utilizan con el mismo propósito que los escalares: mantener cerca del procesador los datos que se van a utilizar. Los registros vectoriales son un recurso limitado. Por una parte, se trata de un número de registros no muy alto, menor en todo caso que el de registros escalares. Por otra parte, el número de buses de escritura y lectura también será limitado, y además se mantienen ocupados a lo largo de muchos ciclos (con la instrucción ADDV V3,V2,V1 se ocupan tres buses durante N ciclos). Por ello, necesitaremos bastantes buses para poder efectuar simultáneamente más de una operación con registros.

Una tercera limitación proviene del tamaño de los registros vectoriales. Si bien los vectores que procesa el usuario pueden ser de cualquier tamaño, los registros vectoriales suelen ser de un tamaño limitado, habitualmente de 64-128 palabras. Por tanto, con una instrucción vectorial no se pueden procesar más elementos que los correspondientes al tamaño de los registros. Para vectores más largos hay que montar un bucle y procesar el vector en trozos de tamaño Lmax. A este procedimiento se le denomina strip mining.

El tamaño de los vectores que hay que leer o escribir en memoria se indica en un registro especial, VL (vector length). Si VL ≤ Lmax, se procesará el número de elementos indicado en VL; por el contrario, si VL > Lmax, entonces se procesarán únicamente Lmax elementos. Por ejemplo:

do i = 0, N-1 A(i) = A(i) + 1 → enddo

MOVI VS,#1 MOVI R1,#N mas: MOV VL,R1

LV V1,A(R2) ADDVI V2,V1,#1 SV A(R2),V2

ADDI R2,R2,#Lmax (× tam. pal.) SUBI R1,R1,#Lmax BGTZ R1,mas

Page 44: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 30 ▪ Capítulo 1: COMPUTADORES VECTORIALES

Según la longitud de los vectores, puede ser que el último (o el primer) trozo que se procese sea más pequeño que el resto. Por ejemplo, si tenemos Lmax = 128 y N = 1000, en la última iteración sólo se procesarán 104 elementos (7 × 128 + 104 = 1000).

Todo ello va a repercutir en el tiempo de ejecución de la operación vectorial (en la velocidad de cálculo, por tanto). Si el tiempo de una operación vectorial puede darse como TV = ti + tv N, el hecho de que los registros sean de tamaño Lmax hará que el tiempo de ejecución se exprese como:

NtttL

NT vbuciV ++

= )(

max

N/Lmax indica el número de trozos a procesar (iteraciones del bucle), y tbuc el tiempo necesario para el control del bucle (ahora el sumando inicial también depende de N).

El proceso de strip mining es prácticamente inevitable cuando hay que procesar vectores, ya que, en la mayoría de los casos, la longitud de los vectores es un parámetro que se decide en ejecución. Por tanto, incluso en el caso de que cupiera el vector en el registro, habrá que “pagar” una vez el coste del control del bucle, tbuc.

Veamos un ejemplo:

N = 500 TV = 30 + 3N → TV = 30 + 1500 = 1530 ciclos (idea) 3,06 ciclos/elemento pero <

Lmax = 64 tbuc = 10 ciclos → TV = 8 × (30+10) + 1500 = 1820 ciclos 3,64 ciclos/elemento (+ 19%)

1.4 VELOCIDAD DE CÁLCULO DE LOS COMPUTA-DORES VECTORIALES

Cuando hemos definido los computadores vectoriales hemos indicado el deseo de construir una máquina de gran velocidad en la ejecución de determinados programas. Analicemos un poco más despacio los parámetros básicos que definen la velocidad de cálculo en estas máquinas.

Page 45: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.4 VELOCIDAD DE CÁLCULO DE LOS COMPUTA-DORES VECTORIALES ▪ 31 ▪

1.4.1 Velocidad de cálculo en función de la longitud de los vectores

1.4.1.1 R∞ y N1/2

Recordemos cómo se puede expresar el tiempo de ejecución de un programa en modo vectorial y en modo escalar:

▪ en modo escalar TE = te N te = tiempo para ejecutar una iteración

▪ en modo vectorial7 TV = ti + tv N ti = tiempo de inicio tv = tiempo para procesar un elemento del vector

Los tiempos de ejecución se suelen dar en ciclos o en (nano)segundos (basta multiplicar por el periodo del reloj). En la figura se representa el tiempo de ejecución vectorial en función de la longitud de los vectores, una recta.

A partir de ahí, definimos la velocidad de cálculo o rendimiento (performance) como el número de elementos que se procesa por unidad de tiempo (ciclo o segundo):

NttN

TNR

viVN +

==

La velocidad de cálculo se suele dar en Mflop/s (Mega FLoat OPeration / second), es decir, cuántos millones de operaciones de coma flotante se realizan por segundo, para lo que tenemos que considerar el número de 7 Sin considerar el tamaño de los registros vectoriales.

0 5

100 150 200 250 300

0 25 50 75 100 125 150 N (longitud de los vectores)

ti

pendiente = tb

TV = 30 + 2N N1/2

2ti

TV

Page 46: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 32 ▪ Capítulo 1: COMPUTADORES VECTORIALES

operaciones que se realizan con los vectores, OpCF8, ya que en total se ejecutarán N × OpCF operaciones.

El tiempo de ejecución debe estar en segundos; si está en ciclos (como en los ejemplos vistos hasta ahora) hay que multiplicarlo por el periodo de reloj, lo que, dado que el tiempo está en el divisor, equivale a multiplicar la expresión anterior por la frecuencia de reloj (T = 1/F). Por tanto:

FOpCFNtt

NTNR

viVN ××

+== Mflop/s (TV en ciclos, F en MHz)

Analicemos gráficamente el comportamiento de la expresión anterior.

Tal como se observa en la figura, la función R tiene una asíntota cuando N tiende a ∞. Aunque los vectores fueran muy largos, la velocidad de cálculo tiene un límite. A ese valor máximo se le denomina R∞.

FOpCFt

RRv

NN ××== ∞→∞1lim

También es habitual indicar la eficiencia del sistema, es decir, la fracción

de la velocidad máxima que se consigue:

Eficiencia = R / R∞ en el intervalo [0, 1]

Por definición, ninguna máquina puede alcanzar la velocidad máxima R∞. Por ello, se suele utilizar otro parámetro más: N1/2, tamaño mínimo de los vectores que permite alcanzar al menos la mitad de la velocidad máxima. De acuerdo a la definición, R(N1/2) = R∞/2; por tanto:

N1/2 / (ti + tv × N1/2) = 1/tv × 1/2 → N1/2 = ti / tv (entero superior) 8 En lo que a la velocidad de cálculo respecta, no es lo mismo efectuar una suma con los vectores que

efectuar dos sumas y una multiplicación.

N (número de elementos)

R∞

R (

rend

imie

nto)

R∞/2

N1/2

Page 47: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.4 VELOCIDAD DE CÁLCULO DE LOS COMPUTA-DORES VECTORIALES ▪ 33 ▪

¡Si N1/2 es muy alto, lo más probable es que andemos lejos del valor máximo de velocidad; en cambio, si es pequeño, no necesitaremos procesar vectores muy largos para acercarnos a la velocidad máxima de cálculo. De la misma manera que hemos definido N1/2, podemos definir N3/4, N1/4, etc., es decir, el tamaño mínimo de los vectores para conseguir una determinada fracción de la velocidad máxima9.

Los dos parámetros que definen la velocidad de cálculo (performance) pueden obtenerse mediante un experimento muy simple. Se ejecuta el programa vectorial para diferentes valores de N, se mide el tiempo de ejecución, y se dibuja la función TV(N), que deberá ser una recta, ya que el tiempo crece linealmente con N. La ordenada en el origen de esa recta nos indica el valor de ti y la pendiente de la recta el valor de tv. Así pues, el valor de R∞ será el inverso de la pendiente de la recta. Para calcular N1/2 basta con medir el valor de N que hace TV = 2 ti (cuando N = N1/2 = ti / tv, TV = ti + (ti /tv) tv = 2 ti).

El parámetro R∞ es función del programa que se ejecuta. En todo caso, es sencillo obtener la velocidad teórica máxima (peak performance) que podría conseguir un computador vectorial. El máximo lo obtendríamos si tv = 1 ciclo y se utilizaran simultáneamente todas las unidades funcionales (OpCF = #UF). Por ejemplo, un computador vectorial que dispone de 6 unidades funcionales y cuyo reloj es de F = 500 MHz, podría lograr 6 × 500 = 3000 Mflop/s = 3 Gflop/s. Sin embargo, ese valor no es representativo de un caso real, ya que sólo se puede conseguir en casos muy excepcionales. Lo más habitual es que tv > 1 y que no se estén utilizando todas las unidades funcionales simultáneamente.

1.4.1.2 Speed-up o factor de aceleración

Para representar la velocidad de cálculo de un computador vectorial podemos efectuar esta otra comparación: ¿cuántas veces es más rápida la ejecución del programa en modo vectorial que en modo escalar?

NttNt

TTK

vi

e

V

EV +

== v

e

ttK =∞

El comportamiento de la función KV es similar al de R, y obtiene un máximo cuando N tiende a infinito. El parámetro K∞ indica cuántas veces es

9 Si se prefiere, el tiempo de ejecución y la velocidad de cálculo pueden darse en función de los dos

parámetros que acabamos de definir, N1/2 y R∞: TV = (N + N1/2) / R∞ RV = R∞ × (1 / (1 + N1/2/N))

Page 48: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 34 ▪ Capítulo 1: COMPUTADORES VECTORIALES

más rápido el proceso de un elemento del vector en modo vectorial que en modo escalar, siendo los vectores que se procesan muy largos. Inicialmente, nos interesa que el parámetro K∞ sea grande, porque indica que el procesador vectorial es muy rápido. Sin embargo, como vamos a ver enseguida, la situación no es tan clara como parece, ya que habrá que ejecutar también código escalar junto con el vectorial.

1.4.1.3 NV

Los dos parámetros de “calidad” más utilizados son R∞ y N1/2, aunque pueden plantearse otros. Por ejemplo, ¿se obtiene siempre un tiempo de ejecución menor en modo vectorial que en modo escalar? Podemos calcular el parámetro Nv, longitud de los vectores que hace que TE = TV.

12/1

−=

−=→+=

∞KN

tttNNttNt

ve

ivvvive

Por tanto, si los vectores a procesar son más cortos que Nv (función de N1/2 y K∞), entonces no merece la pena ejecutar en modo vectorial, ya que la ejecución en modo escalar será más rápida.

1.4.2 Influencia del código escalar. Ley de Amdahl.

Los programas adecuados para ejecutar en un procesador vectorial son los que procesan vectores. Sin embargo, en un programa general habrá que procesar también, junto al código vectorial, código escalar (no son habituales los programas que se pueden expresar en un 100% en forma de código vectorial). Por tanto, para medir correctamente la velocidad de ejecución, es necesario contar con el tiempo de ejecución del código escalar.

Sea f la fracción de código que puede ser ejecutada vectorialmente y 1 – f la parte que hay que ejecutar en modo escalar. El tiempo de ejecución del programa se puede expresar como:

TVE = f TV + (1 – f) TE

Así pues, comparado con el procesador escalar, el factor de aceleración logrado será:

KKfK

TfKTf

TTffT

TTTK

EE

E

EV

E

VE

EVE +−

=−+

=−+

==)1()1()1(

Page 49: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.4 VELOCIDAD DE CÁLCULO DE LOS COMPUTA-DORES VECTORIALES ▪ 35 ▪

La expresión anterior se conoce como ley de Amdahl. Cuando f = 0 (todo el código es escalar), el factor de aceleración es 1; y cuando f = 1 (todo el código es código vectorial), el factor de aceleración es KV, tal como hemos definido anteriormente. Analicemos gráficamente el comportamiento del factor de aceleración en función de f.

Tal como aparece en la gráfica, para poder obtener factores de aceleración

significativos es necesario que el factor de vectorización sea alto. Por ejemplo, para KV = 16, si queremos que la ejecución vectorial sea 8 veces más rápida, se necesita que f > 93%. Analizado desde otro punto de vista, si, por ejemplo, f = 0,65, el factor de aceleración no será nunca mayor que 3, aunque KV sea infinito; para KV = ∞, el factor de aceleración es 1 / (1–f).

La vectorización del código de un determinado programa es tarea del compilador (con la colaboración, tal vez, del programador). En la gráfica anterior hemos marcado en el eje X los valores de f que suelen lograr los compiladores vectoriales, normalmente en el intervalo [0,55 - 0,75]. Queda claro que con esos factores de vectorización no es posible lograr altos valores de speed-up, aunque KV sea muy grande. Por ello, la eficiencia del proceso de compilación vectorial es crucial para poder obtener el máximo rendimiento de un computador vectorial.

Tal como hemos calculado antes el parámetro N1/2, en este caso se puede obtener un parámetro similar, f1/2, factor de vectorización necesario para obtener al menos la mitad de la velocidad máxima (KV /2).

111

)1(2 2/12/1 −

−=→+−

=VVV

VVK

fKKf

KK

KV = ∞ 16

8 4 2

Page 50: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 36 ▪ Capítulo 1: COMPUTADORES VECTORIALES

En general, por tanto, si se desea calcular los Mflop/s que realmente se conseguirán con un determinado programa, hay que considerar tanto el código escalar como el vectorial:

NtKfNttfN

NtfNttfN

TffTN

TNR

vvieviEVVEVE

∞−++=

−++=

−+==

)1()()1()()1(

Como siempre, para ponerlo en Mflop/s hay que multiplicar por el número de operaciones en coma flotante realizadas, y por la frecuencia de reloj (si el tiempo estaba en ciclos).

En algunos textos, la expresión anterior suele darse de la siguiente manera:

RN,f = R∞ × εN × εf donde εN = 1 / [1 + N1/2/N] y εf = 1 / [f + (1–f) K∞ × εN]

es decir, hay un rendimiento máximo —R∞,1— cuando la longitud de los vectores es infinita y el factor de vectorización es 1; y luego existen dos limitaciones, una debida a la longitud finita de los vectores, N, y otra debida a que el factor de vectorización es f y no 1. Pongamos un ejemplo. Un computador vectorial tiene los siguientes parámetros: R∞ = 800 Mflop/s, N1/2 = 60, K∞ = 10, f = 0,8 y N = 128. Si fueran N infinito y f = 1, se obtendrían 800 Mflop/s. Como N = 128, el primer límite es εN = 0,68. Además, como f = 0,8 (y N = 128) tenemos un segundo límite, εf = 0,46. En consecuencia, la velocidad de cálculo que se consiga será: 800 × 0,68 × 0,46 = 252 Mflop/s.

Conviene enfatizar nuevamente la influencia del código escalar en el rendimiento total del sistema. La siguiente figura presenta la comparación de dos mejoras efectuadas en un computador vectorial.

El comportamiento en los dos extremos es claro. Cuando los programas se

vectorizan por completo (f = 1), el factor de aceleración es mejor en la

0 2 4 6 8

10 12

14 16

0 0.2 0.4 0.6 0.8 1

Fact

or d

e ac

eler

ació

n (n

orm

aliz

ado)

f (factor de vectorización)

Ley de Amdahl

CRAY X-MP tv = 10 ns te = 66,6ns

tv = 5 ns te = 66,6 ns

tv = 10 ns te = 33,3 ns

Page 51: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 37 ▪

máquina en la que se ha duplicado KV (la nueva máquina es dos veces más rápida). En cambio, si el programa no se puede vectorizar (f = 0, todo código escalar), entonces los resultados son mejores en el computador que ha mejorado el procesador escalar.

¿Y en un caso general? La respuesta depende de f. Pero atención, si f se mantiene en el intervalo [0,6 – 0,8], en ese caso no interesa que KV sea muy alto, y resulta más eficaz mejorar la respuesta del procesador escalar. Por tanto, salvo que sepamos que nuestros programas se vectorizan siempre en un factor muy elevado, no resulta interesante que el computador vectorial sea de KV muy elevado, puesto que no vamos a poder aprovechar sus características específicas.

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR

CÓDIGO VECTORIAL

Los procesadores vectoriales ejecutan código vectorial, pero, en general, sólo una parte de los programas puede ejecutarse de esa manera. ¿A quién corresponde detectar qué partes del código son vectorizables y escribir el correspondiente código, al programador o al compilador? Lo más adecuado es que el trabajo del programador sea independiente de la máquina; se programan algoritmos en alto nivel, y el correspondiente compilador traducirá esos programas al código más adecuado para la máquina en que se vayan a ejecutar, teniendo en cuenta las características de la misma. Afortunadamente, existen buenos compiladores vectoriales que generan código vectorial de manera eficiente, para lo que previamente analizan las dependencias entre instrucciones y deciden qué partes del código pueden ejecutarse vectorialmente. En todo caso, siempre es importante la ayuda de un programador “inteligente”, ya que a veces no es sencillo traducir automáticamente de alto nivel a código vectorial. Por ello, algunos lenguajes (Fortran, por ejemplo) tienen directivas especiales para indicar operaciones vectoriales y ayudar al compilador.

Como hemos comprobado, es esencial conseguir factores de vectorización altos. En caso contrario, la velocidad de procesamiento se alejará mucho de los máximos teóricos. En los siguientes apartados vamos a analizar las estrategias principales que sigue un compilador vectorial para generar código vectorial. Utilizaremos las mismas o parecidas estrategias un poco más adelante, cuando tengamos que ejecutar un bucle entre P procesadores.

Page 52: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 38 ▪ Capítulo 1: COMPUTADORES VECTORIALES

1.5.1 Dependencias de datos entre instrucciones

Como ya hemos comentado, vectorizar implica, entre otras cosas, una determinada reordenación del código original. Sin embargo, las instrucciones no pueden reordenarse de cualquier manera, puesto que hay que respetar las dependencias de datos. Recordemos brevemente los tres tipos de dependencias de datos.

• Dependencias verdaderas (RAW read-after-write, RD)

1: A = B + C 2: D = A

Existe una dependencia entre las instrucciones 1 y 2, porque el

resultado de la instrucción 1 se utiliza en la 2. Representamos las dependencias en un grafo, el grafo de dependencias, mediante una flecha que va de 1 a 2, indicando qué se debe hacer antes y qué después. En este ejemplo, la instrucción 1 debe escribir antes que la instrucción 2 lea el operando.

Las dependencias RAW no pueden evitarse, puesto que son intrínsecas al algoritmo que se quiere ejecutar. En algunos casos, pueden resolverse mediante cortocircuitos; en caso contrario, habrá que esperar a que se ejecute la operación anterior.

• Antidependencias (WAR write-after-read, DR)

1: A = B + C 2: B = D

Existe una antidependencia entre las instrucciones 1 y 2, puesto que un operando que necesita la instrucción 1 –B– es modificado por la 2. En el grafo de dependencias las antidependencias se indican mediante una flecha cruzada por una raya. En este ejemplo, la flecha indica que se debe leer B en la instrucción 1 antes que escribir B en la 2.

Las antidependencias no son dependencias “fuertes”, y en muchos casos desaparecen con una correcta reordenación del código.

1

2

A

1

2

B

Page 53: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 39 ▪

• Dependencias de salida (WAW write-after-write, RR)

1: A = B + C 2: A = D

Existe una dependencia de salida entre las instrucciones 1 y 2, ya que la instrucción 2 va a escribir en la misma variable que la 1. La representamos en el grafo mediante una flecha con un pequeño círculo, que indica que hay que respetar el orden de las escrituras.

Como en el caso anterior, las dependencias de salida no son “fuertes” y suelen estar asociadas a la manera de escribir el programa; por tanto, pueden desaparecer con una correcta ordenación del código vectorial.

Recuerda: sea cual sea el tipo, las dependencias implican un orden determinado de ciertas operaciones: qué hay que hacer antes y qué después.

En los párrafos anteriores hemos visto las dependencias entre instrucciones individuales. Pero el código vectorial reemplaza un bucle completo de instrucciones, por lo que al analizar las dependencias entre instrucciones hay que tener en cuenta que se pueden producir entre instrucciones de cualquier iteración. Definimos “distancia” de una dependencia como el número de iteraciones que hay entre las instrucciones que tienen dicha dependencia, y la indicaremos en el propio grafo de dependencias. Por ejemplo, si la dependencia se produce en la misma iteración, la distancia es 0; si es en la siguiente, la distancia es 1, etc. En los bucles de más de una dimensión, la distancia se representa como un vector de distancias, con un elemento por cada dimensión del bucle. Por ejemplo:

do i = 2, N-2 1: A(i) = B(i) + 2 2: C(i) = A(i-2) + A(i+1) enddo

do i = 2, N-1 do j = 1, N-2 1: A(i,j) = A(i,j-1) * 2 2: C(i,j) = A(i-2,j+1) + 1 enddo enddo

grafo de dependencias espacio de iteraciones

1

2

A

A, 2

A, 1

i

i=0 i=1 i=2 …

1

2

A, 2 A, 1

1

2

A, (2, –1)

A, (0, 1) j

i A, (2, –1)

A, (0, 1)

Page 54: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 40 ▪ Capítulo 1: COMPUTADORES VECTORIALES

Cuando la dependencia se produce entre iteraciones diferentes (d > 0), se dice que es loop carried.

Para poder vectorizar un bucle, el primer paso consiste en efectuar el análisis de dependencias (no olvides que vectorizar implica desordenar el código), y generar el grafo de dependencias. En este grafo se representan las dependencias entre instrucciones —de la instrucción i a la j—, para todas las iteraciones del bucle. En los casos de más de una dimensión, también es útil dibujar un segundo grafo, el espacio de iteraciones (como en la figura anterior), en el que las dependencias no se marcan entre instrucciones, sino entre iteraciones. En los siguientes ejemplos utilizaremos ambos grafos.

1.5.2 Vectorización

1.5.2.1 Vectores de una dimensión

Antes de formalizar las técnicas de vectorización, veamos algunos ejemplos10.

1.5.2.1.1 Primer ejemplo

do i = 0, N-1 A(i) = B(i) + C(i) enddo

No existe ningún tipo de dependencia entre las instrucciones del bucle, por lo que puede escribirse en forma vectorial, sin problemas:

MOVI VL,#N ; longitud de los vectores MOVI VS,#1 ; paso de los vectores (stride) LV V1,B(R1) LV V2,C(R1) ADDV V3,V1,V2 SV A(R1),V3

En algunos lenguajes, el código anterior se expresa así: A(0:N:1) = B(0:N:1) + C(0:N:1), donde A(x:y:z) indica: x, comienzo del vector; y, número de elementos; y z, paso del vector.

10 Salvo que se indique lo contrario, los vectores son de tamaño N (o N×N); la dirección A indica el

primer elemento del vector, A0; A+1 indica el siguiente elemento, etc. (sin considerar el tamaño de los elementos y la unidad de direccionamiento de la memoria). Vectores de nombre diferente utilizan posiciones de memoria diferentes, es decir, no se solapan (no hay aliasing). El contenido inicial del registro utilizado para direccionar es siempre 0 (en el ejemplo, R1).

Page 55: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 41 ▪

El código escalar original y el vectorial que acabamos de escribir no son exactamente equivalentes. El bucle escalar utiliza la variable i para controlar el número de iteraciones e indicar los elementos del vector, por lo que al acabar el bucle i contendrá el valor correspondiente a la última iteración, y, aunque no es habitual, tal vez se utilice dicha variable más adelante en el programa. El compilador vectorial tiene que generar código equivalente al original; por ello, aunque no se necesita para nada en las instrucciones vectoriales, debe dejar en i el valor final correspondiente, en este caso N–1. Lo mismo habrá que hacer con el resto de variables similares. Por claridad, vamos a omitir el código correspondiente a esas operaciones.

1.5.2.1.2 Segundo ejemplo

do i = 0, N-1 1: A(i) = B(i) + C(i) 2: D(i) = A(i) enddo

grafo de dependencias espacio de iteraciones

Hemos dibujado ambos grafos: el de dependencias y el del espacio de iteraciones. Existe una dependencia en el bucle, de la primera instrucción a la segunda, y la dependencia se produce en la misma iteración, como se observa en el espacio de iteraciones. En algunos textos, la dependencia anterior se indica de la siguiente manera: 1 δ= 2; el símbolo = indica que la dependencia es de distancia 0 (si la distancia es mayor que 0, se utiliza el símbolo <).

La dependencia no implica ningún problema, y el código se puede vectorizar de la siguiente manera:

MOVI VL,#N MOVI VS,#1 (1) LV V1,B(R1) LV V2,C(R1) ADDV V3,V1,V2 SV A(R1),V3 ; A = B + C (2) SV D(R1),V3 ; no hay que leer A, ya que está en V3

1

2

A, 0 A, 0

i

Page 56: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 42 ▪ Capítulo 1: COMPUTADORES VECTORIALES

1.5.2.1.3 Tercer ejemplo

do i = 1, N-1 1: A(i) = B(i) + C(i) 2: D(i) = A(i-1) enddo

1 δ< 2

Aunque el grafo de dependencias es similar al del ejemplo anterior, ahora las dependencias van de iteración a iteración: hay que utilizar en la segunda instrucción de la iteración i el resultado de la primera instrucción de la iteración i–1. Aunque se observa una cadena de dependencias en el espacio de iteraciones, las dependencias son entre instrucciones diferentes: 1i → 2i+1.

Nuevamente, el código puede vectorizarse sin problemas:

MOVI VL,#N-1 MOVI VS,#1

(1) LV V1,B+1(R1) LV V2,C+1(R1) ADDV V3,V1,V2 SV A+1(R1),V3 ; se escibe el vector A1–AN-1

(2) LV V4,A(R1) ; se lee de memoria el vector A0–AN-2 SV D+1(R1),V4

Claramente, en esta ocasión no se puede aprovechar en la segunda instrucción el resultado de la primera, ya que no se trata del mismo vector (A0-AN-2); por tanto, primero hay que escribir en memoria el vector A1-AN-1 y luego leer A0-AN-2.

1.5.2.1.4 Cuarto ejemplo

do i = 0, N-2 1: A(i) = B(i) + C(i) 2: D(i) = A(i+1) enddo

En este bucle existe una antidependencia, de la segunda instrucción a la primera. El bucle no puede vectorizarse en el orden original, puesto que no se puede escribir el vector A(i) (todos los elementos) en la primera instrucción antes que leer el vector A(i+1) en la segunda.

1

2

A, 1

A, 1

i

1

2

A, 1

A, 1

i

Page 57: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 43 ▪

Formalizaremos este caso un poco más adelante; basta ahora decir que el problema se arregla con un cambio de orden, tal como el siguiente:

MOVI VL,#N-1 MOVI VS,#1

(2) LV V1,A+1(R1) ; adelantar la lectura de la instrucción 2 SV D(R1),V1

(1) LV V2,B(R1) LV V3,C(R1) ADDV V4,V2,V3 SV A(R1),V4 ; escribir el resultado de la instrucción 1

Como puede observarse, el código vectorial respeta la antidependencia

original.

1.5.2.1.5 Quinto ejemplo

do i = 1, N-1 1: A(i) = B(i-1) + 1 2: B(i) = A(i) enddo

En este bucle aparece el problema más grave de vectorización. Las

dependencias forman un ciclo en el grafo: la instrucción 2 necesita los datos producidos por la 1 (vector A), y la primera instrucción necesita los datos producidos por la segunda (casi todo el vector B). No hay nada que hacer; hay que ejecutar el bucle en modo escalar.

El ejemplo más típico de un ciclo de dependencias es una recurrencia: un ciclo de una única instrucción. Por ejemplo:

do i = 3, N-1 1: A(i) = A(i-3) * 3 enddo

En cada iteración, se necesita como operando el resultado producido tres iteraciones antes. Está claro que una recurrencia no puede vectorizarse: ¿cómo leer con una instrucción —LV V1,A(R1)— todo un vector, si todavía no se han generado los elementos del vector?

1

2

A, 0 B, 1

A, 0

i

B, 1

1 A, 3

Page 58: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 44 ▪ Capítulo 1: COMPUTADORES VECTORIALES

1.5.2.2 Vectores de N dimensiones

Los vectores de los ejemplos anteriores son de una dimensión. Pero, ¿cómo se vectoriza, por ejemplo, una operación con matrices?

do i = 0, N-1 (todos los vectores son de tamaño [N, M]) do j = 0, M-1 A(i,j) = B(i,j) + C(i,j) enddo enddo

Cuando se trabaja con matrices, se suelen utilizar habitualmente dos tipos

de vectores: filas y columnas. El propio bucle indicará cómo hay que procesar la matriz, por filas o por columnas, pero, en muchos casos, ambas posibilidades son correctas (como en el caso anterior, en el que da igual ejecutar el bucle en el orden do i / do j que en el orden do j / do i).

Para generar el grafo de dependencias, el compilador analizará el bucle más interior, y en base a ello decidirá qué hacer. En todo caso, para procesar vectores de dos dimensiones es necesario montar un bucle escalar, que procese las filas, o las columnas, una a una.

Por ejemplo, en el bucle anterior no hay ninguna dependencia. Por tanto, tenemos dos posibilidades: vectorizar el bucle interior —j, procesar la matriz por filas—, o el exterior —i, por columnas—.

Si vectorizamos por filas, el bucle quedaría así:

MOVI R2,#N ; número de filas MOVI VL,#M ; longitud de las filas MOVI VS,#1 ; paso buc: LV V1,B(R1) LV V2,C(R1) ADDV V3,V1,V2 SV A(R1),V3 ADDI R1,R1,#M ; siguiente fila SUBI R2,R2,#1 ; una fila menos BNZ R2,buc

Después de procesar un fila vectorialmente, se actualiza el registro R1

(+M), para direccionar la fila siguiente. El registro R2 es un simple contador, para procesar todas las filas.

j

A

s = 1

i

0,0 0,1 … 0,M-1

1,0 1,1 … 1,M-1

… … … …

N-1,0 N-1,1 … N-1,M-1

Page 59: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 45 ▪

Si se quiere vectorizar la matriz por columnas, el código sería el siguiente:

MOVI R2,#M ; número de columnas MOVI VL,#N ; longitud de las columnas MOVI VS,#M ; paso buc: LV V1,B(R1) LV V2,C(R1) ADDV V3,V1,V2 SV A(R1),V3 ADDI R1,R1,#1 ; siguiente columna SUBI R2,R2,#1 ; una columna menos BNZ R2,buc

En este caso, el paso de los vectores (columnas) es M, y para apuntar a la

siguiente columna basta con incrementar (+1) la dirección de comienzo11. Cuando se utiliza esta segunda opción se dice que se ha efectuado un intercambio de bucles.

En general, para vectorizar bucles de P dimensiones hay que analizar P alternativas (una por cada dimensión), para escoger la más adecuada en función de las dependencias de datos entre las instrucciones.

1.5.2.3 Condición para vectorizar un bucle

Resumamos lo visto en los ejemplos anteriores. El compilador debe analizar las dependencias entre las instrucciones y generar un grafo de dependencias. Basándose en ello, debe decidir si el bucle es vectorizable o no y cómo. A menudo, para generar código vectorial es necesario reordenar el código original. Al hacerlo, claro está, el compilador debe respetar el orden de ejecución que imponen las diferentes dependencias de datos: la dependencia x → y indica que alguna operación de la instrucción x debe ir antes que alguna de la y.

Por desgracia, no todos los bucles son vectorizables. ¿Cómo saber cuándo sí y cuándo no? En general, un bucle puede vectorizarse si las dependencias entre instrucciones no forman ciclos en el grafo de dependencias.

11 En estos ejemplos hemos supuesto que las matrices están almacenada en memoria por filas, tal como,

por ejemplo, se hace en C; en Fortran, en cambio, las matrices se guardan por columnas.

j A

s = M

i

0,0 0,1 … 0,M-1

1,0 1,1 … 1,M-1

… … … …

N-1,0 N-1,1 … N-1,M-1

Page 60: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 46 ▪ Capítulo 1: COMPUTADORES VECTORIALES

En esos casos, el compilador generará código vectorial para el bucle, manteniendo en algunos casos el orden original de las instrucciones, y cambiándolo en otros para respetar las dependencias. La condición anterior no implica que no se pueda vectorizar el bucle cuando existan ciclos de dependencias, puesto que, como vamos a ver, pueden aplicarse ciertas técnicas que “deshacen” dichos ciclos.

Aunque las dependencias formen ciclos en el grafo de dependencias, normalmente sólo algunas instrucciones del bucle tomarán parte en dichos ciclos. Por ello, aunque no se puedan vectorizar todas la instrucciones del bucle, el compilador debe intentar vectorizar el mayor número posible de operaciones; las instrucciones que presenten problemas se ejecutarán escalarmente, y el resto vectorialmente (loop fission). Por ejemplo:

do i = 1, N-1 A(i) = B(i) B(i) = B(i-1) enddo

(puede vectorizarse la primera instrucción, pero no la segunda, debido a la dependencia, una recurrencia)

MOVI VL,#N-1 MOVI VS,#1 LV V1,B+1(R1) SV A+1(R1),V1

MOVI R3,#N-1

buc: FLD F1,B(R2) FST B+1(R2),F1 ADDI R2,R2,#1 SUBI R3,R3,#1 BNZ R3,buc

1.5.2.4 Test de dependencias

Como hemos comentado, el primer paso del proceso de vectorización es el análisis de las dependencias. ¿Es sencillo saber si existe una dependencia entre dos instrucciones dadas? En los ejemplos anteriores era muy simple, porque los índices utilizados para el acceso a los vectores eran funciones muy sencillas (i, i+1...). Sin embargo, ¿qué podemos decir en este ejemplo?

do i = L1, L2 X(f(i)) = X(g(i)) + 1 enddo

¿Hay una dependencia en el vector X (sería una recurrencia)? Claro está, la respuesta depende de las funciones f y g. Por desgracia, el resultado de las funciones f y g no se puede predecir, en el caso general, en tiempo de

1

2

B, 1

B, 0

Page 61: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 47 ▪

compilación, por lo que el compilador no tiene información suficiente para tomar una decisión, por lo que debe suponer que existe la dependencia.

Sin embargo, en algunos casos, que son muy comunes, el compilador puede analizar y decidir si existe o no una dependencia: en el caso en que f y g sean funciones lineales de los índices del bucle. Por ejemplo:

do i = L1, L2 X(a*i+b) = ... ... = X(c*i+d) enddo

Por otro lado, ése es el único caso que se corresponde con la definición que hemos dado de vector: la distancia entre dos elementos consecutivos es constante. Más adelante veremos cómo procesar vectores cuyo paso no sea constante (por ejemplo, A(i2)→ A1, A 4, A 9, A16...).

Para saber si existe una dependencia en el vector X hay que resolver la siguiente ecuación:

a i1 + b = c i2 + d L1 ≤ i1, i2 ∈ Z ≤ L2

es decir, hay que saber si existen dos valores i1 e i2, dentro de los límites de iteración del bucle, para los que coincidan las direcciones de acceso al vector.

La expresión anterior es una ecuación diofántica, y encontrar una solución general a la misma es muy complejo. Sin embargo, puede afirmarse que:

▪ No existe dependencia (la ecuación anterior no tiene solución), si (d – b) / MCD(a, c) ∉ Z, es decir, si no es un entero.

Este test se conoce como el test del máximo común divisor (MCD). No es el único test que aplican los compiladores para analizar las dependencias, pero es suficiente para los casos más habituales.

a i + b

c i + d

i1 i2 L1 L2

Page 62: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 48 ▪ Capítulo 1: COMPUTADORES VECTORIALES

El test del MCD indica cuándo no hay dependencia, no cuándo la hay. Esto es, si el resultado es un número entero, las ecuaciones tienen solución, pero para saber si existe, o no, dependencia habrá que analizar las soluciones y comprobar que se encuentran dentro de los límites del bucle. Para ello, a menudo es suficiente con analizar los trozos del vector que accede cada instrucción: si no se solapan, entonces no hay dependencia; pero si se solapan, entonces sí que puede haberla y habrá que hacer un análisis más detallado12. Se pueden diferenciar tres casos:

(1) (2) (3)

En el primer caso, no hay dependencia; es decir, la hipotética solución de la ecuación está fuera de los límites del bucle (dentro de los límites, las dos ecuaciones no proporcionan nunca el mismo valor). En el segundo caso, puede haber dependencia, ya que las dos ecuaciones tienen un trozo de vector común al que acceden (el tipo de dependencia variará en función del tipo de operación de cada acceso). El tercer caso es el más complejo. Puede existir dependencia entre las dos instrucciones; además, si en una se lee y en la otra se escribe, en un tramo del bucle tendremos antidependencias y en el otro, dependencias (si las dos operaciones fueran escrituras tendríamos un problema similar). Por tanto, estas instrucciones no se pueden vectorizar, algunos elementos hay que leer antes de escribir sobre ellos y otros después de que se hayan escrito (quizás se pueda dividir el bucle en dos partes, en función del punto de cruce, y utilizar técnicas distintas en cada parte para generar el código).

Veamos algunos ejemplos: (1) do i = 1, 100 A(2*i) = ... → (1 – 0) / MCD(2, 2) = 1/2 ... = A(2*i+1) enddo

Por tanto, no hay dependencias entre las dos instrucciones; en este caso, una instrucción escribe elementos pares y la otra lee elementos impares.

12 Habrá que tener en cuenta los pasos de los vectores (a y c) y la longitud del segmento que se solapa,

para comprobar si ambos accesos coinciden en, al menos, un elemento del vector.

i L1 L2 i L1 L2

i L1 L2

Page 63: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 49 ▪

(2) do i = 5, 100 A(i-5) = ... ... = A(2*i+90) → (90 – (–5)) / MCD(2, 1) = 95 enddo

Por tanto, puede haber una dependencia. Pero no la hay, porque los intervalos de acceso son disjuntos: wr: A0 ... ... A95 rd: A100 ... ... A290

(3) do i = 1, 100 A(3*i+100) = ... → (100 – (–1)) / MCD(3, 2) = 101 ... = A(2*i-1) enddo

Podría haber una dependencia; los intervalos de acceso son los siguientes: wr: A103 ... ... A400 rd: A1 ... ... A199 Los dos intervalos tienen un trozo en común, por lo que puede haber una dependencia; y en este caso la hay: por ejemplo, la escritura de la iteración i = 1 en (A103) se lee en la iteración i = 52.

(4) do i = 1, 100 A(6*i+3) = ... → (81 – 3) / MCD(6, 3) = 26 ... = A(3*i+81) enddo

Por tanto, puede haber una dependencia. Los intervalos de acceso son los siguientes: wr: A9 ... ... A603 rd: A84 ... ... A381

Un intervalo está dentro del otro; si existe dependencia, seguramente será de dos tipos. Por ejemplo, en la iteración i = 2 se lee el elemento A87, que luego se va a escribir (una antidependencia); pero, en la iteración i = 28, se lee el elemento A165, que es el resultado de la escritura de la iteración i = 27 (una dependencia verdadera).

Atención. El paso de los accesos a memoria se puede indicar en dos sitios: en la definición de los límites del bucle y en las propias instrucciones. Antes de aplicar el test MCD, es necesario normalizar el bucle, efectuando un cambio de variable que haga que el paso del bucle sea 1. Por ejemplo:

do i = 1, 100, 2 do k = 1, 50, 1 A(i) = ... A(2*k-1) = ... B(2*i+5) = ... B(4*k+3) = ... enddo enddo

Page 64: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 50 ▪ Capítulo 1: COMPUTADORES VECTORIALES

En resumen: el compilador vectorial analiza las dependencias entre instrucciones del bucle y genera el correspondiente grafo. Si no existen ciclos en dicho grafo, no habrá problemas para vectorizar el bucle; en caso contrario, se intentará aplicar algunas técnicas sencillas que permiten reducir o anular el impacto negativo de las dependencias y/o vectorizar parcialmente el bucle. Analicemos, por tanto, las principales técnicas de optimización.

1.5.3 Optimizaciones

El proceso de compilación es esencial en la obtención de altas velocidades de cálculo en un computador vectorial. No hay que olvidar que de no obtener un factor de vectorización elevado el rendimiento de la máquina será bastante bajo (ley de Amdahl). Acabamos de ver cuál es la condición que hay que cumplir para poder vectorizar un bucle: que no haya ciclos de dependencias. En todo caso, algunas de las dependencias que aparecen en los bucles no son intrínsecas a la operación que se realiza, sino que están relacionadas con la manera en que se indica dicha operación (por ejemplo, las antidependencias o las dependencias de salida). En esos casos, es posible efectuar pequeñas transformaciones del código original que facilitan la vectorización final. Vamos a ver dos tipos de optimizaciones: las que ayudan a que desaparezcan las dependencias, y las que ayudan a obtener una mayor velocidad de cálculo.

1.5.3.1 Sustitución global hacia adelante (global forward substitution)

Analicemos este bucle:

NP1 = L + 1 NP2 = L + 2 ... do i = 1, L 1: B(i) = A(NP1) + C(i) 2: A(i) = A(i) - 1 do j = 1, L 3: D(j,NP1) = D(j-1,NP2) * C(j) + 1 enddo enddo

¿Existe una antidependencia entre las instrucciones 1 y 2? ¿Hay una recurrencia en la instrucción 3?

Page 65: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 51 ▪

Las dos definiciones, NP1 y NP2, que se han hecho antes del bucle son un obstáculo para poder tomar una decisión. Por ello, antes que nada, el compilador deshará ambas definiciones en todo el programa, sustituyendo las variables por su valor original (una constante), y entonces hará el análisis de dependencias. Recuerda: si no puede analizar los índices de los vectores, el compilador debe asumir que sí existe la dependencia.

do i = 1, L 1: B(i) = A(L+1) + C(i) 2: A(i) = A(i) - 1 do j = 1, L 3: D(j,L+1) = D(j-1,L+2) * C(j) + 1 enddo enddo

Ahora la decisión es clara: no existe antidependencia entre 1 y 2, porque los índices de los vectores (i y L+1) nunca serán iguales; de la misma manera, no existe recurrencia en la instrucción 3, porque L+1 ≠ L+2.

Esta técnica se aplica para deshacer la definición de cualquier constante.

1.5.3.2 Eliminación de las variables de inducción

Analicemos este bucle:

j = 2 k = 2 do i = 1, L j = j + 5 R(k) = R(j) + 1 k = k + 3 enddo

¿Existe una recurrencia en el vector R? Tal como está escrito, el

compilador no sabe analizar la dependencia, porque desconoce los valores de las variables j y k. Sin embargo, un análisis sencillo de cómo se accede a los vectores nos indica que no existe tal dependencia. La evolución de las variables j y k con relación a i es la siguiente:

i = 1 2 3 4 5 ... j = 7 12 17 22 27 ... k = 2 5 8 11 14 ...

Page 66: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 52 ▪ Capítulo 1: COMPUTADORES VECTORIALES

Los valores que toman j y k forman una progresión aritmética, y no hay problema en redefinirlas de la siguiente manera:

j = 5 i + 2 y k = 3 i – 1

Las variables que forman una serie aritmética en función del índice del bucle se conocen como variables de inducción. Eliminando las variables de inducción, el bucle anterior puede escribirse así:

do i = 1, L R(3*i-1) = R(5*i+2) + 1 enddo

Ahora sí, un compilador vectorial puede analizar si existe una dependencia en R. Es bastante común encontrar variables auxiliares de este tipo en los bucles de cálculo, y, por tanto, el compilador tendrá que detectarlas y sustituirlas por las funciones correspondientes, para poder realizar el análisis de dependencias (y, en su caso, para poder vectorizar el bucle).

1.5.3.3 Antidependencias (DR, WAR)

Tal como ya hemos comentado, las antidependencias son dependencias “débiles”, y normalmente su efecto en la vectorización del código puede eliminarse con pequeñas transformaciones del código original.

Por ejemplo:

do i = 0, N-2 1: A(i) = B(i) + C(i) 2: D(i) = A(i) + A(i+1) enddo

Las dependencias del bucle forman un ciclo en el grafo de dependencias.

Por tanto, si no se hace algo, el bucle no se puede vectorizar. Pero entre las dependencias que forman el ciclo hay una antidependencia: la segunda instrucción debe leer el vector A(i+1), antes que la primera instrucción escriba A(i) (si no, leeríamos los valores nuevos, y no los viejos, que es lo que indica el programa). ¿Se puede hacer algo? Sí: leer primero el vector A(i+1). Basta para ello con escribir el bucle de la siguiente manera:

2

1

A, 0 A, 1

Page 67: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 53 ▪

do i = 0, N-2 0: [T(i)] = A(i+1) 1: A(i) = B(i) + C(i) 2: D(i) = A(i) + [T(i)] enddo

En la nueva versión, el grafo de dependencias del bucle no presenta ningún ciclo de dependencias, por lo que puede vectorizarse sin problemas. Normalmente no es necesario salvar en memoria el vector cuya lectura se ha adelantado, y basta con dejarlo en un registro, que se utilizará luego para ejecutar la instrucción correspondiente. Sólo si no tuviéramos un registro disponible llevaríamos el vector a memoria.

Así quedará el código vectorial:

MOVI VL,#N-1 MOVI VS,#1

(2/0) LV V1,A+1(R1) ; se aadelanta la lectura de A+1 (1) LV V2,B(R1) LV V3,C(R1) ADDV V4,V2,V3 SV A(R1),V4

(2) ADDV V5,V1,V4 ; se utiliza lo que se leyó antes (V1) SV D(R1),V5

1.5.3.4 Dependencias de salida (RR, WAW)

Un caso similar al anterior se puede producir con las dependencias de salida, como, por ejemplo, en este bucle:

do i = 0, N-3 1: A(i) = B(i) + C(i) 2: A(i+2) = A(i) * D(i) enddo

El grafo de dependencias presenta un ciclo, en el que toma parte una

dependencia de salida. Si no se efectúa alguna transformación, el bucle no es vectorizable. Para mantener el significado del bucle, la segunda instrucción tiene que efectuar la escritura antes que la primera; o, lo que es equivalente, hay que atrasar la escritura de la primera instrucción, así por ejemplo:

2

1 T, 0 A, 1

0

A, 0

1

2

A, 0 A,2

Page 68: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 54 ▪ Capítulo 1: COMPUTADORES VECTORIALES

do i = 0, N-3 1: [T(i)] = B(i) + C(i) 2: A(i+2) = [T(i)] * D(i) 3: A(i) = [T(i)] enddo

Como en el caso anterior, no suele ser necesario utilizar el vector auxiliar (T), y basta con dejar el resultado en un registro, para llevarlo más tarde a memoria. Así quedará el bucle vectorial:

MOVI VL,#N-2 MOVI VS,#1 (1) LV V1,B(R1) ; instruccción 1, salvo la escritura LV V2,C(R1) ADDV V3,V1,V2 (2) LV V4,D(R1) MULV V5,V3,V4 SV A+2(R1),V5 (1/3) SV A(R1),V3 ; escritura de la instrucción 1

1.5.3.5 Intercambio de bucles (loop-interchanging)

Los vectores de dos dimensiones (en general, de n dimensiones) pueden vectorizarse de más de una manera, según su definición (por filas, por columnas...). Para escoger una de ellas, hay que tener en cuenta las dependencias entre instrucciones. Por ejemplo:

do i = 0, N-1 do j = 1, N-1 A(i,j) = A(i,j-1) + 1 enddo enddo

Además del grafo de dependencias (sólo hay una instrucción en el bucle, por lo que de haber alguna dependencia será consigo misma), hemos dibujado las dependencias en el espacio de iteraciones, para ver cómo se

1

2

3

T, 0

A, 2

T, 0

j

i 1

A, (0, 1)

Page 69: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 55 ▪

reparten en el tiempo. De dicho grafo es fácil concluir que el código no puede vectorizarse tal como está escrito, es decir, por filas, ya que en la iteración j se necesitan los resultados de la iteración j–1. Pero igualmente se observa que no hay inconveniente en vectorizar la operación por columnas, de esta manera:

do j = 1, N-1 do i = 0, N-1 A(i,j) = A(i,j-1) + 1 enddo enddo

Basta con utilizar como vector las columnas de la matriz (s = N), es decir,

intercambiar el orden original de los bucles. El intercambio de bucles no puede aplicarse a cualquier bucle, ya que, por

supuesto, hay que respetar las dependencias entre instrucciones. Por ejemplo, no puede aplicarse en el siguiente ejemplo: no se puede procesar la matriz por columnas, puesto que en la columna j se necesitan los resultados de la columna j+1.

do i = 1, N-1 do j = 1, N-2 (1) A(i,j) = B(i-1,j+1) + 1 (2) B(i,j) = A(i,j-1) enddo enddo

Cuando se intercambia el orden de los bucles, se modifica el vector de

distancias de las dependencias. Por ejemplo, una dependencia de distancia (2, 1) se convierte en otra de distancia (1, 2). La regla que permite el intercambio es la siguiente: el primer elemento no cero del nuevo vector de distancias debe ser positivo.

Por ejemplo, para el ejemplo del grafo anterior:

- sin intercambiar los bucles - tras intercambiar los bucles d1 → (0, 1) d1 → (1, 0) no hay problemas d2 → (1, -1) d2 → (-1, 1) esto no es posible

Así pues, no se puede vectorizar por filas y no se puede intercambiar los bucles.

j

i

j

i 1

2

A, (0, 1)

B, (1, -1)

Page 70: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 56 ▪ Capítulo 1: COMPUTADORES VECTORIALES

En algunos casos, es necesario aplicar fisión e intercambio de bucles para poder vectorizar el bucle. Por ejemplo,

do i = 1, N-1 do j = 1, N-1 (1) A(i,j) = A(i-1,j) + 1 (2) B(i,j) = B(i,j-1) * 2 enddo enddo

De acuerdo a las dependencias que aparecen en el espacio de iteraciones,

el bucle no se puede vectorizar ni por filas ni por columnas. Pero, en este ejemplo, la dependencia por filas corresponde a una instrucción (2) y la de las columnas a otra (1), tal como vemos en el grafo de dependencias. El bucle lo dividiremos en dos; luego, la primera instrucción (vector A) la vectorizaremos por filas, y la segunda (vector B) por columnas, intercambiando los bucles.

Sea como sea, sólo se intercambian los bucles si con ello se facilita la vectorización del código o se mejora el rendimiento. Por ejemplo, en este caso:

do i = 0, 99 do j = 0, 9 A(i,j) = A(i,j) + 1 enddo enddo

No hay dependencias entre las iteraciones, y por tanto puede vectorizarse por filas, tal como está escrito, o por columnas, si se cambia el orden de los bucles. Si se hace por filas, se procesan 100 vectores de 10 elementos. Los vectores son pequeños, por lo que el rendimiento no será alto (es una función de N). Sin embargo, si se cambia el orden se procesarán 10 vectores de 100 elementos, con lo que se obtendrá una mayor velocidad de proceso.

1.5.3.6 Expansión escalar (scalar expansion)

Al escribir bucles es habitual utilizar variables auxiliares que facilitan la escritura del bucle. Por ejemplo:

do i = 0, N-1

1 A, (1, 0)

2 B, (0, 1)

j

i

A

B

Page 71: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 57 ▪

suma = A(i) + B(i) C(i) = suma * suma D(i) = suma * 2 enddo

Aunque procesamos vectores, utilizamos una variable escalar auxiliar, suma. Sin embargo, esa variable impide la vectorización del bucle, ya que genera dependencias entre todas las iteraciones. No se trata de una variable propia del bucle, sino de una simple variable auxiliar, así que ¿por qué no escribir el código de esta otra manera?

do i = 0, N-1 suma(i) = A(i) + B(i) C(i) = suma (i) * suma (i) D(i) = suma (i) * 2 enddo

Lo que antes era una variable escalar ahora es un vector completo: suma(i). Ya no hay ningún problema para vectorizar el bucle anterior. Esta técnica, convertir un escalar en un vector, se conoce con el nombre de expansión escalar.

Como en casos anteriores, no suele ser necesario guardar el vector auxiliar en memoria, sino que basta con utilizar los registros del procesador. En todo caso, no hay que olvidar que, al final del bucle, la variable original suma debe contener el valor correspondiente a la última iteración del bucle: suma = suma(N-1)

1.5.3.7 Fusión de bucles (loop fusion)

Con esta optimización se intenta fundir dos (o más) bucles en uno solo, para intentar reducir toda la sobrecarga asociada al control del bucle, y para, si es posible, reutilizar los resultados almacenados en los registros. Por ejemplo,

do i = 0, N-1 Z(i) = X(i) + Y(i) enddo do i = 0, N-1 R(i) = Z(i) + 1 enddo

do i = 0, N-1 Z(i) = X(i) + Y(i) R(i) = Z(i) + 1 enddo

Page 72: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 58 ▪ Capítulo 1: COMPUTADORES VECTORIALES

Los dos programas del ejemplo son idénticos, pero el segundo es más “sencillo” de ejecutar. Para empezar, el compilador puede aprovechar en la segunda instrucción las operaciones de la primera, leyendo el operando de un registro (no haremos SV Z y luego LV Z); además de ello, todo el código asociado con la ejecución del bucle sólo se ejecutará una vez (direccionamiento, longitud y paso de los vectores...).

De todas maneras, no es seguro que el compilador efectúe esta optimización automáticamente, puesto que para ello debería realizar el análisis de dependencias más allá del bloque básico.

En todo caso, claro está, no siempre es posible fundir dos bucles en uno, puesto que hay que respetar las dependencias de datos. Por ejemplo, estos dos programas no son iguales:

do i = 1, L Z(i) = X(i) + Y(i) enddo ≠ do i = 1, L R(i) = Z(i+1) + 1 enddo

do i = 1, L Z(i) = X(i) + Y(i) R(i) = Z(i+1) + 1 enddo

1.5.3.8 Colapso de bucles (loop collapsing)

Como ya sabemos, los bucles de varias dimensiones pueden vectorizarse de diferentes maneras: por filas, por columnas... Pero cuando el tamaño de los vectores es pequeño, puede ser interesante "juntar" dos (o más) bucles en uno. Por ejemplo:

float A(10,10)

do i = 0, 9 do j = 0, 9 A(i,j) = A(i,j) + 1 enddo enddo

El bucle puede ejecutarse vectorialmente sin problemas, pero los vectores (filas o columnas) son muy pequeños. Para aprovechar mejor el tamaño de los registros vectoriales, podemos transformar el bucle de la siguiente manera:

float A(10,10)

do i = 0, 99 A(i) = A(i) + 1 enddo

Page 73: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 59 ▪

Como sabemos, el espacio de memoria es lineal, y las filas de la matriz se almacenan una tras otra; por tanto, la matriz A[N,N] puede tratarse como si fuera el vector A[NxN].

1.5.3.9 Otras optimizaciones

Las técnicas de optimización que acabamos de ver son las más habituales, aunque existen otras. Sin embargo, más de una vez ocurre que lo que parece muy simple de vectorizar resulta muy complejo de hacer automáticamente (para el compilador). En esos casos, la ayuda del programador resulta el camino más sencillo. Esa ayuda suele efectuarse mediante pseudo-instrucciones para el compilador, al que se le indica qué trozos de código debe traducir a código vectorial sin preocuparse del análisis de dependencias. Veamos un ejemplo:

do i = a, b X(i) = Y(i) + X(i+M) enddo

Sin más información, el compilador no puede vectorizar el bucle, porque puede existir una recurrencia en X, en función del valor de M: si M ≥ 0, no hay problemas para vectorizar el bucle, pero si M < 0, el bucle no es vectorizable (M no es una constante; en caso contrario, el compilador la sustituiría por su valor). Sin embargo, puede ser que el usuario tenga información extra sobre la variable M. Por ejemplo, tal vez sabe que se trata de un parámetro físico que siempre es positivo (o que, por ejemplo, se acaba de ejecutar M = A(i) * A(i)). Si es así, bastaría con indicarle al compilador que vectorizara el bucle, sin más.

Por otro lado, el compilador podría también ejecutar el bucle de la siguiente manera:

if (M ≥ 0) then do i = a, b X(i) = Y(i) + X(i+M) enddo else do i = a, b X(i) = Y(i) + X(i+M) enddo endif

La primera parte (then) se ejecutará como código vectorial; la segunda, en cambio, escalarmente.

Page 74: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 60 ▪ Capítulo 1: COMPUTADORES VECTORIALES

1.5.4 Vectores de máscara y vectores de índices

Todas las operaciones vectoriales que hemos analizado hasta el momento han sido muy “simples”. Sin embargo, no siempre es ése el caso en los programas reales. Vamos a analizar dos casos muy habituales, que aparecen mucho en el cálculo científico: el uso de máscaras y los vectores de paso variable.

1.5.4.1 Uso de máscaras

En más de una ocasión, no hay que procesar todos los elementos de un vector, sino solamente algunos de ellos. Por ejemplo:

do i = 0, N-1 if (B(i) > 5) then A(i) = A(i) + 1 enddo

Con lo que hemos analizado hasta el momento, no sabríamos cómo ejecutar vectorialmente ese bucle, pero es un caso tan habitual que tiene una solución específica: el uso de un registro de máscara. El registro de máscara (VM, vector mask) es un registro vectorial booleano (1/0) especial, que guarda el resultado de una operación lógica sobre vectores. Todas la operaciones vectoriales toman en consideración el registro VM para decidir qué elementos del vector hay que procesar y cuáles no.

El procesador dispone de instrucciones específicas para trabajar con el registro de máscara. Por ejemplo:

SxxV V1,V2 Compara dos vectores, elemento a elemento, y deja los resultados (1/0) en el registro de máscara VM (xx = operación de comparación: EQ, NE, GT...).

SxxVS V1,F1 Igual, pero utilizando un escalar para la comparación.

CVM Clear vector mask, para inicializar la máscara.

POP R1,VM Cuenta el número de bits activados en el registro de máscara; deja el resultado en R1.

Usando esas instrucciones, podemos ejecutar vectorialmente el bucle anterior de la siguiente manera:

Page 75: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 61 ▪

MOVI VL,#N MOVI VS,#1 MOVI F1,#5

LV V1,B(R1) SGTVS V1,F1 ; Set Greater Than Vector/Scalar VM := V1~F1

LV V2,A(R1) ADDVI V3,V2,#1 SV A(R1),V3

CVM ; Clear Vector Mask

La instrucción SGTVS compara los elementos de V1 con el contenido de F1, y el resultado se deja en el registro VM. Las operaciones vectoriales siguientes sólo tendrán efecto en las posiciones de los vectores indicadas en el registro VM. La instrucción CVM inicializa nuevamente el registro VM (los valores concretos dependen del computador).

Ten en cuenta que el tiempo de ejecución no cambia; cuando se ejecuta ADDVS, únicamente se enmascaran las escrituras en el registro destino. También hay que estar atentos a las posibles excepciones que se generen en la unidad funcional correspondiente, puesto que se tratan todos los elementos. En otras máquinas en cambio, se enmascara tanto la operación en la unidad funcional como la escritura en el registro.

1.5.4.2 Vectores de índices

En muchas aplicaciones científicas se utilizan estructuras de datos muy grandes (por ejemplo, una matriz de 10.000 × 10.000 elementos). Sin embargo, tal vez sólo haya que procesar unos pocos elementos de esas estructuras. Un ejemplo podría ser el de la figura.

Aunque la matriz es muy grande, sólo se van a procesar los elementos

marcados. Con dichos elementos puede formarse un vector, pero tenemos un pequeño problema. Hasta el momento, el paso (distancia de un elemento al

s = 3

5

14

4

1 8

Page 76: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 62 ▪ Capítulo 1: COMPUTADORES VECTORIALES

siguiente) de los vectores que hemos utilizado ha sido constante. Sin embargo, el vector que definimos en la figura no tendría un paso constante. Por tanto, no podríamos aplicar el mecanismo normal de direccionamiento para acceder a un elemento a partir del anterior: sumar una constante. En general, el problema es el siguiente: ¿cómo procesar vectorialmente vectores cuyo paso no es constante? Necesitamos un nuevo modo de acceso a memoria (un nuevo modo de direccionamiento) para poder leer o escribir dicho vector.

El nuevo método de acceso se logra mediante el uso de vectores de índices. Un vector o registro de índices guarda las posiciones concretas de los elementos a los que queremos acceder.

Una operación vectorial de este estilo se suele dividir en tres fases:

1 Fase de agrupamiento (gather): se utiliza el registro de índices para leer los elementos que nos interesan —base + desplazamiento—, y se cargan en un registro vectorial.

2 Fase de ejecución: se ejecuta la operación indicada.

3. Fase de difusión (scatter): se llevan los resultados de la operación vectorial a memoria, a las posiciones correspondientes, utilizando nuevamente el registro de índices.

Para efectuar las operaciones de agrupamiento y difusión y, en general, para trabajar con índices, se pueden utilizar instrucciones tales como (por ejemplo):

LVI V1,A(V2) Lee de memoria los elementos A + V2(i), utilizando V2 como registro de índices.

SVI A(V2),V1 Escribe en memoria los elementos A + V2(i), utilizando V2 como registro de índices.

CVI V1,R1 Genera un vector de índices, con los valores 0, R1, 2R1, ..., (Lmax–1)R1.

Por ejemplo, analicemos este bucle:

do i = 0, M-1 A(i*i) = B(i*i) + 1 enddo

Page 77: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.5 TÉCNICAS DE COMPILACIÓN PARA GENERAR CÓDIGO VECTORIAL ▪ 63 ▪

Tal como está, no se puede vectorizar por el procedimiento habitual, puesto que los pasos de los vectores A y B no son constantes (0, 1, 4, 9...). Sin embargo, tenemos la posibilidad de ejecutarlo vectorialmente así:

MOVI VL,#M MOVI R1,#1 CVI V4,R1 ; 0, 1, 2, 3... MULV V5,V4,V4 ; registro de índices: i*i

LVI V1,B(V5) ; direccionamiento indexado ADDVI V2,V1,#1 SVI A(V5),V2

Para indicar los índices hemos utilizado el registro V5, en el que hemos cargado previamente los resultados de la función i*i. Después, hemos utilizado el modo de direccionamiento indexado (base + vector de índices) para acceder al vector.

El modo de direccionamiento “indexado” puede utilizarse también, por ejemplo, para ejecutar el bucle del apartado anterior —if (B(i)>5) then A(i) = A(i) + 1— de la siguiente manera:

MOVI VL,#N MOVI VS,#1

MOVI F1,#5 LV V1,B(R1) SGTVS V1,F1 ; generar máscara (VM)

MOVI R2,#1 ; create vector index: 0, 1, 2... teniendo en cuenta VM CVI V2,R2 ; p.e.: VM = 10011101 → V2 = 03457 POP R1,VM ; contar bits a 1 en el registro VM (5) MOV VL,R1 ; cargar el registro VL (número de elementos) CVM ; inicializar máscara

LVI V3,A(V2) ; utilizar V2 como registro de índices, ADDVI V4,V3,#1 ; y procesar solamente VL elementos SVI A(V2),V4

De este modo, en la última parte (LVI / SVI) no se leen y escriben todos

los elementos (como se haría con un LV o SV normal), sino solamente los que se tienen que procesar.

Page 78: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 64 ▪ Capítulo 1: COMPUTADORES VECTORIALES

1.6 RESUMEN

Un computador vectorial es una máquina específicamente diseñada para el procesamiento de vectores (o, visto de otra, manera para la ejecución de bucles “largos”), y está compuesta por dos secciones: la que procesa vectores y la que procesa escalares, tan importante como la primera (los programas reales incluirán ambos tipos de código, vectorial y escalar, por lo que es necesario que el computador ejecute código escalar eficientemente). El conjunto de instrucciones de estas máquinas incluye instrucciones vectoriales (LV, ADDV, SV...) que permiten la ejecución de una operación vectorial completa sobre todos los elementos de un vector con una sola instrucción. Las características arquitecturales básicas de un procesador vectorial son: el uso de registros vectoriales, el encadenamiento entre instrucciones, un gran ancho de banda con memoria (múltiples buses de acceso a memoria) y una memoria entrelazada en muchos módulos. Así, el modelo de ejecución lleva a que el tiempo de ejecución de los bucles pueda formularse como TV = ti + tv N (ti = tiempo de inicio; tv = tiempo necesario para procesar un elemento, 1 ciclo en el caso ideal), en lugar del modelo escalar tradicional, TE = te N.

Las medidas básicas de rendimiento de un procesador vectorial ejecutando un determinado programa son R∞ (velocidad de cálculo con vectores de longitud infinita) y N1/2 (tamaño mínimo de los vectores para conseguir al menos la mitad de la velocidad máxima). Tal como ocurre con otros modelos de proceso, la velocidad pico (peak performance) de un procesador vectorial no es un parámetro adecuado para medir el rendimiento de un sistema vectorial. El que los vectores que se procesen no sean muy grandes, hace que el tiempo de inicio del cálculo vectorial (start-up) sea un parámetro muy importante a considerar.

Sin embargo, hay varios factores que limitan el rendimiento. Por una parte, el hardware —el tamaño de los registros vectoriales, el número de buses a memoria, el número de unidades funcionales—. Pero el parámetro que más puede llegar a reducir el rendimiento de una máquina vectorial es el factor de vectorización, f: fracción de código que se ejecuta en modo vectorial.

Por ello, no es posible olvidar el papel que un buen compilador debe realizar en este tipo de máquinas. El compilador vectorial es el responsable de generar código vectorial a partir de un código escalar estándar, y debe lograr el factor de vectorización más alto posible. En caso contrario, y tal

Page 79: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.6 RESUMEN ▪ 65 ▪

como indica la ley de Amdahl, el rendimiento final del sistema será muy bajo. Como siempre, para facilitar la tarea del compilador y mejorar su rendimiento, la ayuda de un usuario experto es siempre importante. Algunas de las técnicas de vectorización son ya clásicas y las aplican todos los compiladores. Esas estrategias se basan en el análisis de las dependencias entre instrucciones, y son comunes a los compiladores que intentan paralelizar el código para ser ejecutado en sistemas con más de un procesador. Por ello, las volveremos a analizar en un tema posterior.

▪ Breve historia de los computadores vectoriales A lo largo de la (breve) historia de la computación, los computadores

vectoriales han estado siempre a la cabeza de las máquinas más rápidas en cálculo científico, aunque la evolución de los sistemas multiprocesador ha relegado a estos procesadores a un segundo lugar. Pioneros en el uso de tecnologías avanzadas (ECL) y aportando soluciones arquitecturales novedosas, han marcado, hasta hoy en día, la referencia de velocidad de cómputo.

Aunque el primer computador vectorial fue el CDC STAR-100 (1972), el computador que marcó la historia de este tipo de máquinas fue el Cray-1 (1975), en la que se utilizaron por vez primera los registros vectoriales y el encadenamiento. Junto a ello, tomando en consideración los resultados de la ley de Amdahl, utilizaba un procesador escalar de gran velocidad (el más rápido del momento). En todo caso, y por limitaciones tecnológicas del momento, sólo disponía de una unidad vectorial, (es decir, sólo podía ejecutar una instrucción LV o SV a la vez).

En 1981, la casa CDC pone en el mercado el CYBER 205, evolución natural del computador STAR: seguía manteniendo el modelo M/M, pero disponía de muchas unidades de memoria (de hecho, por lo menos se necesitan tres en un computador vectorial M/M). En ese computador se utilizaron por primera vez los vectores de índices para procesar matrices de baja densidad (sparse).

La siguiente máquina de CRAY fue el Cray X-MP; una evolución natural del computador anterior (reloj más rápido, más buses a memoria, posibilidad de utilizar más de un procesador). Pronto aparece en el mercado el Cray-2: 4 procesadores, 156 MB de memoria DRAM (palabras de 60 bits), reloj más rápido, pero latencias más altas (ciclos) en las unidades funcionales, sin encadenamiento, y un único bus; no era una gran alternativa, salvo por su gran memoria.

Page 80: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 66 ▪ Capítulo 1: COMPUTADORES VECTORIALES

En los 80 aparecen en el mercado los superminicomputadores, mucho más baratos que los anteriores. Entre ellos el C-1 y C-2 (2 procesadores) de Convex. El éxito de estas máquinas, además de en su precio, hay que buscarlo en su compilador, de una gran eficiencia.

Los computadores Japoneses entran en escena. Los computadores VP100 y VP200 de Fujitsu se comercializan en 1983, y un poco después aparecen los Hitachi S810 y NEC SX/2. En general, estas máquinas japonesas podían lograr velocidades pico muy altas, pero los tiempos de inicio de las operaciones vectoriales (start-up) eran muy altos, lo que los hacía muy sensibles al procesamiento de vectores no muy largos, logrando resultados en muchos casos peores que los del X-MP.

En 1988 aparece el Cray-Y-MP, una evolución del X-MP (8 procesadores, reloj más rápido). La casa Cray continúa adelante y ofrece el C90 —16 procesadores a 240 MHz (y 15 millones de dólares)— y, más tarde, el T90. Comercializa también el J90, una versión CMOS, más barata (1 millón $).

Más tarde, la casa Cray comercializó el computador vectorial Cray Inc. SV1(ex), sucesor del J90 y del T90, en el que se utiliza tecnología CMOS (y se abandona definitivamente la rápida y cara ECL, tal como hicieron en su día Fujitsu y NEC). Se trata de un multiprocesador de memoria compartida que, en su configuración máxima, utiliza 128 procesadores vectoriales, a 450 MHz y 1,8 Gflop/s. Otra característica a destacar es el uso de una cache común de 256 kB para escalares y vectores. En anteriores diseños de Cray no se utilizaba memoria cache, pero en este último la velocidad del sistema de memoria no es suficiente para mantener ocupado el procesador (efecto del tradicional gap entre la velocidad del procesador y la de la memoria).

Por su parte, Fujitsu ofreció el computador VPP5000, evolución natural del VPP700: reloj más rápido (300 MHz) y 16 vector pipes de tipo multiply-and-add. En teoría, por tanto, cada procesador es capaz de lograr 9,6 Gflop/s. El procesador escalar va a 1,2 Gflop/s. En su configuración mayor, se trata de un multicomputador de memoria distribuida de 128 procesadores, en el que la comunicación punto a punto se efectúa a 1,6 GB/s, utilizando como red de comunicación un full distributed crossbar.

Finalmente, otra máquina japonesa más: el NEC SX-5/16A, un multiprocesador vectorial de memoria distribuida. Sus características principales son: reloj a 313 MHz, 16 unidades vectoriales, 10 Gflop/s por procesador. La versión SX-6 de ese procesador fue la base del supercomputador Earth Simulator.

Page 81: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

1.6 RESUMEN ▪ 67 ▪

Todas las máquinas citadas han sido siempre las más rápidas del momento, pero también, con diferencia, las más caras. La evolución de los microprocesadores en los últimos años, junto con el uso del paralelismo, ha ido arrinconando a este tipo de arquitecturas, con lo que, en un futuro cercano, parece que jugarán un papel cada vez menor en el campo del cálculo científico. Para ello, habrá que aprender a programar y utilizar los sistemas de muchos procesadores de manera eficiente, para aprovechar su gran potencial de cálculo. En todo caso, es habitual que los procesadores (super)escalares actuales dispongan de instrucciones de tipo vectorial (SIMD) que, por ejemplo, dividen los 64 bits de una palabra en 8 palabras de 8 bits que son tratadas como un vector corto, y con las que se realizan operaciones tipo producto/suma encadenadas.

Hoy en día, los procesadores vectoriales aparecen como nodos especializados de un sistema paralelo más general. En ese tipo de sistemas, MPP (massive parallel processors) hay que buscar el futuro del cálculo paralelo: miles de procesadores colaboran en la resolución de un problema y se comunican entre ellos mediante una red de comunicación de gran velocidad. En dicha red, algunos procesadores están especializados en determinado tipo de cálculo, por ejemplo, cálculo vectorial.

Siempre es posible, en todo caso, encontrarse con sorpresas en la evolución de los computadores. En el top500 de junio de 2002 (lista de las 500 máquinas más rápidas del mundo, que se publica dos veces al año), se produjo un cambio significativo. En contra de la línea seguida en los últimos años, el número 1 de la lista fue un (multi)computador vectorial: Earth Simulator. Se trataba de un computador japonés de propósito específico con 5120 procesadores vectoriales. Utilizaba chips NEC SX-6, que contienen cada uno 8 procesadores vectoriales. Lograba una velocidad de Rmax = 36 Tflop/s (el segundo en dicha lista, junio 2002, el ASCI White, alcanzaba 7,2 TF/s, utilizando 8192 procesadores). En la lista citada (junio 2002) había 41 computadores vectoriales.

En 2009 disponemos de una nueva versión de dicha máquina (1280 procesadores NEC SX-9, de 350 millones de transistores, a 3,2 GHz) que ha logrado una velocidad de cálculo de 122,4 TF/s. Cada CPU dispone de una unidad superescalar (de 4 vías) y una unidad vectorial con las siguientes características: 72 registros vectoriales de 256 elementos y 8 conjuntos o pipes de unidades funcionales vectoriales (+, ×, /, lógicas, máscaras y LV/SV). Cada chip puede alcanzar los 102,4 GF/s.

Page 82: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 68 ▪ Capítulo 1: COMPUTADORES VECTORIALES

El nuevo Earth Simulator fue el número 22 de la lista top500 de junio de

2009, pero es ya la única máquina vectorial de la lista; parece, por tanto, que la arquitectura vectorial tiende a desaparecer.

En el último capítulo haremos un repaso de la situación de la lista top500

y de las principales máquinas, arquitecturas y tendencias que en ella aparecen.

Page 83: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 2 ▪

Computadores Paralelos (conceptos básicos)

2.1 INTRODUCCIÓN

Aunque los procesadores son cada vez más rápidos, existen numerosas aplicaciones para las que la velocidad de cálculo de un único procesador resulta insuficiente. La alternativa adecuada para esas aplicaciones es el uso de paralelismo. Con el término paralelismo se indica que la ejecución de un determinado programa se reparte entre muchos procesadores, que trabajan simultáneamente.

Pueden utilizarse diferentes niveles de paralelismo. Por ejemplo, se explota el paralelismo a nivel de instrucción (ILP) cuando se segmenta la ejecución de las instrucciones de un programa: en un momento dado, se están ejecutando muchas instrucciones a la vez, pero en fases de ejecución diferentes. También puede explotarse el paralelismo en los datos. El ejemplo con más éxito de esa alternativa son los computadores vectoriales que acabamos de analizar. En todos esos casos (y en otros similares, como VLIW), sólo existe un contador de programa o PC, es decir sólo se ejecuta

Page 84: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 70 ▪ Capítulo 2: COMPUTADORES PARALELOS (conceptos básicos)

un programa bajo una única unidad de control. En los próximos capítulos vamos a estudiar el paralelismo a nivel de programa, es decir, vamos a analizar cómo repartir la ejecución de un programa entre P procesadores. Si fabricar réplicas de un procesador es un proceso relativamente sencillo y barato, ¿por qué no utilizar P procesadores para intentar ejecutar un programa P veces más rápido?

Recordemos un momento la clasificación de Flynn de los computadores, que toma en cuenta el número de flujos de datos y de instrucciones:

▪ SISD: un solo flujo de datos y un solo flujo de instrucciones. Se trata del modelo de un solo procesador (superescalar, por ejemplo). El paralelismo se obtiene en el uso simultáneo de unidades funcionales debido a la segmentación de la ejecución de las instrucciones.

▪ SIMD: un solo flujo de instrucciones (un contador de programa), pero muchos flujos de datos. En función del uso de la memoria, pueden diferenciarse dos familias: los procesadores vectoriales (memoria compartida) y los procesadores en array de memoria distribuida.

▪ MIMD: múltiples flujos de datos y de instrucciones. Se trata del verdadero modelo de paralelismo, en el que existen muchos programas en ejecución simultanea. Éste es el tipo de máquina que vamos a analizar a partir de ahora.

Antes de ello, una pequeña precisión acerca del uso de P procesadores, puesto que podemos tener diferentes alternativas, en función del objetivo que busquemos:

▪ Redes de computadores (LAN, WAN...). P usuarios ejecutan cada uno de ellos un programa diferente, independientemente (tal vez, de vez en cuando, se produzca alguna transmisión de datos entre los usuarios). Cada programa se ejecuta según el modelo de un único procesador.

▪ Tolerancia a fallos. En función de la aplicación, existen diferentes maneras de hacer frente a los fallos del sistema. Por ejemplo, se repite la ejecución del mismo programa en P procesadores, para obtener un alto nivel de fiabilidad de los resultados (por ejemplo, en situaciones especiales en las que no podemos permitirnos un error), o para disponer de una máquina cuando falla otra (high reliability, en un banco, por ejemplo) En otros casos, un computador ofrece un determinado servicio y un segundo computador está a la espera del posible fallo del primero, y cuando lo detecta toma su función para que el servicio ofertado esté siempre disponible (high availability).

Page 85: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

2.2 COMPUTADORES DM-SIMD ▪ 71 ▪

▪ Se ejecuta el mismo programa, repetido en todos los procesadores, pero con datos diferentes; por ejemplo, para hacer múltiples simulaciones independientes de un proceso físico en menor tiempo (mejora del throughput). O, en los servidores, para poder atender a múltiples peticiones simultáneas (por ejemplo, en una base de datos).

• Para ejecutar un programa P veces más rápido (high performance). Éste es el tipo de aplicación que nos interesa. Comparado con los anteriores casos, la diferencia fundamental va a estar en la comunicación entre procesos, que va a ser mucho más intensiva y que habrá que efectuar en tiempos muy breves (microsegundos). Esta comunicación es una parte de la ejecución y se produce como consecuencia de ejecutar en paralelo. Existen diferentes arquitecturas de este tipo, en función del número de procesadores, el nivel de acoplamiento entre ellos (frecuencia de la comunicación), capacidad y complejidad de los procesadores, mecanismos de sincronización y control, tamaño de las tareas, etc.

En los próximos capítulos vamos a analizar las principales características y problemas de este nuevo modelo de ejecución. Pero antes de ello, vamos a definir los principales conceptos y terminología de esta área.

2.2 COMPUTADORES DM-SIMD

Acabamos de analizar los computadores vectoriales, máquinas SIMD de memoria compartida. Aunque no son nuestro objetivo, vamos a hacer un breve resumen de las características principales del otro tipo de arquitecturas SIMD, las de memoria distribuida (DM = distributed memory) o procesadores en array.

Como ya hemos comentado, los computadores SIMD explotan el paralelismo de datos: con una única instrucción (la misma en todos los procesadores en el caso de los arrays) se procesan múltiples datos.

Procesador de control

Computador front-end

Pr + M + I/O

Red de comunicación

Array de cálculo

Page 86: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 72 ▪ Capítulo 2: COMPUTADORES PARALELOS (conceptos básicos)

Las características principales son las siguientes:

- Procesadores: en general, se utilizan muchos procesadores muy sencillos, por ejemplo, 1024 procesadores de 1 bit. Así pues, procesadores baratos, pero no muy rápidos. En el caso de los procesadores serie, de 1 bit, se procesan datos de cualquier tamaño, siendo la latencia proporcional al tamaño de los mismos.

- Control: el control es centralizado. Todos los procesadores ejecutan la misma instrucción en el mismo momento (lock-step), sobre datos diferentes. Si es necesario, la ejecución puede controlarse mediante máscaras, que indican en qué procesadores sí y en cuáles no se debe ejecutar la instrucción actual.

Un procesador especial de control se encarga de repartir las instrucciones a los procesadores y de comunicarse con el computador central front-end, desde el que se controla todo el sistema. Como en el caso de los procesadores vectoriales, el código que no se pueda ejecutar en el array se ejecutará en serie en el procesador central (o en el de control).

Normalmente, las operaciones de entrada/salida se realizan en los procesadores del array, lo que resulta muy adecuado para procesar datos de manera intensiva.

- Estructura: los procesadores forman una matriz o array, de 2 o 3 dimensiones. Una red especial de comunicación facilita la comunicación entre los procesadores; las redes más habituales son las mallas, los toros, etc. En general, y de cara a mejorar la eficiencia del sistema, la red se suele dividir en diferentes planos o subredes: para datos, para control, etc.

- Aplicaciones: este tipo de estructura se adecua muy bien a un determinado tipo de aplicaciones; por ejemplo, procesamiento de señales y de imágenes, o cierto tipo de simulaciones (Montecarlo...). Aunque el espacio de memoria sea común, la eficiencia del sistema es mucho mayor si las comunicaciones son locales (con los vecinos), que es lo que ocurre en las aplicaciones que hemos citado.

La regularidad de las estructuras de datos que se procesan y el tipo de operaciones que se ejecutan hacen que los accesos a memoria se realicen de acuerdo a patrones conocidos, en muchos casos en forma de “permutaciones".

Page 87: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

2.3 COMPUTADORES MIMD ▪ 73 ▪

ILLIAC IV, Solomon, CM1, BSP, DAP, Quadrics Apemille, procesadores sistólicos... son algunas de las máquinas más conocidas que han utilizado este tipo de arquitectura. Aunque han tenido su importancia, los computadores SIMD únicamente han encontrado un hueco en el tipo de aplicaciones citadas, y hoy no tienen presencia alguna en el mercado.

Los sistemas paralelos actuales son de tipo MIMD; veamos, por tanto, las características principales de estos sistemas.

2.3 COMPUTADORES MIMD

Como ya hemos comentado, en un sistema MIMD las aplicaciones se reparten en múltiples procesos que se ejecutan en diferentes procesadores. Desde el punto de vista de la arquitectura del sistema, la primera cuestión a aclarar sería: ¿cómo se estructuran los P procesadores en un sistema único? La respuesta puede ser muy amplia, pero pueden identificarse dos grandes grupos de arquitecturas, de acuerdo al uso de la memoria: los sistemas de memoria compartida y los de memoria distribuida o privada.

2.3.1 Memoria compartida (shared memory)

En los sistemas paralelos de memoria compartida, todos los procesadores comparten la memoria global del sistema, es decir, todos los procesadores utilizan el mismo espacio de direccionamiento.

De esta manera, la comunicación entre procesos es relativamente sencilla, utilizando para ello variables compartidas en la memoria común. Para pasar un dato de un proceso a otro, basta con dejar el dato en una determinada posición de memoria, donde lo leerá el proceso destino.

P0

M0

Mm–1

P1

Pp–1

Procesadores (+ MC)

Red de comunicación

Memoria principal

sistema E/S

Page 88: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 74 ▪ Capítulo 2: COMPUTADORES PARALELOS (conceptos básicos)

Para conectar los procesadores y la memoria se utiliza una red de comunicación. La red más sencilla es el bus; se conoce perfectamente su funcionamiento y no es difícil de controlar. Sin embargo, tendremos un problema nuevo: si se conectan muchos procesadores al bus, es posible que éstos lleguen a saturarlo, y que, por tanto, los tiempos de acceso a memoria sean altos. No hay que olvidar que el bus es una red centralizada que se comparte en el tiempo, que no admite dos operaciones a la vez. También pueden utilizarse otro tipo de redes, que analizaremos más adelante. Para simplificar, vamos a suponer que la red de comunicación es un bus.

A este tipo de arquitectura se le conoce habitualmente con el nombre de multiprocesador, y también como SMP (symmetric multiprocessor), UMA (uniform memory access) o sistemas paralelos de alto grado de acoplamiento. Dada la red de comunicación, un bus, el número de procesadores de un sistema SMP es relativamente bajo, entre 2 y 32, por lo que el paralelismo que se puede conseguir es reducido.

2.3.2 Memoria privada o distribuida (distributed memory)

En este segundo modelo, como puede observarse en la figura siguiente, cada procesador dispone de su propia memoria privada. El espacio de direcciones no es común: todas las direcciones son locales y hacen referencia a la memoria propia del procesador. Por ello, la comunicación entre procesos no puede hacerse, como en el caso anterior, mediante posiciones comunes de memoria. Así, la comunicación se realiza mediante paso de mensajes, utilizando para ello la red de comunicación. Si Pi debe enviar datos a Pj, formará con ellos un mensaje y lo enviará a la red; los controladores de la red se encargarán de ir retransmitiendo el mensaje hasta que llegue a su destino.

Nodos: Procesador (+ MC) + Memoria principal + E/S + Contr. comunic.

Red de comunicación

P0

M

E/S

K

Pp–1

M

E/S

K

Page 89: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

2.3 COMPUTADORES MIMD ▪ 75 ▪

El objetivo de este modelo es conseguir paralelismo “masivo”, es decir, poder utilizar un número grande de procesadores. Por ello, no se utiliza un bus como de red de comunicación, sino redes tales como mallas y toros de 2 y 3 dimensiones, hipercubos, árboles, etc., que analizaremos más adelante.

A este tipo de arquitectura se le conoce como multicomputador (o también como sistema débilmente acoplado, MPP o Massively Parallel Processors...).

2.3.3 Memoria lógicamente compartida pero físicamente distribuida (distributed shared memory)

Existe una tercera alternativa, que corresponde a una mezcla de las dos anteriores. Cuando el espacio de memoria es común, la programación de aplicaciones suele resultar más sencilla, pero la memoria se convierte en un cuello de botella: se producen grandes atascos de tráfico, provocados por los procesadores del sistema, que tienen que acceder a la memoria común a través de una red tipo bus. Cuando la memoria es privada en cada procesador, este problema desaparece, pero la comunicación entre procesadores es más compleja y también lo son los modelos de programación.

Un análisis sencillo de los programas muestra que los procesadores no hacen un uso homogéneo de la memoria, es decir, no acceden con la misma probabilidad a cualquier posición de memoria; ello permite pensar en una alternativa mixta: compartir el espacio de memoria pero distribuirla físicamente entre los procesadores. La estructura que corresponde a este modelo mixto es la de la figura anterior, pero todos los procesadores tienen acceso a todos los bloques de memoria.

Tiene que quedar claro que estamos organizando la memoria principal de manera jerárquica: los accesos locales serán rápidos, pero los externos serán mucho más lentos, puesto que hay que salir a la red de comunicación. Esperamos, en todo caso, que el acceso a la memoria local sea mucho más frecuente que a la memoria “remota”, y que la red de comunicación se utilice principalmente para la comunicación entre procesos.

Esta última estructura es la que está obteniendo el mayor éxito y desarrollo en la actualidad, y habitualmente se conoce como NUMA (Non Uniform Memory Access) o también como MPP.

Page 90: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 76 ▪ Capítulo 2: COMPUTADORES PARALELOS (conceptos básicos)

2.3.4 Clusters, constellations... y otros

Las arquitecturas que hemos citado son las principales, y hacen referencia al uso de memoria por parte de los procesadores. Es muy habitual que encontremos todo tipo de mezclas entre ellas. Por ejemplo, en la mayoría de los supercomputadores actuales los nodos que forman el sistema, y que se conectan mediante una red de comunicación, no son simples procesadores, sino pequeños sistemas paralelos SMP con 4-8 procesadores conectados en un bus. Así, dentro de cada nodo la memoria es compartida, pero la de otros nodos es privada.

Por otra parte, y tratando de reducir el elevado coste de los supercomputadores de diseño específico, han aparecido en el mercado con fuerza los sistemas formados por hardware sencillo y barato: computadores de propósito general conectados entre sí mediante redes más o menos sencillas derivadas de las tecnologías de las redes de computadores. En general, a este tipo de sistemas se les denomina clusters. Así pues, para formar un cluster se necesita un conjunto de nodos de cómputo y una red de comunicación (junto con el software de gestión y programación adecuado). La eficiencia del cluster ejecutando código paralelo será función de ambos, nodos y red. En el caso más simple, los nodos son simples PCs y la red de comunicación es (Gigabit) Ethernet. Ese tipo de sistema se conoce como Beowulf; es la opción más barata, pero también la de menores prestaciones, aunque ofrece buenos resultados en aquellos casos en los que la comunicación entre procesos no es relevante.

Para conseguir clusters más eficientes, pueden usarse pequeños sistemas SMP como nodos de cálculo y redes de comunicación más sofisticadas (Myrinet, Infiniband, Quadrics…); cuando el número de procesadores de cada nodo del cluster es mucho mayor que el número de nodos, el sistema se conoce también con el nombre de constellation.

Todos los fabricantes ofrecen hoy en día diversos tipos de clusters en sus catálogos (custom clusters) como una alternativa interesante para conseguir máquinas de alto rendimiento a un coste “razonable”. Además, es relativamente sencillo montar un cluster de no muy alto rendimiento conectando unos cuantos PC entre sí (commodity clusters).

Sea cual sea la arquitectura del sistema paralelo, en todos ellos es necesario resolver una serie de problemas comunes para poder lograr un buen rendimiento. Analicemos brevemente los principales problemas a los que hay que hacer frente.

Page 91: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

2.4 ALGUNOS PROBLEMAS ▪ 77 ▪

2.4 ALGUNOS PROBLEMAS

En cualquiera de sus estructuras, un computador MIMD presenta numerosos problemas nuevos para resolver. Por ejemplo:

▪ Gestión del sistema: la máquina construida a partir de múltiples procesadores o, incluso, computadores autónomos, debe aparecer al usuario como un único sistema integrado. Van a ser necesarios para ello nuevos sistemas operativos específicos, mecanismos adecuados para la gestión distribuida de las tareas, nuevas herramientas de monitorización, controles de seguridad avanzados, etc. Son todas ellas cuestiones muy importantes, pero no las trataremos en este texto.

▪ Reparto de tareas. ¿Sabemos cómo repartir un programa secuencial entre P procesadores? En algunos casos será muy sencillo; por ejemplo, es muy fácil repartir entre N procesadores la ejecución del bucle do i = 1,N {A(i) = A(i) + 1}; cada uno ejecuta una iteración del bucle, cualquiera de ellas, ya que todas las iteraciones son independientes y por tanto da igual cómo se haga. Pero en los casos más generales puede que no sea sencillo sacar a la luz el paralelismo inherente a un determinado algoritmo. De hecho, en muchos casos va a ser necesario desarrollar nuevos algoritmos para resolver viejos problemas, que saquen partido de las posibilidades de la máquina paralela. En general, la programación paralela es más compleja que la programación secuencial o serie.

Junto a ello, es necesario mantener cierto equilibrio en el reparto de carga de trabajo a los procesadores (load balancing). Si repartimos la carga %80 - %20 entre dos procesadores, el sistema global no será en modo alguno dos veces más rápido, ya que la tarea más larga será la que marque el tiempo final de ejecución. El reparto de carga puede ser estático —en tiempo de compilación— o dinámico —en tiempo de ejecución—. El primero es más sencillo y no añade sobrecargas a la ejecución del programa, pero es más difícil mantener el equilibrio de la carga de trabajo. El segundo es más costoso en tiempo de ejecución, pero permite repartos más equilibrados.

▪ Coherencia de los datos. Cuando se utilizan variables compartidas se cargan copias de dichas variables en las caches de los procesadores. Cuando se modifica una de dichas copias, ¿cómo se enteran del nuevo valor de la variable el resto de procesos? es decir, ¿cómo se mantienen

Page 92: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 78 ▪ Capítulo 2: COMPUTADORES PARALELOS (conceptos básicos)

"coherentes" los datos compartidos? Como veremos en los próximos capítulos, la solución depende de la arquitectura del sistema.

▪ Comunicación. Cuando hablamos de paralelismo, la comunicación entre procesos es el tema principal. Y junto a ello, la red de comunicación (sobre todo en los sistemas DSM o MPP). En un sistema paralelo, el tiempo de ejecución de un programa puede modelarse como:

Tp = Tej + Tcom

donde Tej representa el tiempo de ejecución real y Tcom el de comunicación. El tiempo de ejecución se reduce (en teoría) con el número de procesadores, pero el de comunicación en cambio, crece. La siguiente figura muestra una simplificación de ese compartimiento.

Como se observa en la figura, no siempre es una buena solución

utilizar un número elevado de procesadores, ya que las necesidades de comunicación pueden echar por tierra cualquier otra ventaja. Es necesario por ello encontrar un punto de equilibrio.

Un tipo especial de comunicación es la sincronización. Un grupo de procesadores se sincroniza, por ejemplo, para esperar a que todos terminen una tarea antes de comenzar con la siguiente. Los procesos de sincronización pueden generar mucho tráfico en la red y momentos de gran congestión en el acceso a variables comunes. Analizaremos este problema un poco más adelante.

Considerando el reparto de tareas y la comunicación, suelen distinguirse diferentes tipos o niveles de paralelismo:

• paralelismo de grano fino (fine grain): las tareas que se reparten entre los procesadores son "pequeñas", y la comunicación entre ellas es muy frecuente, aunque no se intercambian mucha información.

Núm. procesadores

Tej Tcom

Tp

Page 93: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

2.5 RENDIMIENTO DEL SISTEMA PARALELO (leyes de Amdahl y Gustafson) ▪ 79 ▪

• paralelismo de grano grueso (coarse grain): las tareas que se reparten entre los procesadores son "grandes", y sólo se comunican entre ellas de vez en cuando, aunque en esos casos se intercambia gran cantidad de información.

2.5 RENDIMIENTO DEL SISTEMA PARALELO (leyes de Amdahl y Gustafson)

El coste de los sistemas paralelos es elevado, y por ello nuestro objetivo debe ser conseguir ir P veces más rápido cuando se utilizan P procesadores. Para comparar sistemas de un solo procesador y de P procesadores suelen utilizarse dos parámetros: el factor de aceleración (speed-up) y la eficiencia (efficiency).

El factor de aceleración mide cuántas veces más rápido se ha ejecutado un determinado programa, es decir:

fa = Ts / Tp

donde Ts es el tiempo de ejecución en serie y Tp en paralelo. Por su parte, la eficiencia se define como:

efic = fa / P (habitualmente en %)

es decir, el tanto por ciento que se consigue del máximo factor de aceleración posible.

En el mejor de los casos, tendremos que Tp = Ts / P; es decir, que el programa se ejecuta P veces más rápido usando P procesadores:

fa = Ts / (Ts / P) = P efic = fa / P = 1

Se trata, en todo caso, de la situación ideal que, debido a múltiples problemas —reparto no equilibrado de la carga, comunicación, sincronización...— es difícil de lograr13. En todo caso, aunque no logremos 13 En algunos casos, pueden conseguirse factores de aceleración superlineales, es decir, mayores que P.

En general, son debidos a otros factores, ya que, además de P procesadores, el sistema paralelo dispone de más memoria, más capacidad de entrada/salida, etc. Tal vez los datos/programas que no cabían en la memoria de un procesador, sí quepan ahora en todo el sistema, con lo que, como sabemos, se ahorrará tiempo.

Page 94: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 80 ▪ Capítulo 2: COMPUTADORES PARALELOS (conceptos básicos)

que el factor de aceleración sea P, deberíamos conseguir que creciera linealmente con el número de procesadores (o, lo que es equivalente, que la eficiencia fuera constante, independiente de P): si se duplica el número de procesadores, que se duplique también el factor de aceleración.

No todos los programas presentan esas características, ya que no podemos olvidarnos de la ley de Amdahl. Tal como ha ocurrido en el caso de los computadores vectoriales, los programas más habituales no pueden ejecutarse completamente en paralelo: siempre queda una parte del código que hay que ejecutar en serie (o en un número reducido de procesadores).

Como ejemplo, supongamos que una fracción del código, f, puede ejecutarse en P procesadores, mientras que el resto, 1–f, debe ejecutarse en un único procesador. En ese caso, el tiempo de ejecución debe escribirse así:

Tsp = f × Tp + (1–f) × Ts (en general, ∑=

=P

i

sisp i

TfT

1

)

Si no consideramos el tiempo de comunicación, y tomamos el mejor caso, Tp = Ts / P, entonces el speed-up o factor de aceleración será:

fa = Ts / Tp = P / [ P (1–f) + f ]

Por ejemplo, si P = 1024 y f = 0,98, entonces fa = 47,7, muy lejos del hipotético 1024. Como se muestra en la siguiente figura, el factor de aceleración se satura, con una asíntota de valor 1 / (1–f), y queda muy lejos del comportamiento lineal.

Page 95: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

2.5 RENDIMIENTO DEL SISTEMA PARALELO (leyes de Amdahl y Gustafson) ▪ 81 ▪

De acuerdo a la ley de Amdahl, el efecto de la parte de código que haya de ejecutarse en serie es muy grande cuando el número de procesadores es grande. Si se cumple en la realidad lo que pronostica dicha ley, va a ser muy difícil conseguir factores de aceleración (speed-up) altos. Como acabamos de ver en el ejemplo anterior, basta que un 2% del código tenga que ejecutarse en serie para que el factor de aceleración se reduzca de 1024 a 47 (a menos del 5%).

Sin embargo, se comprueba que en muchos casos se consiguen aceleraciones reales mucho mayores que las pronosticadas. ¿Dónde está el error? Cuando hemos planteado la ley de Amdahl hemos considerado la siguiente hipótesis: se utilizan P procesadores para hacer que un determinado algoritmo se ejecute más rápido. Pero en realidad, muchas veces lo que ocurre es que se utilizan P procesadores para ejecutar un problema de tamaño más grande en el mismo tiempo. Por ejemplo, se ejecutan más ciclos de simulación o se hacen análisis considerando una red de más puntos, etc. En resumen, se mantiene el tiempo de ejecución, no las dimensiones del problema.

Se ha podido comprobar experimentalmente que cuando se hace crecer el tamaño del problema (por ejemplo, se usan matrices más grandes) no suele crecer el tamaño del código que se debe ejecutar en serie (al menos no en la misma proporción). Esto es equivalente a decir que al crecer el tamaño del problema crece también f (no es un valor constante). Si es así, para calcular el factor de aceleración deberíamos comparar estas dos situaciones:

tamaño del problema constante tiempo de ejecución constante

(1–f) Ts f Ts

f Ts × P (1–f) Ts

(1–f) Ts f Ts (1–f) Ts f Ts / P

en paralelo

problema de mayor tamaño

1 procesador

P procesadores

trozo que hay que ejecutar en serie

trozo que se puede ejecutar en paralelo

en paralelo

Page 96: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 82 ▪ Capítulo 2: COMPUTADORES PARALELOS (conceptos básicos)

Por tanto, cuando el tiempo de ejecución se mantiene constante:

Ts = (1–f) Ts + f Ts P Tp = (1–f) Ts + f Ts = Ts → fa = Ts / Tp = (1–f) + f P

La expresión que acabamos de obtener para el factor de aceleración se conoce como ley de Gustafson, y es lineal con P, lo que asegura que se pueden conseguir factores de aceleración elevados. Por ejemplo, como en el caso anterior, si P = 1024 y f = 0,98, el factor de aceleración que se consigue resulta ser fa = 1003,5.

Como comparación, la siguiente figura muestra la evolución con P del factor de aceleración en su doble versión, para el caso f = 0,9.

En la realidad, y para un programa dado, el factor de aceleración concreto

estará en algún punto entre esos dos extremos.

En los siguientes capítulos vamos a analizar algunos de los problemas que hay que resolver para poder utilizar de manera eficiente un sistema paralelo MIMD; entre ellos, la coherencia de los datos (tanto en sistemas SMP como DSM), la sincronización, el modelo de consistencia, la red de comunicación, y las estrategias de paralelización de bucles. En el último capítulo presentaremos brevemente el mercado de sistemas paralelos de alta velocidad, algunas de las implementaciones de más éxito, así como una pequeña introducción a las herramientas más utilizadas para programar aplicaciones paralelas (OpenMP y MPI).

Page 97: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 3 ▪

Coherencia de los Datos en los Computadores SMP

3.1 PRESENTACIÓN DEL PROBLEMA Y REVISIÓN DE CONCEPTOS

La velocidad de ejecución de programas que puede alcanzar un procesador está íntimamente ligada a la estructura y funcionamiento del sistema de memoria. Desgraciadamente, la velocidad de respuesta de la memoria principal es significativamente menor que la del procesador, y la diferencia es cada vez mayor. Por eso, para poder obtener datos e instrucciones en el menor tiempo posible, la memoria de un computador se organiza en forma jerárquica: registros, cache interna, cache externa, memoria principal, (discos...). Cada uno de los niveles es un subconjunto del nivel superior. Los registros son los más rápidos y cercanos al procesador, pero su capacidad es pequeña (por ejemplo, 128 registros de 64 bits); en el extremo opuesto tenemos la memoria principal, de alta capacidad (ya con 2 o más GB) pero de tiempo de respuesta mucho mayor (p.e., 50 ns).

Page 98: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 84 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

El funcionamiento de la jerarquía de memoria se basa en el hecho de que el acceso a datos e instrucciones no es aleatorio. Así, podemos utilizar esa propiedad de los programas para reducir la latencia media de los accesos a memoria, si vamos llevando a la memoria más rápida los datos que prevemos va a necesitar el procesador. Ésa es por tanto la función de la memoria cache: tener preparados los datos (instrucciones) que "pronto" o con más "frecuencia" utiliza el procesador, ya que el tiempo de respuesta de la cache es del orden de 5 a 10 veces menor que el de la memoria principal. Así pues, se copian en la cache de datos del procesador algunos de los bloques14 de datos de la memoria principal; es decir, el procesador va a trabajar con copias de los datos.

El hecho de trabajar con copias presenta un nuevo problema en los multiprocesadores de memoria compartida: hay que asegurar que las posibles copias de los datos que estén en las caches del sistema sean todas iguales, es decir, que sean coherentes. De no asegurarse la coherencia de los datos, los procesos no podrán utilizar variables compartidas, ya que nunca estarán seguros de sus valores reales.

3.1.1 Coherencia de los datos en los sistemas de un solo procesador

El problema de coherencia no se presenta exclusivamente en los multiprocesadores, sino que también aparece en los sistemas con un solo procesador, ya que también en ese caso se utilizan copias de los datos: una en la memoria cache y otra en la memoria principal15. El problema sin embargo no es complicado de resolver, ya que ambas copias están bajo control del único procesador existente.

Cuando se quiere modificar una palabra de la cache, ¿qué hay que hacer con las dos copias que existen de dicho bloque? Ya conocemos las dos políticas de escritura habituales:

▪ Write-through (WT): se actualizan ambas copias, la de la cache y la de la memoria principal, con lo que el sistema se mantiene siempre

14 El bloque es la unidad de transferencia entre la memoria cache y la memoria principal. Se trata de un

conjunto de palabras consecutivas de memoria (por ejemplo, bloques de 64 bytes: 16 palabras de 32 bits, u 8 palabras de 64 bits), estando el tamaño del bloque directamente relacionado con el nivel de entrelazado de la memoria. El término inglés para bloque suele ser line.

15 En los procesadores actuales la memoria cache está dividida en dos o tres niveles, por lo que el número de copias de un determinado bloque de datos puede ser mayor.

Page 99: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.1 PRESENTACIÓN DEL PROBLEMA Y REVISIÓN DE CONCEPTOS ▪ 85 ▪

coherente. Ello implica que todas las escrituras se efectúan también en memoria principal, lo que requiere más tiempo.

▪ Write-back (WB): sólo se modifica la copia de la memoria cache, y se mantiene la memoria principal con el valor antiguo. El objetivo es reducir el número de accesos a memoria, y con ello el tráfico en el bus y la latencia de las operaciones. Es la estrategia que habitualmente usan los procesadores.

Por tanto, el sistema de datos no es coherente (ambas copias no son iguales), y en algunos momentos será necesario recuperar la coherencia, es decir, actualizar la memoria principal, normalmente al eliminar el bloque de la cache (por ejemplo, por reemplazo). Para gestionar los bloques de datos se utilizan algunos bits de control en el directorio de la cache, que indican el “estado” del bloque de datos. Es suficiente con dos bits: valid, para indicar que la información almacenada es útil; y dirty, para indicar que está modificada.

Aunque hemos dicho que es el procesador el único dispositivo que tiene capacidad de modificar una copia, lo que facilita mucho la gestión de las mismas, no es estrictamente cierto, ya que en las operaciones de entrada/salida, por DMA por ejemplo, es un controlador especial el que toma control del bus y de la operación de escritura. En esa operación se modificarán varios bloques de datos; ¿qué habría que hacer con las posibles copias de esos datos en la cache? En algunos casos el problema desaparece porque se declaran como no “cacheables” los bloques de datos de E/S (nunca se copian en cache, y todos los accesos se hacen en memoria principal); si no es así, será el sistema operativo el que tenga que tomar control de esa operación y mantener la coherencia (flush de la cache). En todo caso, las operaciones de E/S son de muy baja frecuencia en comparación con las operaciones del procesador sobre la cache.

3.1.2 Coherencia de los datos en los multiprocesa-dores de memoria compartida (SMP)

El problema de la coherencia es mucho más peliagudo en los multiprocesadores de memoria compartida. La comunicación entre procesos se realiza mediante el uso de variables compartidas. Cada procesador usará su propia cache local, en la que tendrá copia de dichas variables, por lo que las copias potenciales de un bloque de datos no serán 2 (las correspondientes

Page 100: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 86 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

a la jerarquía de memoria) sino P+1, siendo P el número de procesadores. Además, y aquí está el problema principal, cualquier procesador puede efectuar una modificación en dichos bloques, en su cache local. Tal como hemos visto, las políticas de escritura WT o WB permiten gestionar la coherencia de los datos en el caso de un procesador, pero ¿cómo hacer lo mismo con el resto de las memorias cache del multiprocesador? ¿cómo saber si otro procesador ha modificado el bloque en su cache, y por tanto ya no es válida nuestra copia? Por definición, el problema de coherencia sólo existe con los datos compartidos; con los datos privados el problema se resume al de un solo procesador.

Como hemos comentado antes, el problema desaparece si se decide no llevar a las caches las variables compartidas (por ejemplo, las que se utilizan para la comunicación entre procesos), dado que no se harán copias, pero dicha decisión puede tener un efecto severo en el rendimiento del sistema, ya que todos los accesos de los procesadores a dichas variables tendrán que hacerse en memoria principal: crecerá mucho el tráfico en el bus y, en consecuencia, debido a los conflictos en el acceso al bus, subirán los tiempos de respuesta. Algo de ello se muestra en el siguiente ejemplo.

La velocidad de transferencia del bus de un multiprocesador es 1 GB/s y el reloj es de 800 MHz. Los procesadores ejecutan una instrucción por ciclo, y el 2% de las mismas son operaciones de memoria, LD/ST, sobre variables compartidas. Los datos son de 8 bytes. ¿Cuántos procesadores pueden conectarse en el bus sin llegar a saturarlo si las s compartidas se dejan en la memoria principal?

En cada segundo hay que transferir: 800 106 ciclos × 0,02 instr. (LD/ST) × 8 bytes = 128 MB por procesador, considerando sólo los datos compartidos. Por tanto, 8 procesadores generarán un tráfico de 1024 MB/s para acceder a las variables compartidas, el máximo que admite el bus.

Ineludiblemente, necesitamos una estrategia que permita disponer de copias en las caches locales de los procesadores y que éstos las puedan modificar. Ya sabemos que el uso de las caches (de copias, por tanto) ofrece dos grandes ventajas: los tiempos de acceso a memoria son menores y se reduce el tráfico en el bus.

3.1.3 Falsa compartición

El problema de coherencia aparece con las variables compartidas. Las variables privadas sólo estarán, como mucho, en una cache, y no significan ningún problema nuevo.

Page 101: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.1 PRESENTACIÓN DEL PROBLEMA Y REVISIÓN DE CONCEPTOS ▪ 87 ▪

No hay que olvidar, sin embargo, que el control del contenido de la cache, y el de la coherencia en concreto, se hace por bloques, no palabra a palabra: se cargan bloques de datos, se borran bloques, se anulan bloque, etc. Por ello, es posible que un bloque de datos se encuentre en más de un procesador, aunque todas las variables del bloque sean privadas. Por ejemplo:

Bloque de datos de 4 palabras

X Y Z T

Aunque las variables son privadas están en el mismo bloque de datos, por

lo que el bloque será compartido y tomará parte en las operaciones de coherencia. Se dice que hay un problema de falsa compartición (false sharing). Para evitar este efecto es necesario distribuir los datos en memoria de manera adecuada y es útil que los bloques de datos no sean muy grandes.

3.1.4 Definición de la coherencia

Decimos que un sistema es coherente si al leer una variable se obtiene siempre como resultado el último dato que se escribió en dicha variable. En esta definición no muy formal, se introducen dos conceptos: la propia coherencia —qué valor se obtiene—, y la consistencia —cuándo se verá en la variable el valor que ha escrito otro procesador—. Ésta segunda cuestión la analizaremos un poco más adelante.

Se asegura la coherencia de un sistema de memoria si se cumplen las tres siguientes condiciones:

1. Desde el punto de vista de un solo procesador, el resultado de una lectura (LD) debe ser siempre el correspondiente a la última escritura efectuada por ese procesador en esa variable (siempre que ningún otro procesador haya modificado dicha variable). Es decir, hay que respetar el orden entre LD y ST (sobre la misma variable). Se trata de una condición que también hay que cumplir en el caso de los sistemas con un solo procesador.

Variables del procesador Pi

Variables del procesador Pj

Page 102: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 88 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

2. Considerando todos los procesadores, la operación Pi_rd_A debe devolver siempre lo escrito por la última operación Pj_wr_A, si es que ha pasado "suficiente tiempo" desde que se realizó. Ésta es, más o menos, la definición de coherencia: todos los procesadores tienen que conocer los cambios producidos en el resto.

3. Las escrituras (cambios) sobre una variable tienen que verse en el mismo orden en todos los procesadores.

Las estrategias y mecanismos que se han desarrollado para mantener la coherencia de los datos son diferentes en función de la arquitectura de la máquina. Como hemos visto en el capítulo anterior, tenemos dos opciones para los sistemas de memoria compartida: multiprocesadores SMP — de 2 a 16 procesadores conectados en un bus—; o computadores DSM —muchos procesadores conectados mediante una red de comunicación más sofisticada—. En el primer tipo de arquitecturas se utilizan protocolos de coherencia tipo snoopy, mientras que en el segundo se utilizan protocolos basados en directorios.

En este capítulo vamos a analizar los protocolos de coherencia más habituales en los sistemas SMP (y en el capítulo 7 analizaremos los directorios de coherencia).

3.2 PROTOCOLOS DE COHERENCIA SNOOPY

Como hemos comentado en el capítulo anterior, la memoria de los sistemas SMP está "concentrada" en un solo sitio, y los procesadores utilizan normalmente un bus16 para acceder a memoria. En este tipo de sistema, la coherencia de los datos se mantiene por hardware, por medio de un dispositivo que se conoce como snoopy (fisgón). Puesto que la memoria y los procesadores se conectan mediante un bus, una red centralizada, todas las operaciones con la memoria principal son “públicas”, es decir, que cualquier procesador puede ver lo que otros están haciendo (LD, ST) dado que también él está conectado al bus. La función del snoopy es justamente ésa: espiar en todo momento el bus para enterarse de las operaciones que realizan otros procesadores, y, en su caso, distribuir por el bus información de control. En 16 Vamos a utilizar el modelo más simple de bus, en el que sólo se procesa una petición de uso del bus y

no se admite otra hasta finalizar con la anterior. En general, los buses de los sistemas multiprocesador son más complejos.

Page 103: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 89 ▪

función de la información que obtenga, el snoopy decidirá qué hacer con los bloques de datos que tiene en la cache local.

Cuando se modifica un determinado bloque de datos en la cache, ¿qué hay que hacer con el resto de posibles copias del mismo en los otros procesadores? Tenemos dos alternativas:

▪ Invalidar todas las copias de ese bloque que existan en el resto de memorias cache, y dejar por tanto una única copia, la que se ha modificado.

▪ Actualizar todas las copias de ese bloque, enviando a través del bus el nuevo valor de la palabra modificada.

En la siguiente figura aparece un ejemplo de ambas alternativas.

Invalidación Actualización

En el primer caso, el procesador P1 va a modificar la variable A en su cache, de 4 a 3. Efectúa la escritura y envía una señal de control especial al bus, INV, para invalidar la copia de P2; como consecuencia de ello, sólo permanecerá en las caches la copia de P117. En el segundo caso en cambio, se distribuye a todos los procesadores el nuevo valor de la variable A, mediante una señal de control especial, BC –broadcast–, para que la actualicen en su cache. Se mantienen por tanto todas las copias.

Tanto en un caso como en el otro, la memoria principal se actualizará o no en función de la política de escritura que se utilice: en todas las escrituras si se usa WT, y sólo en algunas ocasiones si se utiliza WB.

Ya hemos comentado que la coherencia de los datos se mantiene por bloque, y para ello se añaden algunos bits de control a los bloques de datos en el directorio de la cache. Mediante esos bits se definen diferentes estados 17 Aunque en el ejemplo sólo aparece una palabra, un bloque contiene siempre varias palabras.

MC1 MC2

wr A,#3

MP

P1

P2

INV A

A = 4→3

A = 4

A = 4→3?

MC1 MC2

wr A,#3

MP

P1

P2

BC A,3

A = 4→3

A = 4→3

A = 4→3?

Page 104: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 90 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

para los bloques. Un autómata finito (el snoopy) se encargará en cada cache de ir modificando los estados de los bloques en función de las operaciones que se realicen, tanto desde el procesador local como desde el resto de los procesadores, sobre los mismos.

3.2.1 Estados de los bloques en la memoria cache y señales de control

Para mantener la coherencia de los datos en la cache se suelen utilizar cinco estados. No es necesario utilizarlos todos, y en muchos casos sólo se usan algunos de ellos, como vamos a ver. Los estados se definen de acuerdo a dos características: el número de copias de un bloque y si el bloque es o no coherente (igual) con la copia de memoria principal (los nombres de los estados pueden variar de máquina a máquina).

I Inválido (invalid) Un bloque está en estado I si la información que contiene no es válida;

es lo mismo que si no estuviera en la cache (un fallo de cache). Para indicar que un bloque no está en la cache, utilizaremos también

el símbolo (-). Por ejemplo, cuando se reemplaza un bloque no se anula, simplemente desaparece. En definitiva, ambos casos, I o (-), son completamente equivalentes.

E Exclusivo (exclusive, reserved) Un bloque está en estado E si se trata de la única copia en todas las

caches del multiprocesador y si además su contenido es el mismo que el del bloque en memoria principal, es decir, es coherente.

M Modificado (modified, dirty, exclusive rd/wr) Un bloque en estado M es la única copia existente en el

multiprocesador, pero no está actualizado en memoria principal: se ha escrito en la cache pero no en memoria principal (write-back).

S Compartido (shared, shared rd only) Existen (o pueden existir) múltiples copias de dicho bloque en el resto

de las caches del multiprocesador, y todas las copias son iguales entre sí y, normalmente, iguales con la copia de memoria principal (coherentes).

Page 105: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 91 ▪

O Propietario (owner, shared dirty) Existen (o pueden existir) múltiples copias de dicho bloque en el resto

de las caches del multiprocesador, pero, aunque entre ellas son iguales, el bloque no está actualizado en memoria principal. La copia en estado O será la encargada, en su momento, de actualizar la memoria principal y mantener así la coherencia (por ejemplo, al ser reemplazada). El resto de copias, si existen, se encuentran en estado S (atención, esas copias no son coherentes con memoria principal).

Para definir los estados del bloque basta con usar tres bits. Por ejemplo:

válido (valid)

modificado (dirty)

compartido (shared)

Estado

0 – – I 1 0 0 E 1 0 1 S 1 1 0 M 1 1 1 O

Como hemos comentado al principio, los dos primeros bits son los mismos que se utilizan en los sistemas de un solo procesador, por lo que, dfe momento, sólo se añade un bit más al directorio.

Una máquina de estados finitos en cada procesador, el snoopy, se encarga de mantener los estados de los bloques de datos en la cache de acuerdo a la definición anterior, para lo que hay que tomar en consideración las siguientes acciones:

1. Acciones del procesador local PR: processor read

Se lee una variable (en un bloque de datos). Si el bloque está en la cache (acierto), no hay que hacer nada; pero si no está (fallo) hay que generar una petición de lectura de ese bloque (BR, bus request).

PW: processor write Se escribe en una variable (un bloque de cache). En general, hay que avisar a los otros procesadores, para que actualicen el estado de dicho bloque en su cache: INV (invalidar) o BC (actualizar), en función del tipo de protocolo. Además, si ha sido un fallo, hay que pedir el bloque de datos correspondiente.

Page 106: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 92 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

2 Acciones que se observan en el bus (señales de control enviadas por los otros snoopy), como resultado de operaciones de otros procesadores. El número, nombre y tipo de las señales depende de la implementación del protocolo. En nuestro caso, usaremos las siguientes:

BR: bus read Un procesador quiere leer una palabra y se ha producido un fallo en su cache (no está). Tienen por tanto que conseguir el bloque correspondiente, y para ello se genera esta petición (BR en el bus de control, y la dirección en el bus de direcciones). Todos los snoopy locales tienen que considerar esta señal para adecuar el estado del bloque (si tienen una copia del mismo).

INV: invalidate [ en los protocolos de invalidación ] Se escribe una palabra en la cache y, por tanto, hay que eliminar todas las copias de dicho bloque. Se envía al bus de control la señal INV y al bus de direcciones la dirección del bloque a anular. Todos los snoopy tienen que responder adecuadamente a la señal, anulando, en su caso, la copia del bloque.

BC: broadcast [ en los protocolos de actualización ] Se escribe una palabra en la cache, por lo que hay que actualizar todas las copias de dicha variable. Se activa la señal BC en el bus de control, y se pone la dirección de la variable en el de direcciones y nuevo valor en el de datos). Todos los snoopy tienen que responder adecuadamente a la señal, actualizando, en su caso, la variable correspondiente.

En algunos casos hay que activar más de una señal de control; por ejemplo, en un fallo en escritura: hay que solicitar el bloque de datos (BR) y anular o actualizar el resto de copias (INV o BC). Las señales que acabamos de definir son simplemente una opción, y las implementaciones de las mismas pueden ser diferentes; por ejemplo, en lugar de activar dos señales de control a la vez, puede utilizarse una tercera señal que indique ambas acciones: RdEx (o BRinv), "lectura exclusiva".

3. Otras señales de control Las acciones anteriores tienen como consecuencia que se modifique el estado de los bloques en la cache. Además de ellas, también

Page 107: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 93 ▪

aparecerán en el bus las siguientes acciones, que no tienen efecto sobre el estado de los bloques:

BW: bus write Un procesador va escribir un bloque entero de datos en memoria principal. Esto va a ocurrir en los casos en los que la política de escritura sea write-back, cuando es necesario actualizar datos o en los reemplazos de bloques modificados.

BW*: Un procesador va a escribir una palabra en memoria principal (estamos usando por tanto WT). Esta señal de control no es estrictamente necesaria, ya que puede utilizarse para ello la señal INV (o BC), porque al escribir la memoria principal también hay que invalidar (o actualizar) el resto de copias (usaremos el * para indicar una transferencia de sólo una palabra).

Un protocolo de coherencia snoopy es un algoritmo distribuido en el que colaboran P autómatas finitos distribuidos en P procesadores. Utilizando los estados que acabamos de describir, permite trabajar con múltiples copias de un bloque de datos. El snoopy debe controlar las peticiones y avisos que le lleguen de su procesador local o del resto a través del bus, y, en función de ellas, decidir el estado de los bloques de datos y generar las señales de control adecuadas.

Pueden definirse muchos algoritmos de coherencia diferentes, utilizando algunos o todos los estados anteriores, y diferenciándose entre ellos por la política de escritura: write-through, write-back, o mezclas de ambos (en función del número de copias, del número de escrituras, de la jerarquía de cache, etc.). En muchos textos, el nombre de estos protocolos hace referencia a los estados que utilizan: MESI, MOSI, etc.

3.2.2 Protocolos de invalidación

Cuando se realiza una escritura en un bloque, y la coherencia se mantiene mediante un protocolo de invalidación, se eliminan todas las copias de ese bloque que haya en el sistema. Los protocolos más simples son de sólo dos estados (I-E o I-S) y utilizan como política de escritura WT, pero no son demasiado eficientes. Por ello, vamos a analizar protocolos de al menos tres estados y que utilizan WB como política de escritura siempre que es posible.

Page 108: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 94 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

3.2.2.1 Un protocolo de tres estados, MSI (Silicon Graphics)

Uno de los protocolos más comunes de tres estados es el que se ha utilizado en algunos de los computadores de Silicon Graphics. Los posibles estados de los bloques en cache son I, M y S; la política de escritura es, por tanto, write-back (se utiliza el estado M), aunque no se admite más de una copia de un bloque que no esté actualizado en la memoria principal.

Para definir un protocolo de coherencia (una autómata de estados finitos), hay que definir las transiciones entre los estados de los bloques de datos y las señales de control que se generan en dichas transiciones, en función, por un lado, de las acciones del procesador local (PR y PW), y, por otro, de las acciones del resto de procesadores, reflejadas en las señales de control que se detecten en el bus (BR e INV). Todo ello se refleja en la siguiente tabla:

Estado presente

Estado siguiente / Señales de control PR PW BR INV

fallo

I, - S BR M BR,INV

acie

rto S S M INV S I

M M M S BW I BW

Tráfico (datos) MP → MC: BR // I → S, M MC→ MP: BW // M → S, I (+reemplazo)

Los protocolos también se pueden representar mediante un grafo, tal como aparece en la siguiente figura.

PW (BR,INV)

PR (BR)

PW (INV) INV (BW)

BR (BW)

INV

PR - PW

PR - BR

M

S

I, -

Page 109: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 95 ▪

Las líneas continuas (letra en negrita) representan las transiciones generadas por acciones del procesador local, y las flechas discontinuas las que se producen como consecuencia de las señales de control que aparecen en el bus, debidas a lecturas y escrituras de otros procesadores. Entre paréntesis y en cursiva aparecen las señales de control que se envían al bus.

Cuando el procesador lee una variable que está en la cache (PR), el estado del bloque no se modifica ni se generan señales de control. En cambio, si la variable no está en la cache (I), hay que pedir el bloque de datos correspondiente, generando para ello la señal de control BR; una vez que obtengamos el bloque, se carga en la cache en estado S (no se puede poner en estado M porque no ha sido modificado).

En caso de escritura (PW), el estado del bloque pasará a ser M: una única copia y modificada (write-back). Si ya estaba en estado M no hay que hacer nada; pero si estaba en estado S hay que invalidar todas las posibles copias18 (mediante la señal de control INV). Si la escritura ha sido un fallo (estado I, la variable no está en la cache), antes de escribir se debe conseguir el bloque, en modo exclusivo, para lo que se generan las señales de control BR e INV (BR: leer el bloque + INV: invalidar todas las copias).

Veamos ahora las consecuencias de las operaciones realizadas por otros procesadores y que se detectan en el bus. Un procesador ha solicitado un bloque de datos, para lo que activado la señal BR. El bloque solicitado podría estar en la cache local, en estado S o M. Si está en estado S, no hay que hacer nada: a las copias que ya había antes, coherentes, se le añade una más. Pero si está en estado M, es decir, si la copia local es la única y no está actualizada, hay que modificar su estado. A partir de ahora habrá dos copias en el sistema, y la única opción en este protocolo es pasar al estado S, es decir, pasar a ser coherente: el nuevo estado es S y hay que actualizar (escribir) el bloque en la memoria principal (BW).

Finalmente, si se detecta la señal INV en el bus, la decisión es muy simple: si el bloque de datos está en la cache, hay que eliminarlo (I). Dado su efecto, la señal de invalidación INV tiene preferencia frente a la señal BR cuando ambas se activan a la vez. Como la política de escritura es write-

18 El estado S no implica que necesariamente tenga que haber más copias en el sistema; es decir,

aunque es seguro que en algún momento sí ha habido más de una copia, pueden haber sido reemplazadas todas ellas, quedando una sola copia, en estado S. Además, en este protocolo la primera copia también se carga en estado S, ya que no se utiliza el estado E (una sola copia).

Page 110: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 96 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

through, es posible que el bloque que hay que borrar esté en estado M, en cuyo caso habrá que actualizar su contenido en MP.

▪ Tráfico en el bus compartido

El tráfico que se genera en el bus que conecta los procesadores y la memoria en un multiprocesador es un aspecto crítico en el rendimiento del sistema. Dado que es un recurso compartido, el bus puede saturarse; en ese caso, la latencia de las comunicaciones con memoria crecerá, y la velocidad de cálculo bajará. Por ello, un protocolo de coherencia adecuado debe intentar reducir dicho tráfico, para que se pueda conectar el mayor número de procesadores al bus.

En la parte inferior de la tabla de transiciones de estados del protocolo se muestra el tráfico en el bus de datos. Hay que transferir un bloque de datos en estos dos casos: de memoria principal a memoria cache al generarse la señal BR (es decir, cuando un bloque pasa de estado I a S o M); y de memoria cache a memoria principal cuando se genera la señal BW (cuando un bloque en estado M pasa a estado S, se anula o se reemplaza).

▪ ¿De dónde se traen los bloques de datos?

Cuando hay que cargar un bloque en la cache, normalmente se traerá de MP. Sin embargo, en algunos casos ese bloque se puede traer de alguna otra cache (porque hay una copia del mismo). Esta posibilidad no disminuye el tráfico en el bus, pero sí el tiempo de acceso, porque traerlo desde otra cache va a ser más rápido. En cualquier caso, hacer esto genera una interferencia en el funcionamiento de otro procesador (mientras se está realizando la copia de cache a cache, no podrá utilizar su memoria cache), además de necesitar un arbitraje para escoger una determinada copia, por lo que habitualmente se trae el bloque de MP.

Si el bloque que se quiere traer está en estado M en otra cache, el snoopy tiene que conseguir esa copia, ya que la MP no está actualizada. Como en el caso anterior, tenemos dos opciones: actualizar primero la MP, y luego leer ahí el bloque; o copiar el bloque en la cache que lo necesita a la vez que se está actualizando la MP (por tanto, el bloque está en el bus):

(a) MC1 (M) → MP → MC2 o (b) MC1 (M) → MP → MC2

Page 111: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 97 ▪

La segunda opción es bastante más adecuada ya que reduce a la mitad el tráfico en el bus y la latencia de la operación.

En el caso de que quien solicita el bloque vaya a efectuar una escritura, se podría eliminar la escritura del bloque en MP, y efectuar únicamente la transferencia MC1 (M) → MC2 (M), ya que, después de todo, se va a cargar en la cache y se va a modificar.

3.2.2.2 El protocolo Illinois, MESI (Papamarcos&Patel, 84) (M88100)

Veamos otro conocido protocolo, Illinois, utilizado (con algunas modificaciones) en los procesadores Pentium, PowerPC y MIPS R4400. Es una mejora del protocolo anterior, al que se le añade un cuarto estado, E, con el objetivo de minimizar el número de invalidaciones.

El estado E nos asegura que en todo el sistema sólo hay una copia del bloque (recordemos que el estado S no distingue entre el número de copias que hay del bloque) y que, además, es coherente con la información que hay en la memoria principal. Para distinguir entre los estados E y S se introduce una nueva señal de control en el bus –sh (shared)–, que indica si un bloque concreto se encuentra en alguna otra cache o no (es decir, si se está cargando una copia única o ya había al menos una copia previamente en el sistema).

La tabla de transiciones correspondiente al autómata de coherencia es la siguiente:

Estado presente

Estado siguiente / Señales de control PR PW BR INV

fallo

I, - nsh: E sh: S

BR M BR,INV

acie

rto

E E M S I

S S M INV S I

M M M S BW I BW

Tráfico (datos) MP → MC: BR // I → E, S, M MC → MP: BW // M → S, I (+reemplazo)

Page 112: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 98 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Comparado con el caso anterior, la principal diferencia estriba en que un bloque que se lee (BR) se carga en la cache en estado E (coherente) si sabemos que es la única copia de dicho bloque en todo el sistema, es decir, si no está en ninguna otra cache; si no, se cargará en estado S. Para saber si hay copias o no, se utiliza la línea de control sh, de manera que cuando aparece en el bus la señal BR todos los snoopy mirarán en los directorios de sus caches para comprobar si tienen una copia de ese bloque o no, y, en caso afirmativo, activarán la señal sh. Por tanto, si sh = 1 existen copias del bloque (al menos una) en otras caches, y si sh = 0 (nsh, not shared), se está cargando en el sistema la primera copia de dicho bloque.

Cuando se carga en el sistema la segunda copia, ambas pasarán a estar en estado S, y a partir de ahí la evolución del bloque será la misma que la que hemos analizado en el protocolo anterior.

El objetivo del estado E es distinguir los bloques privados (siempre serán copias únicas) de los compartidos, y, así, reducir el tráfico en el bus. Si se hace una escritura sobre un bloque que está en estado E, el bloque pasará al estado M, sin generar tráfico (si hubiera estado en estado S, tendríamos que haber activado la señal de invalidación junto con la dirección del bloque). Es decir, no se envía la señal INV cuando no hay copias del bloque que se va a modificar. No hay que olvidar que la mayoría de los bloques de datos serán privados, y sólo algunos de ellos serán compartidos.

PW (BR,INV)

INV (BW)

INV

PW (INV)

BR (BW)

PW

PR

PR - PW

BR

PR - BR

I, -

S

M

E

PR (BR)

INV

sh nsh

Page 113: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 99 ▪

3.2.2.3 El protocolo Berkeley, MOSI

Como último ejemplo de protocolos de invalidación, analicemos el protocolo Berkeley; utiliza los estados I, M, S y O, y la política de escritura es write-back "siempre". Recuerda que el estado O (propietario, owner) se utiliza para poder tener múltiples copias de un bloque no coherente con memoria principal (en los dos protocolos anteriores sólo se permitía una copia no coherente, en estado M). Como no se diferencian los estados E y S, no se usa la señal sh. La tabla de transiciones y el grafo del protocolo son las siguientes:

Estado presente

Estado siguiente / Señales de control PR PW BR INV

fallo

I, - S BR M BR,INV

acie

rto

S S M INV S I

M M M O I BW

O O M INV O I BW

Tráfico (datos) MP / MC → MC: BR // I → S, M MC → MP: BW // M, O → I (+reemplazo)

Con el nuevo estado O es posible tener múltiples copias de un mismo bloque no coherentes con MP pero coherentes entre sí (esto permite utilizar una política de escritura write-back en todos los casos). En el protocolo anterior había que efectuar la transición M → S cuando era requerida una

PR - BR

I

O BR

PR - BR

PW (BR,INV)

INV (BW)

INV

PW (INV) INV (BW)

PR (BR)

PW (INV) PR - PW

M

S

Page 114: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 100 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

segunda copia de un bloque; ahora, en cambio, el cambio de estado será M → O, y no se actualizará la MP. Con esto se consigue no tener que actualizar un bloque en MP hasta que el bloque sea invalidado o reemplazado.

Cuando un bloque está en estado M en una cache, y se produce una lectura de ese bloque en otra cache, el primero pasará a estado O (propietario) y la copia nueva se cargará en estado S. ¡Cuidado! El estado S no implica que el bloque sea coherente con memoria principal. Si las copias de ese bloque están en estado S en todas las caches del multiprocesador, entonces serán coherentes con MP; pero si una de las copias está en estado O, entonces las copias serán coherentes entre sí, pero no lo serán con MP. Cuando una copia está en estado O y tiene que ser reemplazada, el snoopy correspondiente tiene que actualizar la MP; tras ello, el resto de copias de ese bloque, que estarán en estado S, retomarán la definición inicial de estado S (coherentes con MP).

En lo que al tráfico se refiere, no hay ningún cambio sustancial respecto al protocolo anterior. Cuando se tiene que cargar un bloque en estado S en una cache (fallo en lectura), hay que tener en cuenta dos posibilidades. Si el bloque no existe en ninguna otra cache, necesariamente habrá que traerlo de MP; pero si está en alguna otra cache en estado O o M, habrá que traerlo de esa cache, ya que la MP no está actualizada. El controlador de coherencia de esa copia deberá de responder adecuadamente a la petición, pasando los datos de la memoria cache al bus para que se puedan leer y cancelando la lectura que se había solicitado a la MP.

3.2.2.4 Resumen de los protocolos de invalidación

En los apartados anteriores hemos analizado algunos protocolos de coherencia de invalidación, en los que las copias de un bloque se invalidan cuando se modifica una de ellas. Entre ellos se diferencian por los estados que utilizan, la política de escritura, etc.

No hemos descrito todos los que existen, ni mucho menos. No hay problema, por ejemplo, para definir un protocolo de invalidación que utilice los cinco estados. Otro ejemplo bastante conocido es el protocolo write-once, en el que se utilizan ambas políticas de escritura (WT y WB) según los casos: WT cuando se escribe por primera vez en el bloque y WB para las sucesivas escrituras. También se pueden definir protocolos que tienen en cuenta la jerarquía de memoria a la hora de definir los estados (por ejemplo, el procesador Alpha). Todos ellos se dejan como ejercicio.

Page 115: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 101 ▪

3.2.3 Protocolos de actualización

Otros protocolos que se utilizan para gestionar las diferentes copias de un bloque que puede haber en un multiprocesador de memoria compartida se engloban dentro del grupo de protocolos de actualización. En los protocolos de invalidación, cuando una de las copias se va a modificar se elimina el resto de las copias. Ahora, en cambio, se van a mantener el resto de las copias, pero actualizadas. El control, snoopy, de la cache que va a hacer la escritura deberá informar al resto de las caches del cambio realizado, y éstas actualizarán el bloque con el nuevo valor de la variable.

Para poder actualizar una variable en el resto de las caches, se utiliza una señal de control denominada BC (broadcast), y junto con ella se pondrá en el bus la dirección de la variable y el nuevo dato. Los controladores de las caches procesarán esas señales y, cuando corresponda, realizarán los cambios asociados a la escritura, tanto en el valor de la variable como en el estado del bloque.

A pesar de que pudiera parecer que siempre obtendríamos mejores resultados con este tipo de protocolos, en realidad va a depender de la aplicación que se esté ejecutando. En los casos en los que un bloque de datos se reutilice sistemáticamente en los diferentes procesadores, la actualización será más eficiente, pues se mantiene la copia del bloque en las caches; en cambio, si el bloque no se va a reutilizar, quizás se esté actualizando ese bloque sin sacar ningún rendimiento a esas actualizaciones (hubiera sido mejor invalidar el bloque la primera vez). No hay que olvidar que para transmitir los datos de la actualización hay que utilizar el bus y, además, mientras se está actualizando una cache el procesador local no puede utilizarla y deberá esperar.

Los protocolos de actualización no invalidan los bloques y, por tanto, no utilizan el estado I. Sin embargo, es necesario utilizar el estado I para otras cuestiones, tales como, por ejemplo, para invalidar los bloques de datos en los cambios de contexto o en migraciones de procesos. Por ello, para representar el caso de que un bloque no esté en la cache utilizaremos (I, -). Veamos dos protocolos de actualización bastante conocidos.

3.2.3.1 El protocolo Firefly, MSE(I) (Archibald & Baer 85) (DEC)

Este protocolo de actualización utiliza los estados E, M y S. La política de escritura es write-back con los bloques privados y write-through con los

Page 116: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 102 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

compartidos. Esto es, cuando sólo hay una copia de un bloque, en las escrituras no se actualiza la memoria principal; en cambio, cuando hay varias copias del bloque, todas las escrituras actualizan también la memoria principal. Como el protocolo distingue entre los estados E y S, el bus de control cuenta con la señal sh (shared): sh = 1 → hay copias de dicho bloque en alguna otra cache; sh = 0 → no hay copias.

Las transiciones entre estados y las señales de control de este protocolo se muestran en la siguiente tabla y en el grafo correspondiente:

Estado presente

Estado siguiente / Señales de control PR PW BR BC

fallo

- nsh: E sh: S

BR nsh: M sh: S

BR BR,BC

acie

rto

E E M S S

S S nsh: E sh: S

BC S S

M M M S BW S BW

Tráfico (datos) MP / MC → MC: BR // (I) → E, S, M MC → MP: BW // M → S (+reemplazo) MC → MC*MP*: BC // (I) → S(wr); S → E, S(wr)

Cuando se va a cargar un bloque nuevo en una cache, el estado del bloque

va a depender de la señal sh. Si se detecta que no hay copias del bloque en el sistema (nsh), el estado será E (en las lecturas) o M (en las escrituras); a

M

E PR BR - BC

BR (BW)

PW (BC)

PR

PW

S

PR - PW

nsh

sh

BR

PR (BR)

PW (BR)

( - )

PW (BC) nsh

nsh

sh

( - ) sh

(BC)

Page 117: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.2 PROTOCOLOS DE COHERENCIA SNOOPY ▪ 103 ▪

ambos estados, que indican que sólo hay una copia del bloque de datos, se les aplica la política de escritura write-back. En cambio, si se detecta que hay una o más copias del bloque en el sistema (sh), el estado del nuevo bloque será S, y la posterior política de escritura será write-through, que mantiene coherentes la memoria principal y las memorias cache.

Del mismo modo, si se escribe sobre un bloque que está en estado S, se elegirá entre E o S en función de la señal sh. Ten en cuenta que aunque el bloque esté en estado S (compartido), puede ser que en ese momento sea la única copia si se han reemplazado las demás; aprovechamos así la escritura para actualizar el estado (aunque sólo quede una copia, el estado será E y no M, porque la política de escritura con las copias compartidas es siempre la misma: hay que actualizar la memoria principal).

Desde el punto de vista del tráfico, el caso más interesante es la transición (I, -) → S. Si es consecuencia de una lectura, entonces hay que traer el bloque a la cache, bien desde MP o bien desde otra cache. Si el resto de las copias son coherentes (E, S), normalmente se traerá de MP; si no son coherentes (M), entonces antes de traer el bloque (o a la vez) habrá que actualizar la memoria principal. Por otro lado, en las transiciones (I, -) → S, cuando son consecuencia de una escritura, además de traer el bloque hay que actualizar la memoria principal y todas las copias del mismo. Por tanto, cuando se genera la señal BC también hay que actualizar (una palabra) la memoria principal (es decir, cumple la misma función que la señal BW* que definimos anteriormente). Por ello, para reducir el tráfico de actualización, en el caso escritura/fallo antes de generar la señal BC se espera a obtener la respuesta de la señal sh; si no, podríamos genera la señal BC desde el comienzo de la operación.

3.2.3.2 El protocolo Dragon, MOES(I) (McCreight 84, Xeroc Parc Dragon)

En este protocolo se utilizan todos los estados: E, M, S y O (una variación de este protocolo se utiliza en las máquinas Sun Sparc/Server). Al igual que en el protocolo Berkeley, el estado O permite aplicar la política write-back en todos los casos. Se mantiene la señal sh (shared), para distinguir entre los estados E y S y así poder reducir el número de actualizaciones (BC).

En la siguiente tabla y su grafo correspondiente se presenta este protocolo.

Page 118: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 104 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Estado presente

Estado siguiente / Señales de control PR PW BR BC

fallo

- nsh: E sh: S BR nsh: M

sh: O BR

BR,BC

acie

rto

E E M S S

S S nsh: M sh: O

- BC S S

M M M O S

O O nsh: M sh: O

- BC O S

Tráfico (datos) MP / MC → MC: BR // (I) → E, S, M, O MC → MC*: BC // (I), S, O(wr) → O [ MC → MP: reempl. // M, O → (I) ]

Tal y como se muestra en la tabla, comparado con el caso Firefly hay

pocos cambios: se admiten varias copias de bloques sin actualizar entre diferentes copias, y por eso aparecen las transiciones M/S → O.

Recuerda que MC → MC* representa la transmisión de una palabra de una cache a otra; esto es, una operación de broadcast para actualizar las copias del bloque.

PW (BR)

BR

BC

PR BR - BC

BC

PW PW sh (BC) PR - BR

BR PR - PW

PW PW

PR

M O

E S

nsh

PR (BR) ( - )

nsh

sh

sh (BC)

( - ) nsh sh

(BC) nsh

Page 119: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.3 IMPLEMENTACIÓN DE LOS PROTOCOLOS SNOOPY ▪ 105 ▪

3.2.4 Resumen de los protocolos de tipo snoopy

En los párrafos anteriores hemos presentado los principales protocolos de tipo snoopy, tanto los de invalidación como los de actualización. A pesar de que al principio surgieron muchos protocolos diferentes, hoy en día los principales son los que hemos comentado (o variantes de los mismos). Además, debido a ciertos inconvenientes en la implementación, los protocolos de actualización casi no se utilizan. Por tanto, los más utilizados son los protocolos de invalidación de 3 o 4 estados, en los que se utiliza la política de escritura write-back.

3.3 IMPLEMENTACIÓN DE LOS PROTOCOLOS

SNOOPY

3.3.1 Problemas

Para mantener la coherencia de datos en un multiprocesador basado en un bus es suficiente un sistema de tipo snoopy. Los autómatas que hemos analizado (o variaciones de los mismos) son los que se utilizan en todos los multiprocesadores. La “lógica” que tienen que ejecutar los controladores de coherencia es bastante simple, tanto en el caso de invalidación como en el de actualización. Pero la implementación distribuida de esa lógica da lugar a nuevos problemas, que hacen que no sea inmediato conseguir dispositivos sencillos, eficientes y correctos. Por supuesto, el snoopy debe funcionar correctamente en cualquier situación, ya que de no ser así no podremos asegurar la coherencia de los datos y, por tanto, disponer de sistemas paralelos eficientes de memoria compartida.

Un snoopy es un autómata distribuido que se ejecuta en P procesadores. Esto es lo que produce problemas, ya que hay que coordinar el funcionamiento de todos los controladores para, al final, obtener el resultado correcto. Dentro de los problemas que aparecen, la falta de atomicidad es, probablemente, el más importante: la necesidad de asegurar que no se mezclarán, en el tiempo, operaciones de coherencia de dos (o más) procesadores sobre un mismo bloque, produciendo resultados incorrectos. Para mostrar los problemas y las soluciones, analicemos cómo se organiza un controlador de coherencia para un caso “real”. En la figura se muestra el esquema de un controlador de coherencia (simplificado). Analicemos sus componentes principales.

Page 120: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 106 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

3.3.1.1 Directorio de la memoria cache

Las operaciones que se realizan en las memorias cache de los multiprocesadores pueden provenir de dos orígenes diferentes. Por un lado, de las acciones del propio procesador local; y por otro, de las acciones que aparecen en el bus compartido. Por tanto, tendremos interferencias entre ambas fuentes. Por ejemplo, ¿qué se debe hacer cuando se ve una señal INV en el bus, si en ese instante el procesador está utilizando la memoria cache? (o viceversa).

Para conseguir un mejor rendimiento, normalmente el controlador de la cache se divide en dos partes: una analiza lo que está pasando por el bus (snoopy), y la otra procesa las peticiones del procesador. Ambas partes tienen que utilizar el directorio de la cache, y hacerlo con el menor número de interferencias posible: si el procesador está utilizando la cache, el snoopy se retrasará (y, como consecuencia, todas las transferencias de los demás procesadores); si es el snoopy el que está utilizando la cache, entonces será

Cache data RAM

tags + state

snoopy

Bus side

controller

compar.

Processor side

controller

P

compar.

Cmd

Addr

Addr

Cmd

tags + state

proc.

Write-back buffer

Data buffer

tag

state

system bus

to controller

data

to controller

addr contr

MC

Page 121: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.3 IMPLEMENTACIÓN DE LOS PROTOCOLOS SNOOPY ▪ 107 ▪

el procesador el que tendrá que esperar. Por esto, normalmente, el directorio de la cache suele estar duplicado (o se utiliza una memoria de doble puerto), y cada parte del controlador utiliza su correspondiente directorio. De este modo, las operaciones (búsquedas, por ejemplo) se pueden hacer en paralelo en los dos directorios. Eso sí, los dos se deben mantener coherentes, es decir, si se realiza un cambio en uno de ellos, se debe de realizar también en el otro (si existen colisiones al hacer esto, una de las operaciones se deberá retrasar). Por suerte, las modificaciones —escrituras— de los directorios son mucho menos frecuentes que las lecturas. Los datos, por supuesto, no se duplican: ocupan mucho espacio y los controladores los utilizan con frecuencia mucho más baja.

3.3.1.2 Búferes de escritura

Cuando la política de escritura es write-through, hay momentos en los que se debe de actualizar un bloque de datos completo en memoria principal, bien porque se invalide o se reemplace (M → I, -), o bien para mantener la coherencia (M → S). Por ejemplo, supongamos que se debe reemplazar un bloque que está en estado M. Antes de traer el nuevo bloque, hay que guardar el viejo en la memoria, y esto implica un tiempo durante el cual el procesador está parado. Una mejora bastante común es hacer lo siguiente: en lugar de efectuar las dos operaciones en este orden <actualizar (BW) / leer el bloque nuevo (BR)>, se efectúan en el orden contrario: primero traer el bloque nuevo y, después, actualizar el bloque viejo en MP. Para poder hacer las operaciones en este orden, primero hay que realizar una copia del bloque en estado M que se quiere sustituir (si no, se perdería esa información); esta copia se hace en el búfer de escritura. Una vez que el bloque nuevo ya está en la cache y el procesador en marcha, se actualizará la MP con el bloque que está cargado en el búfer de escritura, normalmente aprovechando ciclos libres del bus.

Esta mejora es común también en los sistemas de un solo procesador, pero en los multiprocesadores los búferes de escritura se deben de tratar con cuidado. Cuando un snoopy tiene que efectuar una búsqueda para saber si un determinado bloque está en la cache, además de en el directorio de la cache deberá buscar también en el/los búfer/es de escritura. Por tanto, el hardware de búsqueda, los comparadores, se debe duplicar: uno para el directorio de la cache y otro para cada búfer de escritura (figura anterior).

Page 122: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 108 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

3.3.1.3 Protocolo de petición de bus

Cuando un procesador pone una petición en el bus, por ejemplo BR, debe esperar a la respuesta de los demás snoopy, por si existe alguna copia de ese bloque en otra cache: ¿se debe traer el bloque de memoria, o hay que traerlo de otra cache, que lo ha modificado? En ese caso, ¿cuánto tiempo hay que esperar hasta estar seguro de que todos han respondido? Las estrategias más utilizadas para controlar el tiempo de espera son las siguientes:

• Esperar un tiempo fijo preestablecido, hasta estar seguros de que todos los snoopy ya han respondido. Por supuesto, es el caso peor, ya que la decisión se toma en el tiempo máximo (será el hardware del sistema el que determine ese tiempo), pero, a cambio, es el método más simple para implementar (Pentium quad/HP/SUN).

• Esperar un tiempo variable (un handshake). Para reducir el tiempo de espera, se detecta cuándo responde el último snoopy, y la decisión se toma en ese momento. Así, en la mayoría de los casos la decisión se toma antes del tiempo máximo, pero es complejo de implementar, ya que se deben detectar y controlar las respuestas de todos los dispositivos.

Tanto en el primer caso como en el segundo, una optimización típica consiste en que, mientras se está esperando, se comienza con la lectura de memoria; y luego, dependiendo del caso, se aborta el acceso a memoria (si es que todavía no había terminado) o se bloquea la respuesta de la memoria hasta que todos los snoopy respondan (SGI challenge).

• Añadir un bit más a todos los bloques de datos en memoria principal, para indicar si el bloque está en alguna cache o no. De esta manera, no hay que esperar a ninguna respuesta, ya que la conoceremos consultando ese bit. Esta solución es compleja, porque influye en todos los bloques de memoria principal, por lo que no se usa.

Para poder aplicar estas estrategias necesitamos ayuda del hardware, normalmente más señales en el bus de control. Por un lado, la señal sh, que ya hemos utilizado, para saber si existen o no copias de un bloque de datos. Del mismo modo, es conveniente tener otra señal similar, dirty, para indicar si el bloque está modificado en alguna cache. Por último, es interesante tener otra tercera señal, inh (inhibir), para poder abortar los accesos a memoria principal.

Page 123: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.3 IMPLEMENTACIÓN DE LOS PROTOCOLOS SNOOPY ▪ 109 ▪

3.3.1.4 Atomicidad: estado del controlador snoopy

Para terminar con el análisis del controlador presentado en la figura anterior, nos falta un detalle: el estado del controlador. Tal y como hemos comentado al principio, uno de los principales problemas que se da cuando tenemos P procesadores ejecutando a la vez es el de la falta de atomicidad de las operaciones. Se dice que una operación es atómica si se ejecuta toda la operación, desde el comienzo hasta terminar, sin ningún tipo de interferencia de ningún otro procesador.

El procedimiento para mantener la coherencia no es atómico por definición, ya que hay que realizar diversas operaciones y no se puede asegurar que no vaya a haber “interferencias” (no olvidar que tendremos muchos procesadores trabajando en paralelo). Dentro de ese conjunto de operaciones se encuentran las transferencias de datos por el bus. Vamos a suponer, por simplificar, que las operaciones del bus son atómicas; es decir, no se procesa otra petición hasta haber terminado con la anterior —no se segmenta19—. En los sistemas de un procesador, para trabajar con el bus se utilizan protocolos de comunicación similares a éste (por ejemplo, para una escritura):

procesador controlador del bus

petición-bus ... concesión-bus dirección, control ... recibido datos

De esta manera, el controlador del bus establece orden y prioridades en el uso del mismo. En los multiprocesadores el control del bus es más complicado, por un lado porque hay muchos procesadores conectados al bus, y por otro porque los controladores de las caches son más complicados, para poder hacer las funciones del snoopy. Además, aunque ayuda, el hecho de que el bus sea atómico no asegura que el protocolo de coherencia lo sea. Es por tanto el propio protocolo quien tiene que asegurar la atomicidad de las operaciones. Analicemos cómo se puede conseguir atomicidad en un caso concreto, el protocolo Illinois (MESI).

19 En los procesadores actuales esto no es así, ya que el uso de los buses está optimizado.

Page 124: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 110 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

3.3.2 El protocolo Illinois y la atomicidad

3.3.2.1 Carreras: estados transitorios, señales BRQ y BGN

El protocolo Illinois es un protocolo de invalidación de tipo MESI (lo que vamos a presentar se podría aplicar a los demás protocolos). Este protocolo utiliza la señal sh, para saber si los bloques están compartidos o no. Analicemos el siguiente caso. Dos procesadores comparten un bloque de datos, cuyas copias están en estado S. Los dos hacen una escritura a la vez en dicho bloque. ¿Cómo se resuelve el problema20? ¿Cómo asegurar que todas las operaciones que se deben hacer como consecuencia de esas escrituras se van a realizar del modo adecuado (incluso siendo el bus atómico)?

Por ejemplo, los procesadores P1 y P2 envían la señal INV al bus. Uno de ellos ganará el uso del bus (supongamos que es P1). Por tanto, el controlador de coherencia de P2, en lugar de dejar el bloque en estado S (para luego ponerlo en estado M), lo pondrá en estado I (si no, el bloque estaría en los estados M y S en dos caches simultáneamente). Pero después de hacer esto, la señal enviada al bus, INV, no será suficiente, ya que ahora debería enviar también la señal BR. La consecuencia que podemos extraer está clara: el controlador del snoopy no se puede quedar esperando, sin hacer nada más, a la respuesta a su petición; tal vez tenga que cambiar la petición realizada si entre tanto otro procesador ha querido hacer una operación sobre el mismo bloque de datos.

A este problema se le denomina "carrera" (race), y para solucionarlo, se suelen introducir más estados en el protocolo de coherencia, denominados estados "transitorios". Estos nuevos estados no están asociados a los bloques de la cache, sino al controlador de coherencia. Por tanto, no se introducen en el directorio (a nivel de bloque), sino que se guardan en un registro específico, en el mismo controlador (ver figura del controlador). Es decir, los posibles estados de un bloque son únicamente I, E, S y M. El significado de los estados transitorios es claro: algo se está haciendo, pero todavía no se ha terminado.

Tal y como hemos comentado, vamos a suponer que las operaciones en el bus son atómicas, y para ello vamos a introducir dos señales de control en el protocolo:

20 O, por ejemplo, el caso de dos escrituras simultáneas en fallo en dos procesadores. Los dos piden el

bloque y, si en ese momento nadie dice que lo tiene (sh = 0), los dos lo colocarán en estado M.

Page 125: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.3 IMPLEMENTACIÓN DE LOS PROTOCOLOS SNOOPY ▪ 111 ▪

- petición: BRQ (bus request) petición de utilización del bus - respuesta: BGR (bus grant) permiso para utilizar el bus

Para secuencializar operaciones que se quieren realizar simultáneamente, antes de utilizar el bus hay que efectuar una petición de uso (BRQ); cuando se dé permiso para utilizarlo (BGR), entonces se ejecutará el proceso correspondiente al protocolo de coherencia.

Analicemos el protocolo Illinois teniendo en cuenta todo lo anterior. Para implementar un protocolo MESI, son suficientes 3 estados transitorios: ISE, IM y SM. El grafo del protocolo es el de la figura, y las principales transiciones entre los estados son las siguientes:

• PR y fallo (I → S, E) En lugar de ir directamente a E o S, se pasa al estado transitorio ISE.

En la transición I → ISE se pide permiso para utilizar el bus (BRQ), y el controlador se mantendrá en ese estado hasta que se reciba el permiso (BGR). Cuando éste llegue, se pedirá el bloque (BR), y se cargará en la cache en el estado que corresponda, S o E, en función de la señal sh.

• PW y fallo (I → M) Antes de traer el bloque y modificarlo, hay que pedir permiso para

usar el bus (BRQ), y mientras tanto se pasa al estado IM. Cuando llegue el permiso, se pedirá el bloque y se anulará el resto de copias (BR, INV); finalmente, se cargará el bloque en la cache en estado M.

• PW y acierto (S → M) Al igual que en los casos anteriores, pasaremos a un estado transitorio,

a SM. Pero cuidado, el bloque estaba en estado S, y podría darse, a la vez, la misma transición en otra copia. Por tanto, mientras estamos en el estado transitorio SM, a la espera de poder utilizar el bus (para poder invalidar el resto de las copias), pueden suceder dos cosas:

- Llega la señal de aceptación BGR; por tanto, el bloque pasará a estado M, y se generará la señal INV.

- Se detecta la señal INV en el bus, lo que significa que otro snoopy se nos ha adelantado y quiere hacer una escritura sobre ese bloque. Debemos invalidar nuestra copia, por lo que el autómata pasará al

Page 126: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 112 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

estado IM, ya que ahora la escritura que queremos hacer partirá del estado I (es un fallo, por lo que hay que conseguir el bloque de datos: BR, INV).

• PW y acierto (M, E → M) En este caso no tendremos ningún problema; como nuestra copia es la

única, se escribe y se modifica el estado, si es que estaba en E.

3.3.2.2 Deadlock, livelock, starvation

Los problemas comentados hasta ahora no son los únicos que se dan cuando se implementan protocolos de este tipo. El interbloqueo es otro de los problemas típicos. En el campo de las comunicaciones, el interbloqueo está relacionado con la ocupación de los buses; en los protocolos de coherencia, en cambio, puede aparecer otro tipo de interbloqueo: el denominado fetch deadlock. Veamos un ejemplo.

El controlador de coherencia del procesador P1 está en un estado transitorio, esperando la respuesta del controlador del bus (y nada más). Mientras tanto, el controlador del procesador P2, que ha conseguido el bus,

INV (BW)

BR

INV

BR (BW) BGR (BR,INV)

INV

PW (BRQ)

INV

PR (BRQ)

BGR (INV)

PW (BRQ)

PR - PW

PR

PW

PR - BR

IM

ISE

SM

M

I, -

S

E

nsh sh

BGR (BR)

Page 127: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 113 ▪

ha ejecutado la operación BR; por desgracia, el bloque que él quiere lo tiene P1, y además, en estado M. Consecuencia: el procesador P1 no le enviará el bloque, porque está esperando la señal BGR, y el procesador P2 no cederá el bus, porque el bloque que necesita es el de P1. El sistema se ha bloqueado.

Por tanto, para evitar ese tipo de problema los autómatas de los snoopy no pueden dejar de espiar el bus ni en los estados transitorios. Si estando en un estado transitorio observa una situación como la descrita en el ejemplo anterior, deberá dar la respuesta adecuada.

Al igual que sucede en otros contextos (por ejemplo, en la comunicación entre procesos), además del interbloqueo existen otros problemas, entre ellos los denominados livelock y starvation. El problema de livelock indica que se ha llegado a una situación en la que los procesos no están bloqueados ("muertos / dead"), pero, sin embargo, son incapaces de avanzar. Por ejemplo, dos procesadores escriben a la vez sobre un mismo bloque que no tienen; los dos traerán el bloque e invalidarán el resto de las copias; en este caso se producirá livelock si la secuencia de acciones es la siguiente: rd1 – rd2 – INV1 – INV2 >> rd1 – rd2 – INV1 – INV2... Esto es, la operación no se va a terminar nunca. Por su parte, el problema de starvation suele aparecer ligado a cuestiones de prioridades: por ejemplo, un procesador nunca recibe respuesta a su petición del acceso al bus, porque siempre se le adelantan los demás. En el ejemplo de protocolo que acabamos de analizar estos dos problemas están resueltos.

En resumen, los protocolos de coherencia se deben de diseñar con mucho cuidado, para evitar todo ese tipo de problemas y para que funcionen bien y de manera eficiente en cualquier situación. Al tratarse de protocolos distribuidos entre P procesadores, cumplir con esas características puede resultar complejo.

3.4 SNOOPY JERÁRQUICO

Para llevar a cabo la comunicación entre los procesadores de un multiprocesador hemos utilizado un bus. Como ya sabemos, el número de procesadores que se pueden conectar en un bus es limitado, y éste es el principal inconveniente de la utilización de un bus como red de comunicación. Pronto analizaremos más formas (redes) de conectar los procesadores, pero en este momento vamos a analizar otra red de interconexión, que es una evolución natural del bus: la jerarquía de buses.

Page 128: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 114 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Un bus jerárquico es un árbol de buses, en el que en las hojas están los multiprocesadores (unidos mediante un bus) y en los demás "nodos" no hay más que buses y controladores. Estos últimos se encargan de gestionar la información por toda la red. Como vemos en la siguiente figura, se organiza un tipo de cluster en el que los nodos son pequeños sistemas SMP.

Supongamos que tenemos una jerarquía de dos niveles, en la que el bus del segundo nivel se utiliza para conectar N multiprocesadores (cada uno con P procesadores en un bus). Aunque la memoria es compartida, lo más apropiado es distribuir físicamente la memoria, y de esta manera se llega a un sistema NUMA (non-uniform memory access): el tiempo de acceso es diferente en función de dónde esté situada la posición de memoria a la que se quiere acceder; no es por tanto, un sistema SMP. Dentro de cada multiprocesador SMP, se utiliza un protocolo snoopy para mantener la coherencia. Pero, ¿cómo mantener la coherencia en todo el sistema?

Tal y como veremos más adelante, la solución que se utiliza en los sistemas que no utilizan una red de interconexión centralizada (un bus o similar) son los directorios de coherencia. Cuando se utiliza una jerarquía de buses, se utilizan unos controladores snoopy especiales que hacen la función de directorios, espiando y conectando dos niveles de bus, y decidiendo si hay que pasar la información de un nivel al otro o no.

Estos "monitores" especiales para la coherencia deben de espiar dos tipos de operaciones: por un lado, las operaciones que se realizan sobre bloques de su memoria principal local que han sido copiados en una cache remota; y por otro lado, las que se hacen sobre bloques remotos que han sido traídos a las caches locales. Por supuesto, la información que se queda dentro de un nodo concreto (MP y cache) no afecta a los demás nodos, y será el snoopy local el que se encargue de mantener la coherencia.

P

C

hardware para la coherencia global

MP MP K K

snoopy local

SMP

B1

B2

Page 129: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.4 SNOOPY JERÁRQUICO ▪ 115 ▪

Vamos a dividir el monitor de coherencia o directorio en dos partes:

• KL: controlador de coherencia que guarda información referente a los bloques locales que se encuentran copiados en memorias cache remotas (solamente los estados, no los datos, ya que el número de bloques que pueden estar "fuera" puede ser muy grande).

• KR: controlador de coherencia que guarda información de los bloques remotos que se encuentran en las caches locales (una "cache" que guarda datos y estados, aunque con los estados sería suficiente; si están los datos, se reduce el tráfico en el bus, pero se aumenta la necesidad de memoria).

¿Cómo funciona este hardware para mantener la coherencia? Veamos algunos ejemplos.

3.4.1 Lecturas (fallo)

En una cache se produce un fallo en lectura. Por tanto, se genera la señal BR en el bus B1. Existen dos posibilidades:

1. La referencia pertenece al espacio de direccionamiento local

• No hay copias fuera del nodo (por tanto, KL no responde): es una operación común y se resuelve dentro del mismo nodo (mediante el snoopy local).

• Existe una copia de ese bloque fuera del nodo (por tanto, KL responde): - En estado S: no hay problema, se toma el bloque de su MP (o de

otra MC local). - En estado E: es una situación similar a la anterior, pero el

controlador KL tiene que avisar al otro nodo (al que tiene una copia del bloque), utilizando el bus B2, para que ponga el bloque en estado S (mensajes (a) y (b) de la figura).

- En estado M: ¡cuidado! se debe pedir el dato fuera del nodo. La petición se pondrá en el bus B2. Cuando el controlador KR del nodo que tiene la copia del bloque detecte la petición realizará las siguientes acciones: (i) avisará a la cache local utilizando el bus B1, para que pase el bloque de estado M a estado S; y (ii) enviará

Page 130: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 116 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

el bloque a quien lo solicitó (si tiene los datos en KR, desde ahí mismo; si no, buscará en qué cache local se encuentra el bloque).

Por último, el controlador KL que ha generado la petición tomará el bloque de datos del bus B2, y lo pondrá en el bus B1, para cargarlo en la memoria cache que corresponda y actualizar la memoria principal (en la figura: 1, 2, 3, 4 y 5).

2. La referencia es del espacio de direccionamiento remoto.

• KR no responde. Por tanto, no está en alguna otra cache del nodo. La petición se pasa al bus B2. El controlador KL correspondiente a esa dirección detectará la petición y la pondrá en el bus local. La respuesta (el bloque) llegará de la MP o de alguna de las caches locales de ese nodo, y se pasará el bus B2. Junto a ello, se actualizará la información de los controladores KL y KR.

• KR responde. El bloque está en alguna de las caches locales y se tomará de ahí (si está en estado S, no hay que hacer nada; si está en estado E, se debe poner en estado S y hay que avisar al controlador KL; si está en estado M, además de lo anterior habrá que actualizar la MP).

3.4.2 Escrituras

Veamos un ejemplo concreto. El procesador P0 del nodo N1 quiere ejecutar una operación de escritura ST A en un bloque que está en estado S.

MP

MC

MP

KL KR KL

B1 B1

B2

MC MC MC

a

b

M→S

M→S

E→S

E→S

E→S

M→S

rd, fallo

BR @

KR 5

3

2

4 1

I→S

@ →

Page 131: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

3.4 SNOOPY JERÁRQUICO ▪ 117 ▪

La variable A pertenece al espacio de direccionamiento del nodo N3, y hay una copia de dicho bloque en el nodo N2 en estado S. La operación se desarrollará de la siguiente manera:

1. Se pone el bloque en estado M y se genera una señal de invalidación (INV) en el bus B1.

2. El controlador KR del nodo N1 ve que es una referencia remota; por tanto, pasa la señal INV al bus B2.

3. El controlador KR de N2 invalida su copia y pasa la señal INV al bus B1 (con lo que se invalidarán todas las copias de ese bloque que haya en ese nodo).

3´. El controlador KL de N3 modifica el estado del bloque, de S a M.

En general, cuando la memoria es compartida pero está físicamente distribuida no es sencillo mantener la coherencia de los datos. Los controladores de coherencia son dispositivos complejos y de gran tamaño, y, lo que es peor, la latencia de las operaciones de coherencia puede llegar a ser muy elevada, sobre todo si tenemos que acceder a datos fuera del nodo local. Y no podemos olvidar que hay que mantener la atomicidad de las operaciones de coherencia.

Lo anterior ha sido simplemente un ejemplo. Normalmente, en lugar de utilizar jerarquías de buses se utilizan otro tipo de redes (por ejemplo, mallas), en las que no se pueden utilizar estrategias de tipo snoopy para mantener la coherencia. Por tanto, deberemos buscar otro tipo de solución al problema de la coherencia: el directorio, tal como veremos en el capítulo 7.

MP

MC

KL KR

MP

KL KR

MP

KL KR

S→M

1

2

3

MC MC MC MC MC

A

N1 N2 N3

B1 B1

B2

B1

S→I S→I S→I

S→I S→M

INV A

INV A

INV A INV A

INV A INV A

S→M

wr A

Page 132: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 133: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 4 ▪

Sincronización de Procesos en los Computadores SMP

4.1 INTRODUCCIÓN

En una máquina MIMD, la ejecución de los programas se divide en P procesos o hilos, que se ejecutan en paralelo en los procesadores del sistema. En general, la ejecución de esos procesos no es completamente independiente, sino que se comunican entre ellos, bien sea para pasarse datos o para sincronizar su ejecución. En este capítulo vamos a analizar las necesidades de sincronización entre procesos que se ejecutan en paralelo en una máquina SMP de P procesadores.

Page 134: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 120 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Para presentar el problema de la sincronización entre procesos podemos utilizar un ejemplo muy sencillo. Supongamos que se va a ejecutar este código, en paralelo, en dos procesadores, P1 y P2 (inicialmente, A = 0)21:

P1

... ST A,F1 ... ...

P2

...

...

... LD F4,A

¿Qué valor leerá el procesador P2 en la variable A? Se trata de una variable compartida, que se utiliza en ambos procesos, y por tanto se debe mantener la coherencia (mediante un snoopy), lo cual implica que los cambios que efectúe P1 terminarán apareciendo en P2; sin embargo, no sabemos cuándo ocurrirá eso.

En todo caso, el significado del programa anterior es confuso. ¿Existe una dependencia de datos (RAW) entre P1 y P2 en la variable A? Si es así, se debería indicar de alguna manera que P2 debe leer A después de que la haya modificado P1, y no antes. Algo similar debería ocurrir si existiera una antidependencia en A, para que P2 leyera A antes de que la modificara P1. En otras palabras, se necesita sincronizar el uso de la variable A para que el programa anterior tenga un sentido “lógico”. En general, en estos casos se utiliza la sincronización por eventos, para avisar a un proceso (consumidor) que se ha generado un dato en otro proceso (productor).

La necesidad de sincronización no se reduce a casos como el anterior. Veamos otro ejemplo. Dos procesos comparten una variable, CONT, que hace las veces de contador. Ambos procesos incrementan el valor de dicho contador: CONT := CONT + 1.

P1

... LD R1,CONT ADDI R1,R1,#1 ST CONT,R1 ...

P2

... LD R1,CONT ADDI R1,R1,#1 ST CONT,R1 ...

¿Qué valor tendrá la variable CONT tras ejecutar el código anterior en ambos procesadores? Aunque no existan problemas de coherencia, el 21 Para simplificar el código, en los ejemplos de este capítulo utilizaremos el modo de direccionamiento

absoluto. Como es habitual, el contenido del registro R0 es siempre 0.

Page 135: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.1 INTRODUCCIÓN ▪ 121 ▪

resultado no está claro. Por ejemplo, ambos procesos ejecutan “a la vez” el código citado, siendo CONT = 0, pero las instrucciones en cada procesador se intercalan en el tiempo de la siguiente manera:

LD (P1) - ADDI (P1) - - ST (P1) LD (P2) - - ADDI (P2) - ST (P2)

El resultado es inesperado: aunque ambos procesadores han incrementado el valor de CONT, el valor final será CONT = 1. ¿Dónde está el problema? La variable compartida CONT se ha accedido de manera no adecuada, habiéndose intercalado en el tiempo las operaciones de P1 y P2 sobre dicha variable. ¿Cuál sería la solución? También en este caso se necesita sincronizar el uso de la variable compartida y ordenar su acceso (primero en un procesador y luego en el otro), para que el resultado de la ejecución en paralelo sea el esperado. De hecho, aunque el código se ejecute en dos procesadores, ese trozo de código se debería ejecutar en serie. Dicho de otra manera, la ejecución de ese código debe ser atómica.

En el ejemplo anterior, los dos procesos sólo comparten una variable, sobre la que se efectúa una operación muy simple (+1), pero en general se ejecutan más operaciones sobre las variables compartidas. Por eso, algunos trozos de código de los procesos paralelos tienen que definirse como secciones críticas, y hay que controlar de manera adecuada que sólo un proceso ejecute simultáneamente dicho código, para lo que suelen utilizarse variables de tipo cerrojo, que funcionan como semáforos a la entrada de las secciones críticas, regulando el acceso de los procesadores a las mismas.

En resumen, para poder ejecutar un programa en P procesadores, a menudo es necesario sincronizar el uso de las variables compartidas. La sincronización entre procesos puede resolverse por software o por hardware. Si se hace en hardware, suele ser más rápida pero menos flexible; si se hace por software (bibliotecas), se suelen obtener soluciones más flexibles. Hoy en día se utiliza una mezcla de ambos tipos; por una parte, se añaden instrucciones especiales al lenguaje máquina, y, por otra, utilizando esas instrucciones se escriben diferentes funciones de sincronización.

Las estrategias básicas de sincronización son dos: exclusión mutua (mediante funciones lock/unlock) y sincronización por eventos (punto a punto, mediante indicadores o flags, o global, mediante barreras). En las operaciones de sincronización, los procesos esperan hasta que ocurra una determinada acción (que se abra el cerrojo, que se active un flag...). Como

Page 136: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 122 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

sabemos, los algoritmos de espera pueden ser de dos tipos: espera activa o bloqueo. En espera activa, el proceso entra en un bucle en el que continuamente se pregunta si ya se ha producido una determinada acción; mientras tanto, el procesador no realiza ninguna tarea útil. En los casos de bloqueo, en cambio, el sistema operativo efectúa un cambio de contexto para pasar a ejecutar otro proceso. El propio sistema operativo se encargará de “despertar” al proceso que está en espera cuando se produzca el evento esperado (o el propio proceso volverá cada cierto tiempo a analizar el estado de la sincronización). Ambos mecanismos, espera activa y bloqueo, son adecuados, y escogeremos uno u otro en función de las circunstancias concretas de la aplicación y de la máquina (tiempo a esperar, latencia del cambio de contexto, existencia de otros hilos o threads para ejecutar...); también puede utilizarse un sistema mixto: un tiempo umbral de espera, seguido de un cambio de contexto. En los ejemplos que vamos a analizar, utilizaremos un bucle de espera activa.

¿De quién es la responsabilidad de escribir las rutinas de sincronización? En general, el programador utilizará las rutinas de sincronización de la librería del sistema (ya optimizadas); en todo caso, hay que analizar con detenimiento el comportamiento de dichas rutinas, porque no todas ellas son adecuadas para cualquier situación, situación que puede variar mucho de programa a programa o dentro del mismo. Por ejemplo, hay que dar solución eficiente al caso de un único procesador que desea entrar en una sección crítica o al caso de P peticiones simultáneas de entrada. Una función de sincronización que dé buen resultado en el primer caso, tal vez no lo dé en el segundo.

Como hemos comentado, la sincronización no es algo intrínseco al algoritmo que se va a ejecutar, sino al hecho mismo de que se quiere ejecutar en paralelo, en P procesadores, lo que va a generar un tráfico de control específico. Por ello, un mecanismo de sincronización adecuado debe cumplir algunas condiciones, entre las que cabe destacar:

• Baja latencia: se debe gastar el menor tiempo posible en efectuar la operación de sincronización, sea cual fuera la situación del programa; por ejemplo, no se debería perder tiempo en el cerrojo de una sección crítica cuando ésta está libre y no hay competencia en la entrada.

• Tráfico limitado: el tráfico que se genera en el acceso y uso de las variables de sincronización debe ser el mínimo posible, para evitar saturar la red de comunicación.

Page 137: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 123 ▪

• Buena escalabilidad: tanto la latencia como el tráfico no deben crecer (al menos no demasiado) con el número de procesadores del sistema.

• Poco coste de memoria: no se debe utilizar mucha memoria para la sincronización.

• Igualdad de oportunidades: todos los procesos deben tener las mismas oportunidades de resolver sus peticiones de sincronización; deben evitarse situaciones en las que, por ejemplo, un determinado proceso no consiga nunca entrar en una sección crítica, mientras que otros lo hacen una y otra vez (starvation).

Definido el problema, analicemos las principales estrategias de sincronización.

4.2 EXCLUSIÓN MUTUA (mutual exclusion)

Se utiliza la exclusión mutua para controlar la ejecución de un trozo de código que, aunque está replicado en P procesos, no puede ser ejecutado por más de un proceso simultáneamente. Ese trozo de código forma una sección crítica y nunca debe haber más de un proceso ejecutándolo. Para proteger el acceso a una sección crítica se utilizan dos funciones específicas, lock y unlock, que manejan una variable de tipo cerrojo, y que hacen las veces de un semáforo.

El cerrojo puede tomar dos valores: 0 y 1, Si el cerrojo vale 0 (abierto), no hay problema alguno para ejecutar la sección crítica; en cambio, si el cerrojo vale 1 (cerrado) hay que esperar, ya que otro proceso está ejecutando en ese momento la sección crítica.

Dos funciones se ejecutan con la variable cerrojo. El proceso que entra en la sección crítica cierra el cerrojo (lock), y, al finalizar la ejecución de la sección crítica, lo abre (unlock). Antes de entrar en la sección crítica, los procesos analizan el valor del cerrojo y se quedan a la espera mientras esté cerrado. Mediante esas dos funciones es posible gestionar el acceso a una sección crítica para que los procesos la ejecuten siempre de uno a uno:

lock(…)

[ sección crítica ]

unlock(…)

La exclusión mutua puede lograrse también por medio del hardware. Por ejemplo, se pueden dedicar algunas líneas del bus de control para utilizarse

Page 138: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 124 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

como variables cerrojo hardware (or-wired, como la señal sh). Sin embargo no se suele utilizar esa solución, sino que las funciones lock y unlock se implementan en software. Veamos cómo podrían escribirse esas dos funciones (CER es una variable tipo cerrojo):

función lock(CER) lock: LD R1,CER BNZ R1,lock ; saltar si no es 0

ADDI R2,R0,#1 ; R2 := 1 ST CER,R2 ; cerrar cerrojo RET

función unlock(CER) unlock: ST CER,R0 RET

Antes de entrar en la sección crítica se lee el cerrojo. Si está cerrado (CER = 1), los procesos se quedan en el bucle, analizando una y otra vez el valor del cerrojo; si está abierto (CER = 0), se puede pasar a la sección crítica, cerrando previamente el cerrojo. Finalmente, al terminar de ejecutar el código de la sección crítica se ejecuta la función unlock para abrir el cerrojo (CER = 0).

Sin embargo, aunque las rutinas anteriores podrían ser adecuadas en el caso de un sistema con un solo procesador (en función de cómo se reparta el tiempo de ejecución), no funcionan bien en un sistema multiprocesador. ¿Cuál es el problema? El mismo que tiene la sección crítica, la falta de atomicidad. El uso (lectura / escritura) de la variable CER no es atómico, por lo que no se puede impedir que dos procesos pasen a la sección crítica. El problema reside en que no existe una unidad de control centralizada, puesto que los procesos van en paralelo de manera completamente independiente.

Para poder gestionar secciones críticas necesitamos disponer de instrucciones atómicas de tipo RMW (read-modify-write) que permitan efectuar una operación de lectura y escritura sobre una variable (el cerrojo) en modo atómico. Mientras se está ejecutando una operación especial de este tipo, el controlador del sistema de memoria bloquea el acceso del resto de procesadores a esa variable.

Existen diferentes instrucciones de tipo RMW, y todos los procesadores actuales disponen de una o varias de ellas en su juego de instrucciones, ya que todos ellos están pensados para ser utilizados en entornos multiprocesador de memoria compartida. Veamos las principales instrucciones atómicas RMW.

Page 139: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 125 ▪

4.2.1 Instrucciones Test&Set y Swap

Como primera opción para gestionar cerrojos, vamos a analizar dos instrucciones similares. En ambos casos se ejecuta una operación de tipo RMW: se lee una variable en memoria, se modifica, y se vuelve a escribir en memoria, sin ninguna interferencia (operación atómica).

4.2.1.1 Instrucción Test&Set

Es una instrucción atómica RMW, la más antigua, que realiza la siguiente operación:

▪ T&S R1,CER R1 := MEM[CER]; MEM[CER] := 1;

Es decir, se carga una variable en un registro y se escribe un 1 en dicha variable en memoria.

Utilizando la instrucción T&S, las dos funciones de un cerrojo (cerrar/abrir) pueden hacerse así:

lock: T&S R1,CER BNZ R1,lock

RET

unlock: ST CER,R0 RET

La instrucción T&S asegura que sólo un proceso leerá CER = 0, ya que junto a ello, de manera atómica, se escribe un 1; por tanto, el resto de los procesos verá un 1 en dicha variable, y continuará en el bucle de espera.

Al salir de la sección crítica hay que abrir el cerrojo, y para ello es suficiente con escribir un 0 en la variable CER, con una operación "estándar" de escritura, ya que en la sección crítica sólo hay un proceso.

4.2.1.2 Instrucción Swap

La instrucción Swap es similar a T&S, pero, en lugar de escribir una constante en memoria, escribe el contenido de un registro. Se trata por tanto de un intercambio atómico entre el contenido de un registro y una posición de memoria:

▪ SWAP R1,CER R1 ↔ MEM[CER];

Page 140: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 126 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Para efectuar un cerrojo, basta con cargar previamente un 1 en el registro. Éstas serían las correspondientes rutinas lock y unlock:

lock: ADDI R1,R0,#1 ; R1 := 1

l1: SWAP R1,CER BNZ R1,l1

RET

unlock: ST CER,R0 RET

4.2.1.3 Análisis del tráfico

Tal como hemos visto, las instrucciones de tipo RMW permiten controlar el acceso a una sección crítica, pero tenemos que analizar si se hace de manera eficiente o no. Como hemos comentado, las funciones de sincronización deben ser de latencia baja y generar poco tráfico, todo ello, a ser posible, independiente del número de procesos/procesadores, y con un reparto equilibrado de los recursos entre los procesos.

Sin embargo, no es eso lo que ocurre. Supongamos que se utiliza un protocolo tipo MESI (invalidación) para mantener la coherencia de los datos. Cada vez que un proceso ejecuta la instrucción T&S se produce una escritura sobre una variable compartida, el cerrojo. La variable cerrojo estará en estado S (shared) en la cache y, al ser una escritura, habrá que invalidar todas las copias para mantener la coherencia (snoopy). Esto no es un problema si somos el único proceso intentando acceder a la sección crítica; sin embargo, si en ese momento hay muchos procesos efectuando la misma operación, el próximo intento en todos los procesadores será un fallo en cache (se ha anulado la variable cerrojo). Todos los procesos, más o menos a la vez, pedirán el bloque de datos correspondiente, por lo que se generará un tráfico de datos muy alto en el bus, más alto cuanto mayor sea el número de procesos esperando entrar en la sección crítica. Como consecuencia, las latencias (el tiempo de respuesta) de dichas operaciones crecerán mucho.

En la siguiente figura puede observarse una simulación de dicha situación. Al principio, el procesador P0 está en la sección crítica y otros cuatro procesadores esperan para entrar. P0 abandona la sección crítica y escribe CER = 0 (unlock), por lo que invalida todas las copias de dicha variable. Los otros cuatro procesos, a la vez, pedirán (BRQ) el bloque que contiene CER, para poder ejecutar T&S. Al ser una instrucción atómica, el controlador del bus sirve las peticiones de manera "ordenada" (FIFO en la figura). Para indicar la atomicidad, hemos puesto la ejecución de la instrucción T&S entre corchetes.

Page 141: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 127 ▪

Simulación de la entrada a una sección crítica Sincronización: Test&Set (TS)

BRQ = petición de bloque / x = invalidado / transmisión de un bloque de datos

P0 C=0 INV

P1 ? x [TS BRQ TS INV] x SECCIÓN CRÍTICA

P2 ? x [TS BRQ. . . . . . . . . . . . TS INV] [TS. . . . x BRQ. . . . . . . . . TS INV] [TS. . . . x BRQ. . . . . . . . . .

P3 ? x [TS BRQ. . . . . . . . . . . . . . . . . . . . . . . TS INV] [TS . . . x BRQ. . . . . . . . . . . TS INV] [TS. . . . x BRQ. .

P4 ? x [TS BRQ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . TS INV] [TS. . . . . x BRQ. . . . . . . . . . TS INV] [TS. .

repetir y repetir

Tráfico de datos (bloques) Para que entre un procesador en la sec. cr. → P + (P – 1) × k veces Al salir de la sección crítica → 1

La conclusión de la simulación es clara: mientras la sección crítica se mantiene ocupada, los procesos que intentan entrar están anulando, una y otra vez, el bloque que contiene la variable cerrojo, lo que implica que hay que enviar una y otra vez ese bloque, generando un gran tráfico en el bus. No hay que olvidar que ese tráfico no corresponde al algoritmo que se ejecuta sino al hecho de ejecutarlo en paralelo.

Así pues, aunque la función lock anterior formalmente funciona bien, y en situaciones de poca competencia no da problemas, pero cuando la competencia por entrar en la sección crítica es alta el proceso se degrada mucho, es decir, no es escalable. Pueden plantearse, sin embargo, algunas mejoras en el diseño de las rutinas de acceso a la sección crítica, intentando reducir el tráfico y la latencia.

4.2.1.4 Procedimiento Test&Set with backoff

La fuente del tráfico que se genera en el bus está en la instrucción T&S (que efectúa siempre una escritura). Por tanto, deberíamos limitar el número de veces que se ejecuta dicha instrucción.

Una primera alternativa sería esperar un cierto tiempo entre dos operaciones de T&S:

T&S – t. de espera – T&S – t. de espera – ...

Page 142: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 128 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

es decir, si no es posible entrar en la sección crítica en un momento determinado, no intentarlo una y otra vez, generando tráfico y sin poder entrar, sino esperar un cierto tiempo para aumentar la probabilidad de encontrar libre la sección crítica.

El tiempo de espera entre intentos no debería ser muy alto, para evitar tener el proceso parado cuando ya se ha liberado la sección crítica, ni muy bajo, para no intentar entrar en vano. Es decir, hay que tomar un compromiso entre reducir el tráfico (tiempo alto) y no perder tiempo en balde (bajo). Diferentes experimentos muestran que suele ser adecuado utilizar un tiempo que crece de forma exponencial, del tipo ti = k ci (k y c, dos constantes; i, número de intentos realizados para entrar en la sección crítica: 0, 1, 2...), lo que genera la siguiente secuencia de tiempos de espera:

t0 = k t1 = k c t2 = k c2 ... (c > 1)

A esta estrategia se le suele denominar Test&Set with backoff. Las rutinas de control del cerrojo pueden ser las siguientes:

lock: T&S R1,CER BNZ R1,esp

RET esp: CALL ESPERA(t1) ; t1 = tiempo de espera [t1 := ...] ; calcular nuevo valor para t1 JMP lock

unlock: ST CER,R0 RET

4.2.1.5 Procedimiento Test-and-Test&Set

Veamos una segunda alternativa para reducir el tráfico. Cada vez que se ejecuta T&S se escribe un 1 en memoria... aunque el contenido de la memoria sea precisamente 1. ¿Por qué escribir en la variable cerrojo en todos los intentos de acceso a la sección crítica si no se va a modificar su contenido?

Page 143: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 129 ▪

La idea es dividir la operación de sincronización en dos fases. En la primera parte, simplemente se analiza el contenido del cerrojo, y para ello basta con utilizar una instrucción de lectura estándar. Repetiremos esa operación todas las veces que sea necesario hasta encontrar el cerrojo abierto. En ese momento, se ejecuta una operación de T&S, intentando cerrar de manera atómica el cerrojo. Sólo un proceso lo logrará, y el resto volverá a la fase inicial, a leer el valor del cerrojo. Así pues, cuando se abra el cerrojo cada proceso sólo escribirá una vez.

Los procesos que están intentando acceder a la sección crítica no generan tráfico en el bus mientras la sección crítica está ocupada. Las invalidaciones (y, por tanto, la necesidad de tener que traer bloques de datos) sólo ocurrirán en dos ocasiones: cuando uno de los procesos cierra el cerrojo (escribe un 1) y cuando el proceso que termina la ejecución en la sección crítica lo abre (escribe un 0).

A esta estrategia de sincronización se le conoce como Test-and-Test&Set, y las rutinas de control de la variable cerrojo son las siguientes:

lock: LD R1,CER ; fase de test BNZ R1,lock

T&S R1,CER ; fase de test-and-set BNZ R1,lock

RET

unlock: ST CER,R0 RET

En comparación con el uso simple de la instrucción T&S, cuando se utiliza

el procedimiento Test-and-Test&Set el tráfico generado se reduce notablemente. La siguiente figura muestra una simulación de dicha estrategia de sincronización. Al inicio, todos los procesos están en la fase de test (LD). Al abrirse el cerrojo, todos los procesos solicitan el bloque de datos que contiene la variable cerrojo, ya que ha quedado invalidado en todas las caches. Todos verán que el cerrojo está abierto (CER = 0), y ejecutarán T&S (atómico), pero sólo uno logrará pasar a la sección crítica; el resto volverá a la fase de test, ya que encontrarán el cerrojo cerrado.

Page 144: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 130 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Simulación de la entrada a una sección crítica Sincronización: Test-and-Test&Set

BRQ = petición de bloque / x = invalidado / transmisión de un bloque de datos

P0 C=0 INV

P1 LD x BRQ LD [TS . . . . . . . . . TS INV] x SECCIÓN CRÍTICA

P2 LD x BRQ. . . . . LD [TS . . . . . . . . . x BRQ TS INV] LD. . . . x BRQ. . . . . . . . . . LD . . . . . . . . .

P3 LD x BRQ. . . . . . . . . . . . LD [TS . . . . x BRQ. . . . . . . . . . . . TS INV] LD . . . . x BRQ. . LD . . . . .

P4 LD x BRQ. . . . . . . . . . . . . . LD [TS x BRQ. . . . . . . . . . . . . . . . . . . . . . . . . TS INV] LD . . . . . . . . . . . . . .

Tráfico de datos (bloques) Para que entre un procesador en la sec. cr. → P + (P – 1) + (P – 2) Al salir de la sección crítica → 1 En total → 3P – 2

Para que entren P → 2

32

)13()23(2

1

PPPPP

p→

−=−∑

=

El tráfico que se genera es de orden P2, siendo P el número de procesos

que está intentando acceder simultáneamente a la sección crítica; no es por tanto muy escalable, por lo que el bus se saturará con facilidad al crecer P. Además, el tráfico se genera en momentos concretos; todos los procesos fallan a la vez en la cache, al abrirse el cerrojo, y solicitan a la vez el bloque de datos (en este segundo caso no sirven las estrategias de esperar un cierto tiempo, ya que sólo se ejecuta una vez la instrucción T&S).

4.2.1.6 Resumen de características

Como hemos visto, las funciones más simples de tipo T&S para controlar el acceso a una sección crítica generan mucho tráfico de sincronización cuando existe alta contención en el acceso a la sección crítica mientras ésta está ocupada. Pero por otra parte, resultan muy adecuadas en casos de baja contención: son muy simples, tienen una latencia muy pequeña (pocas instrucciones) y no generan tráfico.

Se utiliza muy poca memoria, ya que basta con una variable, independientemente del número de procesos. Desde el punto de vista del

Page 145: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 131 ▪

equilibrio en el reparto de recursos, no se establece ningún tipo de política de asignación y, por tanto, el tiempo de espera a recibir respuesta dependerá de los criterios de prioridad que utilice el controlador del bus (por ejemplo, si es FIFO, sabemos que el tiempo estará acotado).

En resumen, un T&S simple es una estrategia adecuada únicamente cuando se sabe que la contención en la entrada a la sección crítica va a ser muy baja (o cuando el número de procesadores del sistema es muy pequeño).

La estrategia T&S-BO tiene un comportamiento similar, aunque genera menos tráfico y es, por consiguiente, más escalable.

Test-and-T&S es el mecanismo más adecuado de los tres. En situación de alta competencia, mantiene el tráfico bastante limitado; cuando la competencia es baja, presenta una latencia algo superior a la de los casos anteriores, ya que hay que ejecutar siempre las dos fases: LD [fase de test] y T&S [fase de test-and-set].

Una última cuestión relacionada con la función lock. ¿Es necesario llevar a la cache la variable cerrojo de la función Test&Set, o es mejor mantenerla siempre en memoria principal? Sabemos que es útil llevar a la cache las variables que vamos a utilizar, pero si fallamos continuamente en el acceso al cerrojo, porque nos lo invalidan continuamente, y tenemos que transferir el bloque de datos completo una y otra vez, tal vez sería más cómodo dejar el cerrojo en la memoria principal y no hacer copias. En todo caso, si lo dejamos en MP, todos los accesos a dicha variable serían a la memoria principal, a través del bus, con lo que la latencia en casos de baja contención sería mucho más alta.

4.2.2 Instrucciones Load Locked / Store Conditional y Compare&Swap

Acabamos de comentar el problema que presenta una función de lock basada en la estrategia Test-and-T&S: cuando ejecutan la instrucción T&S todos los procesos efectúan una escritura en la variable cerrojo, se invalidan entre todos ellos y se genera un hot spot de tráfico, ya que todos los procesos solicitan en un intervalo muy corto de tiempo el bloque de datos correspondiente. Que el tráfico sea muy alto no se puede aceptar cuando el número de procesadores crece, por lo que hay que intentar algo más para reducir dicho tráfico.

Page 146: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 132 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

4.2.2.1 Instrucciones LL y SC

Cada vez es más habitual en los procesadores que las operaciones atómicas necesarias para la sincronización se repartan en dos instrucciones, que, usadas de manera adecuada, permiten realizar una operación atómica RMW sobre una variable. Además de las dos instrucciones, se utiliza un flag hardware para saber que la operación se ha ejecutado de manera atómica. Las dos instrucciones específicas para sincronización son: LL –Load Locked (o linked)– y SC –Store Conditional–.

La instrucción LL efectúa una lectura en memoria, pero tiene un efecto lateral: en un registro (latch) especial que sólo se usa para sincronización (le llamaremos LSin) se guarda la dirección accedida y un flag, para indicar que se ha leído dicha posición en un modo especial.

▪ LL R1,CER R1 := MEM[CER]; LSin[dir] := CER; LSin[flag] := 1;

La instrucción SC efectúa una escritura condicional en memoria, para lo que primero analiza el latch LSin. Si contiene la dirección que se quiere escribir y el flag está en 1, entonces efectúa la escritura en memoria y envía una señal especial de invalidación de dicho flag a todos los procesadores, que al recibirla pondrán a 0 el flag si está asociado a la dirección indicada. En cambio, si el flag está desactivado, entonces la escritura no se ejecuta. En ambos casos, se devuelve un código de control, normalmente en el registro que se quiere escribir, indicando si la escritura se ha ejecutado o no.

▪ SC CER,R1 si (LSin[dir,flag] = CER,1) { MEM[CER] := R1; LSin[flag] := 0 (INV, todos) } R1 := 1/0 (se ha escrito / o no)

Veamos cómo se utilizan ambas instrucciones para gestionar la entrada a

una sección crítica. La operación se realiza en dos o tres pasos:

1 Se lee la variable de sincronización (cerrojo) mediante la instrucción LL, con lo que se guarda la dirección accedida y se activa el flag de sincronización en el latch de sincronización.

2 Si es necesario, se efectúa cálculo o se procesan variables.

Page 147: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 133 ▪

3 Se intenta escribir en la variable cerrojo (normalmente los resultados del segundo paso), mediante la instrucción SC. Si el flag de sincronización asociado a la dirección del cerrojo está activado, se realiza la escritura y se anulan, además de la variable cerrojo, todos los flags del sistema correspondientes a la dirección de la variable que se escribe: la operación total [LL — SC] se ha realizado atómicamente, sin interferencias.

En cambio, si el flag está desactivado, no se realiza la escritura, ya que otro proceso ha efectuado una escritura en dicha variable (razón por la cual se ha anulado el flag que se activó con la instrucción LL). No se ha podido realizar atómicamente el par [LL — SC] y por ello hay que repetir todo el proceso. Como SC no ha escrito, no se produce ninguna invalidación, ni se genera, por tanto, tráfico alguno.

En resumen, si SC termina bien, entonces el trozo de código [LL — SC] se ha ejecutado atómicamente (lo cual no quiere decir que las instrucciones entre LL y SC formen una sección crítica).

Utilizando esas dos instrucciones, las rutinas lock y unlock quedan de la siguiente manera:

lock: ADDI R2,R0,#1 ; R2 := 1

l1: LL R1,CER ; examinar cerrojo BNZ R1,l1

...

SC CER,R2 ; intentar cerrar cerrojo BZ R2,lock ; SC no ha escrito, repetir RET

unlock: ST CER,R0 RET

Como ocurre en el caso Test-and-T&S, no se genera tráfico mientras estamos en el bucle de espera (LL), ya que sólo se hace una lectura. Las mejoras vienen en la segunda parte. La instrucción T&S siempre escribe en memoria, independientemente del valor del cerrojo; en cambio, la instrucción SC sólo escribe cuando el cerrojo está abierto (es decir, cuando nadie ha escrito en dicha variable desde que se ejecutó LL). Así pues, sólo se genera tráfico en el bus en dos casos: al entrar en la sección crítica (cuando SC cierra el cerrojo), y al salir de la misma (al abrir el cerrojo).

Page 148: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 134 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Simulación de la entrada a una sección crítica Sincronización: LL / SC

BRQ = petición de bloque / x = invalidado / transmisión de un bloque de datos

P0 C=0 INV

P1 LL x BRQ LL(1) [SC . . . . . .SC INV] SECCIÓN CRÍTICA

P2 LL x BRQ. . . . . LL(1) [SC . . . . . . . (0)x BRQ SC] LL. . . . . . . . . . . .

P3 LL x BRQ. . . . . . . . . . . . LL(1) [SC . . . (0)x BRQ. . . . . SC] LL . . . . . . .

P4 LL x BRQ. . . . . . . . . . . . . . LL(1) [SC (0)x BRQ. . . . . . . . SC] LL . . .

Tráfico de datos (bloques) Para que entre un procesador en la sec. cr. → P + (P – 1) Al salir de la sección crítica → 0 En total → 2P – 1

Para que entren P → 2

1)12( PP

P

p∑=

=−

En la figura anterior se muestra una simulación de esta estrategia. Como puede observarse, sólo una instrucción SC logra escribir en memoria, la primera, ya que las demás encuentran desactivado el flag que activaron con la instrucción LL. El tráfico de datos, por tanto, es menor. En todo caso, el tráfico todavía es alto, y además no se aplica ningún tipo de gestión de las peticiones de entrada. Hay, por tanto, oportunidades para la mejora.

4.2.2.2 Instrucción Compare&Swap

Antes de estudiar posibles mejoras del procedimiento anterior, veamos otra alternativa en la línea de la anterior. Se trata de la instrucción Compare&Swap, que realiza la siguiente operación en modo atómico:

▪ C&S R1,R2,CER si (R1 = MEM[CER]) entonces MEM[CER] ←→ R2

En este caso, la escritura en memoria tampoco se realiza en todos los casos, sino sólo cuando se cumple la comparación. Lo que en la pareja LL/SC se consigue con la ayuda del hardware (flag), en este caso se logra mediante el flag estándar resultado de una comparación.

Page 149: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 135 ▪

El código para controlar un cerrojo usando la instrucción C&S es el siguiente:

lock: ADDI R2,R0,#1 ; R2 := 1

l1: C&S R0,R2,CER ; no escribe siempre BNZ R2,l1 ; R2 = 1 → no se ha escrito

RET

unlock: ST CER,R0 RET

La instrucción C&S es más "compleja" que las anteriores, puesto que utiliza dos registros y una posición de memoria (es decir, una operación de memoria con tres operandos). En muchas arquitecturas RISC no se utiliza ese formato, por lo que suele ser más habitual que se utilice la pareja LL/SC.

4.2.2.3 Algunos problemas con las instrucciones LL/SC

Las instrucciones LL/SC necesitan la ayuda del hardware para cumplir su función. Por una parte, cada procesador tiene que disponer de un registro especial y un flag asociado al mismo, y, por otra, se necesita la colaboración del controlador del bus. Cuando se ejecuta LL se guarda la dirección accedida en dicho registro y se activa el flag. A partir de ese momento, el controlador deberá espiar continuamente el bus para detectar si se produce una escritura en esa dirección en algún otro procesador, en cuyo caso tiene que borrar el flag. También hay que borrar el flag cuando se reemplaza el bloque que contiene la variable de sincronización o en los cambios de contexto.

Al ir a ejecutar la instrucción SC se mira el flag. Si está activado, no hay problemas: se escribe en memoria y se envía una señal de control para borrar todos los flags asociados a dicha dirección en el resto de procesadores. Pero si está desactivado, no se efectúa la escritura y se devuelve el correspondiente código de “error”. Hay que implementar el protocolo con cuidado para evitar problemas de deadlock, livelock, y similares. Por ejemplo, tendríamos un caso de livelock (repetir continuamente un proceso sin llegar a completarlo nunca) si ocurriera esto: LL – SC (fallo) – LL – SC (fallo) - ... (por ejemplo, porque se reemplaza continuamente el bloque que contiene la variable cerrojo).

Page 150: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 136 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Para evitar estos problemas es conveniente no aceptar reemplazos del bloque que contiene la variable de sincronización. ¿Cómo? Por un lado, no efectuar operaciones en memoria entre las instrucciones LL y SC, para no tener que cargar nuevos bloques de datos en la cache (y evitar así un posible reemplazo).

Por otro lado, aunque no se acceda a datos en memoria, es muy probable que haya instrucciones entre LL y SC. Como normalmente la cache de datos y la de instrucciones estarán separadas, no tendremos problemas. En todo caso, se recomienda siempre utilizar muy pocas instrucciones entre la pareja LL/SC, para reducir la posibilidad de que otro procesador acceda a la variable de sincronización intercalándose con nosotros.

4.2.3 Instrucciones Fetch&Op

En muchos casos, las operaciones que se realizan con las variables compartidas son muy simples, tal como hemos visto en los ejemplos anteriores. Por ello, existen instrucciones especiales que realizan esas operaciones de modo atómico: las instrucciones Fetch&Op. Se trata de un grupo de instrucciones RMW de uso más general que las anteriores: antes de volver a escribir la variable en memoria se efectúa algún tipo de operación (op) con la misma. Según la operación que se realice, tenemos diferentes instrucciones; por ejemplo:

▪ Fetch&Incr R1,VAR R1 := MEM[VAR]; MEM[VAR] := MEM[VAR] + 1;

▪ Fetch&Dcr R1,VAR R1 := MEM[VAR]; MEM[VAR) := MEM[VAR] – 1;

▪ Fetch&Add R1,R2,VAR R1 := MEM[VAR]; MEM[VAR] := MEM[VAR] + R2;

Por ejemplo, utilizando la instrucción Fetch&Incr podemos incrementar el valor de la variable CONT de manera atómica:

Fetch&Incr R1,CONT

El valor de la variable CONT se deja en el registro R1, y, a la vez, se incrementa el contenido de la posición de memoria CONT. Es decir, si CONT valía 6, tras ejecutar la instrucción tendremos que R1 = 6 y CONT = 7.

Page 151: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 137 ▪

Si el código que hay que ejecutar en exclusión mutua es más largo (algo más complejo que una simple operación de incremento), entonces habrá que generar una sección crítica; aunque utilizando este tipo de instrucciones también se pueden implementar dichas funciones, lo más habitual es utilizar otro tipo de instrucciones atómicas para hacerlo.

4.2.4 Alternativas para reducir el tráfico

Tal como hemos comentado, con las instrucciones LL/SC conseguimos reducir el tráfico, pero aún caben ciertas optimizaciones. Analicemos las más importantes.

4.2.4.1 Tickets

Un mecanismo basado en tickets puede ser útil para reducir el tráfico en la entrada a una sección crítica. La idea es sencilla. Un proceso que quiere entrar en la sección crítica tiene que coger primero un ticket, que le indica el número de turno de entrada que le corresponde. A continuación, se quedará esperando a que llegue su turno. En ese momento, solamente él tendrá permiso para entrar en la sección crítica: es su turno. Al abandonar la sección crítica incrementará la variable que indica el turno, para dejar paso al siguiente proceso.

Con el método de los tickets no se produce contención en la entrada de la sección crítica, ya que todas las entradas se han ordenado, y por tanto se reduce algo el tráfico. En cambio, hay que utilizar dos variables compartidas: la que sirve para repartir tickets (TICKET), y la variable que indica el turno actual (TURNO).

El contador que se utiliza para repartir tickets tiene que accederse en exclusión mutua, para lo que podemos utilizar, si disponemos de ello, una instrucción de tipo Fetch&Incr o bien las instrucciones LL y SC. Por ejemplo:

F&I R1,TICKET ; R1 := MEM[TICKET]; ; MEM[TICKET]:= MEM[TICKET]+ 1

o bien: tick: LL R1,TICKET ; conseguir ticket ADDI R2,R1,#1 ; incrementar número de ticket para el siguiente SC TICKET,R2 ; pero de manera atómica BZ R2,tick ; repetir la operación hasta conseguir atomicidad

Page 152: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 138 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Utilizaremos una u otra solución en función del tipo de instrucciones que pueda usar el procesador. Finalmente, las rutinas de lock y unlock quedarán así22:

lock: F&I R1,TICKET ; obtener ticket

esp: LD R2,TURNO SUB R3,R1,R2 BNZ R3,esp ; esperar turno

RET

unlock: LD R1,TURNO ; actualizar turno ADDI R1,R1,#1 ST TURNO,R1 ; para dar paso al siguiente RET

En la siguiente figura (un poco más adelante) se presenta una simulación de esta estrategia. Los procesos ya han conseguido su ticket y están esperando su turno. Sólo se genera tráfico una vez, cuando se incrementa el turno al salir de la sección crítica, aunque tendríamos que contar también el tráfico generado al obtener el ticket, ya que hay que traer a la cache el bloque que contiene dicha variable. Sumado todo el tráfico, el nivel del mismo es similar al que habíamos conseguido antes, aunque ahora está más distribuido en el tiempo (la obtención de los tickets no tiene por qué ser simultánea), lo que también es importante.

A pesar de todo, todavía hay momentos en los que se genera bastante tráfico. Al actualizar la variable TURNO (al salir de la sección crítica) se producirán fallos en la cache en todos los procesadores que estén esperando entrar, que pedirán, más o menos a la vez, una copia de dicho bloque: se genera un “pulso” de tráfico.

Desde el punto de vista de la latencia, cuando no se produce contención (simultaneidad) en la entrada, esta técnica es de latencia más alta, puesto que primero hay que conseguir el ticket.

El reparto de peticiones es justo: se aplica una política tipo FIFO. Si se quiere, en este caso se pueden aplicar técnicas de espera tipo backoff, con un tiempo de espera “proporcional” a la diferencia entre el ticket obtenido y el turno actual. 22 Si el número de procesos es P, conviene incrementar las variables TICKET y TURNO módulo P, para

evitar desbordamientos.

Page 153: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.2 EXCLUSIÓN MUTUA (mutual exclusion) ▪ 139 ▪

4.2.4.2 Vectores de cerrojos

Como hemos comentado, el método anterior genera momentos de tráfico alto al actualizar la variable TURNO, compartida por todos los procesos. El problema desaparece si, en lugar de obtener un ticket con el turno correspondiente, se obtiene la dirección de un elemento de un vector de cerrojos, un cerrojo particular donde esperar para entrar en la sección crítica. Así, primero se reparten posiciones del vector de cerrojos —valores de la variable INDICE—, tal como hemos hecho con los tickets en el método anterior; luego, cada proceso espera a que se abra su cerrojo particular: VECT_CER(INDICE).

vector de cerrojos: VECT_CER → ... 0 1 1 1 1 ...

proceso en la sección crítica INDICE: siguiente posición de espera

Las rutinas de lock y unlock serían las siguientes:

lock: F&I R1,INDICE ; obtener posición del vector de cerrojos ; ¡ojo! función módulo esp: LD R2,VECT_CER(R1) ; esperar turno BNZ R2,esp

ST MI_INDICE,R1 ; guardar índice para la salida de la S.C. RET unlock: ADDI R2,R0,#1 ; R2 := 1

LD R1,MI_INDICE ; recuperar índice del vector de cerrojos ST VECT_CER(R1),R2 ; cerrar cerrojo propio (1)

ADDI R1,R1,#1 ; ¡ojo! función módulo ST VECT_CER(R1),R0 ; abrir siguiente cerrojo (0) RET

En la figura siguiente se muestra una simulación del tráfico generado. El tráfico es ahora constante, independiente del número de procesos, porque al salir de la sección crítica sólo se actualiza (y se anula) el cerrojo de un proceso. El resto de procesos no se entera, y continúa a la espera de su turno. Estamos suponiendo que no existe un problema de falsa compartición en la cache, es decir, que los elementos del vector de cerrojos están en bloques diferentes de memoria.

Page 154: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 140 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

Simulación de la entrada a una sección crítica Sincronización: Tickets / Vectores de cerrojos

BRQ = petición de bloque / x = invalidado / transmisión de un bloque de datos

Tickets Vectores de cerrojos

P0 TURNO++ INV VC(i+1)= 0 INV

P1 LD x BRQ LD SEC. CRIT. LD x BRQ LD SEC. CRIT.

P2 LD x BRQ. . . . . LD . . . . . . . . . . . . . LD . . . . . . . . .

P3 LD x BRQ. . . . . . . . . . . . LD . . . . . . . . LD . . . . . . . . .

P4 LD x BRQ. . . . . . . . . . . . . . LD . . . LD . . . . . . . . .

Tráfico de datos (bloques) TICK. V.C. Para conseguir el ticket / turno → 1 1 Para que entre un procesador en la sec. cr. → P 1 Al salir de la sección crítica → 0 1 En total → P + 1 3

Para que entren P → P+3) / 2 3P

El tráfico de datos se reduce considerablemente, pero en cambio se

necesita más memoria para implementar la sincronización (un vector de P elementos).

Como hemos visto, existen diferentes alternativas para gestionar secciones

críticas (para generar funciones lock); por tanto el programador tendrá que analizar las características de su aplicación y de la máquina paralela para optar por la más adecuada.

Como ejemplo final, y a modo de resumen, el tráfico que se generará en el bus, si tenemos P = 7 procesadores (en una máquina de 8) esperando a entrar en una sección crítica, será el siguiente en función de la estrategia empleada:

T-T&S: P(3P–1) / 2 → 70 bloques LL/SC: P2 → 49 bloques

Tick.: P(P+3) / 2 → 35 bloques V.C.: 3P → 21 bloques

Page 155: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.3 SINCRONIZACIÓN "PUNTO A PUNTO" MEDIANTE EVENTOS ▪ 141 ▪

4.3 SINCRONIZACIÓN "PUNTO A PUNTO" MEDIANTE EVENTOS

Decimos que la sincronización es "punto a punto" si sólo toman parte en la misma dos procesadores (o grupos): el primero avisa al segundo de que se ha ejecutado determinada operación. La sincronización se suele ejecutar mediante un bucle de espera activa sobre una variable común que hace las veces de flag o indicador.

El flag o indicador es una variable de control que permite sincronizar ambos procesos. Por ejemplo, en el caso de un productor y un consumidor, la sincronización puede ser así:

P1 (productor) X = F1(Z); aviso = 1;

P2 (consumidor) while (aviso==0) {}; Y = F2(X);

(En algunos casos se puede usar el propio resultado como indicador; por ejemplo, si sabemos que el resultado va a estar en un rango determinado, el consumidor puede quedarse esperando mientras el resultado esté fuera de ese rango.)

La idea anterior (un flag de sincronización) puede extenderse y ejecutarse

en hardware (y así se ha intentado en algunas máquinas de tipo experimental y en situaciones de paralelismo de grano muy fino), añadiendo a cada posición de memoria un bit de control full/empty que indique si se ha escrito un nuevo dato desde la última vez que se leyó el anterior o no. La sincronización productor/consumidor se efectuaría de la siguiente manera: el productor escribe en la posición de memoria un nuevo dato si el bit de control asociado está a 0, y en ese caso lo pone a 1; el consumidor lee el contenido de la posición de memoria si el bit de control está a 1, y en ese caso lo pone a 0. No es una solución que se haya aplicado comercialmente, ya que es cara (1 bit por cada posición de memoria), requiere instrucciones especiales de memoria y presenta problemas en casos como, por ejemplo, un productor y muchos consumidores.

La sincronización por eventos se efectúa mediante una escritura y un bucle de espera. En algunos contextos, esas dos operaciones se indican mediante dos funciones específicas. Por ejemplo:

Page 156: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 142 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

flag = 1 → post(flag) [ signal(flag) ] while (flag == 0) {} → wait(flag)

Esas funciones pueden generalizarse utilizando vectores de flags (vectores de eventos):

post(vf,i) → activar el elemento i del vector de flags: vf(i) := 1

wait(vf,i) → esperar a que el elemento idel vector de flags vf sea 1

4.4 SINCRONIZACIÓN MEDIANTE BARRERAS

En la ejecución en paralelo de los programas suele ser muy habitual que se necesite sincronizar un grupo de procesos entre sí de manera global, todos a la vez; por ejemplo, para asegurar que todos los procesos han llegado a un determinado punto en la ejecución del programa. Para ese tipo de sincronización se utilizan barreras (barrier).

Para construir una barrera de sincronización se utiliza una variable cerrojo, un contador y un flag. En la barrera se sincronizan P procesos. Cuando los procesos llegan a la barrera, incrementan el valor de un contador —en exclusión mutua— y se quedan esperando a que lleguen todos los procesos. Cuando llega el último, activa el indicador de barrera abierta, y todos los procesos abandonan la misma. Veamos algunos ejemplos.

4.4.1 Una barrera sencilla

El código siguiente representa una barrera de sincronización sencilla. Se ha definido un struct, de tipo tipo_barrera, con tres variables: un cerrojo, un contador y un flag para indicar el estado de la barrera, cerrada (0) o abierta (1). Además de ello, se utiliza la variable local mi_cont, que indica cuántos procesos han llegado a la barrera.

struct tipo_barrera { int cer; variable para el cerrojo int cont; núm. proc. que han llegado a la barrera int estado; estado de la barrera }; struct tipo_barrera B; declaración de la barrera

Page 157: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.4 SINCRONIZACIÓN MEDIANTE BARRERAS ▪ 143 ▪

BARRERA (B,P) P = número de procesos { LOCK(B.cer); entro en la sección crítica if (B.cont == 0) B.estado = 0; soy el primero, cierro la barrera B.cont++; mi_cont = B.cont; cuántos hemos llegado a la barrera UNLOCK(B.cer); salgo de la sección crítica if (mi_cont == P) soy el último { B.cont = 0; inicializo el contador B.estado = 1; abro la barrera } else while (B.estado == 0) {}; espero hasta que la barrera se abra }

Los procesos que ejecutan la barrera incrementan el valor de B.cont,

dentro de una sección crítica. El primer proceso (B.cont = 0) cierra la barrera, tras lo cual todos los procesos que entran en la barrera pasan a esperar que la barrera se abra (B.estado = 1). El último proceso que llega a la barrera (B.cont = P) la abre, y, como consecuencia de ello, todos los procesos abandonan el bucle de espera.

Tras incrementar el contador dentro de la sección crítica, se utiliza la variable mi_cont para decidir si hay que abrir la barrera o no. Dicha variable es necesaria porque, tal y como está escrito el código, no se puede utilizar, sin más, el contador B.cont, ya que en ese momento puede haber otro proceso en la sección crítica incrementando dicho contador. Si se quiere utilizar la variable B.cont, se debe mantener la sección crítica hasta después de la comparación de la instrucción if, y luego terminar la sección crítica (unlock) por las dos ramas del if (then y else).

4.4.2 Barreras reutilizables

¿Algún problema con la barrera anterior? Sí, si se utiliza de manera repetida (por ejemplo, dentro de un bucle): cálculo / barrera / cálculo / barrera..., lo cual es muy normal, ya que la barrera será normalmente una función de biblioteca que llamarán los procesos una y otra vez.

Supongamos que se está ejecutando una barrera de sincronización. El último proceso entra en la barrera y la abre, para que todos los procesos

Page 158: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 144 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

salgan y continúen ejecutando el programa. Si en el código vuelve a aparecer una llamada a la barrera, es posible que un proceso entre en esa segunda llamada a la barrera cuando tal vez algún proceso no haya abandonado todavía la anterior, porque, por ejemplo, no se ha enterado todavía de que la barrera se ha abierto (no estaba en ejecución).

El primer proceso (B.cont = 0) que vuelva a entrar en la barrera la cerrará (B.estado = 0) de nuevo. Por tanto, los procesos que se hayan quedado en la primera barrera ya no podrán salir, y los que entren en la segunda nunca llegarán a abrirla, porque no están todos. Claramente, hemos llegado a una situación de deadlock.

¿Cómo evitar ese problema? Por ejemplo,

a. Utilizando un contador que cuente el número de procesos que abandonan la barrera (de manera similar a como se hace al entrar). Mientras no la abandonan todos, ningún proceso puede volver a entrar.

Por una parte, la latencia de la barrera de sincronización puede ser mayor (en ocasiones hay que esperar, aunque no hay que olvidar que es el último proceso en llegar el que marca la latencia de la barrera), y, por otra, puede haber una mayor competencia en la entrada de la barrera (mientras se espera, se agrupan los procesos).

b. Utilizando valores diferentes, de barrera a barrera, para indicar que la barrera está abierta (bit alternante, sense reversal). ¿Cuántos valores diferentes habría que utilizar? Es suficiente con dos, 0 y 1, puesto que no es posible tener más de dos instanciaciones simultáneas de la misma barrera. Así pues, el flag que abre la barrera irá alternando de valor de una a la siguiente.

Cada proceso utiliza una variable privada para saber el valor actual que indica que la barrera está abierta; no usamos por tanto una variable compartida como en el caso anterior (B.estado) para indicar el estado de la barrera.

De acuerdo a esta segunda opción, la barrera quedaría así:

Page 159: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.4 SINCRONIZACIÓN MEDIANTE BARRERAS ▪ 145 ▪

La variable val_sal es local, una por proceso, e indica el valor actual que permite salir de la barrera.

BARRERA (B,P) { val_sal = !(val_sal); actualizar el valor del bit de apertura LOCK(B.cer); B.cont++; mi_cont = B.cont; UNLOCK(B.cer); if (mi_cont == P) soy el último { B.cont = 0; inicializo el contador B.estado = val_sal; abro la barrera } else while (B.estado != val_sal) { }; espero a que se abra la barrera }

4.4.3 Eficiencia

Los criterios de eficiencia de este tipo de sincronización son los mismos que en el caso anterior: la latencia debe ser baja (no hay que efectuar muchas operaciones para entrar en la barrera), tiene que generarse poco tráfico, debe escalar bien con el número de procesos, etc.

En lo que al tráfico que se genera en una barrera de P procesos se refiere, podemos hacer la siguiente estimación. Supongamos que las variables de la barrera (cer, cont y estado) se encuentran en bloques diferentes (para evitar la falsa compartición). En general, el proceso Pi tiene que conseguir cuatro bloques de datos: el de la variable cer, para entrar en la sección crítica; el de la variable cont, para incrementar el contador; y el de la variable estado dos veces, para quedarse en el bucle de espera, y para salir del mismo, ya que ha sido anulado por el proceso que abre la barrera. Por tanto, el tráfico generado será del orden de 4P bloques (para ser más precisos, 4P – 2, ya que el primer y el último proceso necesitan un bloque menos cada uno).

Analizado en el tiempo, el tráfico se va a repartir, en general, de la siguiente manera: 2 - 3 - 3... - 3 - P–1; es decir, el tráfico que se genera al entrar en la barrera suele estar repartido en el tiempo (suponiendo que no hay

Page 160: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 146 ▪ Capítulo 4: SINCRONIZACIÓN DE PROCESOS EN LOS COMPUTADORES SMP

contención en la entrada a la barrera; si no, la función lock generará más tráfico, tal como hemos visto en los apartados anteriores), pero las últimas P–1 peticiones se generan a la vez, ya que todos los procesos (salvo el último) están esperando a salir de la barrera; en ese momento, por tanto, la latencia de servicio de los bloques será más alta.

Como en los casos anteriores, también aquí son posibles algunas optimizaciones. El objetivo es reducir el número de procesos que acceden a la misma variable, y para ello puede montarse una estructura en árbol, binario por ejemplo (en un bus no se gana nada, ya que todas las trasferencias aparecen en el bus, pero sí cuando la red de comunicación es de otro tipo, no centralizada). En todo caso, el tipo de barreras que hemos visto funcionan suficientemente bien en los sistemas SMP, y no es necesario, salvo casos muy particulares, otro tipo de estructura.

Las barreras pueden implementarse también en hardware, si se dispone de un bus específico de control. La implementación es similar al caso de la línea de control sh (AND wired).

4.5 RESUMEN

Es habitual tener que sincronizar la ejecución de procesos que se ejecutan en paralelo, para que el uso de las variables compartidas sea el adecuado. En algunos casos, hay que utilizar secciones críticas, para lo que se utilizan unas instrucciones específicas del lenguaje máquina del procesador que se pueden ejecutar de manera atómica. De esta manera, se puede leer, modificar y escribir una variable sin que interfiera ningún otro proceso (atómicamente). En otros casos, la sincronización hay que implementarla mediante eventos, punto a punto, o hay que sincronizar un conjunto de procesos, mediante barreras.

Las diferentes instrucciones atómicas que se utilizan para operaciones de sincronización son “similares”, y en un procesador concreto sólo tendremos una o algunas de ellas. De todos modos, se puede “simular” el comportamiento de unas mediante otras. Por ejemplo, se puede escribir una rutina que simule el comportamiento de un F&I o de un T&S mediante las instrucciones LL y SC. Pero no hay que olvidar que algunas pueden ser más adecuadas que otras, en función del tráfico que generan (los bloques de datos que se invalidan en las escrituras). Por ejemplo, una función lock implementada simplemente mediante la instrucción T&S es adecuada si no

Page 161: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

4.5 RESUMEN ▪ 147 ▪

hay contención (competencia) para entrar en la sección crítica, pero no es nada eficiente si se espera una elevada contención.

En los sistemas SMP el tráfico es un problema importante, y, por tanto, se han desarrollado diferentes estrategias para reducir el tráfico que generan las funciones de sincronización: backoff, test-and-test&set, tickets, vectores de cerrojos... Por tanto, es responsabilidad del programador seleccionar entre las diferentes alternativas la más adecuada para su aplicación, bien sea utilizando funciones de una biblioteca del sistema, o bien sea escribiendo funciones específicas.

Page 162: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia
Page 163: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 5 ▪

Consistencia de la Memoria en los Computadores Paralelos

5.1 INTRODUCCIÓN

5.1.1 Sistemas de un solo procesador

¿En qué orden se ejecutan las instrucciones en un procesador? La pregunta tiene más interés del que parece, y tal vez sin pensar mucho podríamos responder: en el orden en que están en el programa. Sin embargo, sabemos que eso no es verdad. Aunque el modelo de ejecución sigue siendo von Neumann, se aplican muchas optimizaciones, tanto hardware como software, que implican cambios en el orden de las instrucciones del programa. Por ejemplo, la ejecución de las instrucciones está segmentada y el inicio y final de las instrucciones no respeta el orden original (modelos orden/desorden tipo scoreboard o Tomasulo, búferes de instrucciones en los superescalares, etc.). Por parte del software, sabemos que el compilador

Page 164: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 150 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

puede desordenar las instrucciones para obtener ejecuciones más eficientes (list scheduling, trace scheduling, software pipelining...).

Lo más preocupante de esas reordenaciones está en las instrucciones de memoria. La memoria del computador debería mantener en todo momento el "estado actual" de la aplicación; es decir, debería reflejar en todo momento hasta qué punto se ha llegado en la ejecución de la misma y los resultados obtenidos. Pero sabemos que no es eso lo que ocurre. Por ejemplo, al utilizar la memoria cache no se mantiene actualizada en todo momento la memoria principal; admitimos que las instrucciones LD adelanten a las ST en ejecución; se utilizan búferes de escritura para volcar contenidos de cache a memoria principal y no parar al procesador; el propio compilador puede eliminar algunos accesos a memoria y usar en su lugar datos de los registros (por ejemplo, al desenrollar bucles con recurrencias), etc. De hecho, es en ese tipo de optimizaciones donde se encuentra una de las razones del aumento de velocidad de los procesadores actuales.

Así pues, la ejecución de un programa sigue su propio camino con el objeto de lograr la mayor eficiencia posible. En todo caso, la ejecución del programa debe ofrecer siempre exactamente los mismos resultados que si se ejecutara en orden estricto. Por ejemplo cualquier lectura de memoria debe obtener siempre lo escrito en dicha variable la última vez.

En los sistemas con un único procesador, todas las optimizaciones que hemos comentado están bajo control de la única unidad de control del sistema, y pueden llevarse así a buen puerto. No ocurre lo mismo, en cambio, en los sistemas multiprocesador, donde el control de los procesos en ejecución es esencialmente distribuido.

5.1.2 Sistemas multiprocesador

Lo que está resuelto en los sistemas de un procesador, se convierte en un problema grave en los multiprocesadores, al estar el control descentralizado entre todos los procesadores. De hecho, ¿en qué orden se ejecutan las instrucciones en un sistema paralelo, considerándolo en su totalidad? ¿es el resultado correcto en todos los casos?

Si la comunicación entre procesos se lleva a cabo en memoria principal, por ejemplo, las cuestiones anteriores se reducen a esta otra: ¿en qué orden se ejecutan las instrucciones de memoria en un sistema paralelo?

Page 165: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.1 INTRODUCCIÓN ▪ 151 ▪

Al problema que hace referencia al orden de ejecución de las instrucciones, y, en general, a la imagen que tienen los procesadores del sistema de memoria se le conoce como el problema de la consistencia. El problema de coherencia de los datos que hemos analizado en el capítulo 3 también hace referencia a estas cuestiones, pero de manera más limitada. Recordemos que un protocolo de coherencia asegura que:

• los cambios que se efectúan en una variable en una determinada cache aparecerán en algún momento en todas las caches.

• los cambios que se efectúan en una variable aparecen en el mismo orden en todos los procesadores.

Así pues, al mantener la coherencia de los datos del sistema aseguramos que todos los procesadores van a observar todos los cambios que se produzcan en las variables compartidas. Sin embargo, no sabemos nada sobre el orden en que se verán los cambios producidos en variables diferentes.

5.1.3 Semántica de los programas y orden de ejecución de las instrucciones

Antes de nada, es necesario estar seguros de la semántica de los programas que se ejecutan en paralelo, entendidos como un todo, para evitar que los resultados obtenidos nos sorprendan, para lo que es necesario controlar adecuadamente el uso de las variables compartidas. El orden de ejecución de las instrucciones es especialmente importante para entender el comportamiento de un programa paralelo. Por ejemplo, ¿cuál será el resultado en P2 al ejecutar este programa paralelo (inicialmente, A = B = 0)? ¿Tiene un significado claro el programa?

P1 P2

A = 1; (wr1) B = 2; (wr2)

print B; (rd1) print A; (rd2)

Tenemos cuatro combinaciones de resultados posibles: BA = 00, 01, 21 y 20. Según el orden en que se intercalen las instrucciones a lo largo del tiempo, las tres primeras posibilidades pueden interpretarse correctamente. Por ejemplo, P2 imprimirá BA = 01 si se ejecutan las instrucciones en este orden en el tiempo:

Page 166: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 152 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

P1 P2

A = 1; ... B = 2; ...

... print B; ... print A;

En cambio, la cuarta combinación, BA = 20, parece “imposible”. Si B = 2, entonces A debería ser siempre 1. Sin embargo, esa combinación es posible si el control de P2 decide desordenar la ejecución de sus instrucciones (el modelo de ejecución habitual es desorden/desorden): dos lecturas en memoria pero sobre variables diferentes, es decir, completamente independientes desde su punto de vista. Por tanto, tal como está escrito, el programa anterior resulta muy ambiguo.

Como hemos analizado en el capítulo anterior, la semántica de los programas paralelos se asegura normalmente mediante operaciones de sincronización; de esa manera, en el siguiente ejemplo deberíamos obtener siempre A = 1 en P2.

P1 P2

A = 1; (wr1)

LISTO = 1; (wr2)

while (LISTO == 0) {}; (rd1) print A; (rd2)

A pesar de ello, podemos seguir teniendo problemas con el orden de las instrucciones. Las dependencias de datos del programa deberían asegurar el resultado correcto, pero, desgraciadamente, esas dependencias se producen entre programas (procesadores) diferentes, y no dentro del mismo programa.

wr1 (A) rd1 (LISTO) wr2 (LISTO) rd2 (A)

Si se respeta el orden de las instrucciones en cada procesador, es decir, el orden entre lecturas y escrituras en memoria (wr1 >> wr2; rd1 >> rd223), entonces se respetará también la dependencia wr1 → rd2, ya que tenemos que: wr1 >> wr2 → rd1 >> rd2 ⇒ wr1 → rd2. Pero si no (si el compila-dor o el hardware desordenan el código), podría ser que obtuviéramos A = 0. 23 Utilizamos el símbolo >> para indicar orden entre operaciones: A >> B indica que A debe ejecutarse

antes que B. El símbolo → indica una dependencia de datos: A → B indica que el dato producido por A se utiliza en B.

tiempo

Page 167: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.1 INTRODUCCIÓN ▪ 153 ▪

El problema de la ordenación de las instrucciones se observa también en este otro ejemplo (inicialmente, F1 = F2 = 0):

P1 P2

F1 = 1; if (F2 == 0) then < código > ...

F2 = 1; if (F1 == 0) then < código > ...

Las dependencias entre la instrucciones son las siguientes:

wr1 (F1) wr2 (F2)

rd1 (F2) rd1 (F1)

De acuerdo a la lógica secuencial, no es posible que ambos procesadores ejecuten el código de la rama then, ya que hay que respetar obligatoriamente la ordenación wr1 >> rd1 y wr2 >> rd2; pero si se desordena el código en cada procesador (para lo que no hay problema alguno, ya que no hay dependencias entre las instrucciones), es posible que ambos procesadores pasen a ejecutar dicho código.

Los cambios de orden que hemos citado son muy habituales en los sistemas de un solo procesador; más aún, son imprescindibles para lograr un rendimiento adecuado del sistema.

5.1.4 Atomicidad de las instrucciones

Las operaciones de memoria son atómicas si mientras se efectúan no se realiza ninguna otra operación en memoria. Además, la finalización de la operación debe entenderse en su sentido más amplio, incluyendo los efectos de la operación en el resto de los procesadores (por ejemplo, una escritura no termina hasta que se han anulado todas las copias de ese bloque en el sistema). Veamos un ejemplo.

P1 P2

A = 1; (wr1) LISTO = 1; (wr2)

while (LISTO == 0) {}; (rd1) print A; (rd2)

Page 168: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 154 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

Aunque se mantenga el orden de las instrucciones, seguimos teniendo un problema. El protocolo de coherencia nos asegura que los cambios efectuados en P1 llegarán a P2, pero nada nos dice sobre el orden en que llegarán, ya que se trata de dos variables diferentes24 Si el nuevo valor de LISTO llega a P2 antes que el de A, entonces se imprimirá A = 0. Esto puede ocurrir si las escrituras en memoria de P1 no son atómicas, y se continua con la ejecución del programa (wr2) antes de que finalice “por completo” (las consecuencias) la instrucción anterior. En definitiva: necesitamos saber cuándo finaliza una instrucción de memoria antes de poder empezar con otra.

El problema es incluso más general. En este ejemplo también aparece la necesidad de atomicidad (A = B = 0):

P1 P2 P3

A = 1;

while (A == 0) {}; B = 1;

while (B == 0) {}; C = A;

Cuando P1 escribe en A, el nuevo valor aparecerá en algún momento en P2 y P3. Al llegar a P2 se ejecutará B = 1, cuyo nuevo valor también llegará a P3. ¿Cuál de los dos cambios se hará efectivo en primer lugar en P3? Si el primero es el cambio producido en P2, entonces es posible que finalmente se ejecute en P3 C = 0 y no C = 1.

La atomicidad de las operaciones de memoria es un problema global, y debe mantenerse en cada procesador y en el sistema global.

5.1.5 Modelos de consistencia

El sistema paralelo debe ofrecer exactamente los mismos resultados que el de un solo procesador al ejecutar un determinado programa, es decir, debe ser consistente. Como hemos visto en los ejemplos anteriores, el problema corresponde a las operaciones de memoria (principalmente en el acceso a variables compartidas), debido a los cambios de orden y a la falta de atomicidad.

24 Los “mensajes / señales de control” enviados de un procesador a otro pueden llegar al destino en

desorden, en función de la red y de los protocolos de comunicación. Eso es muy claro en los sistemas de memoria distribuida, pero también puede darse en los sistemas SMP (con bus) en función del tipo de bus y del protocolo de comunicación.

Page 169: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.2 CONSISTENCIA SECUENCIAL (SC, sequential consistency) ▪ 155 ▪

Tanto los programadores de software del sistema como los de aplicaciones necesitan un modelo que especifique el orden y la atomicidad de las instrucciones que se ejecutan en paralelo, para saber qué optimizaciones pueden hacerse en el código y para poder interpretar adecuadamente el comportamiento de los programas. Un modelo de consistencia debe definir un espacio de memoria “coherente” para todos los procesadores, especificando las relaciones de orden que van cumplir las operaciones de memoria. En los próximos apartados vamos a presentar los principales modelos de consistencia, primeramente el modelo de consistencia secuencial, y luego los modelos relajados.

5.2 CONSISTENCIA SECUENCIAL (SC, sequential consistency)

Como ya hemos comentado, la consistencia no es un problema grave en los sistemas de un solo procesador: sólo existe un flujo de instrucciones y el orden de las instrucciones está bajo control. El compilador puede efectuar cambios en el orden de las instrucciones (por ejemplo, adelantar lecturas), y disponemos de hardware para parar el procesador y resolver las dependencias de datos.

El modelo de consistencia secuencial (SC) consiste en extender al multiprocesador el modelo de orden estricto de un procesador. Un multiprocesador es secuencialmente consistente si: (a) se mantiene el orden local de las instrucciones en cada procesador y (b) el orden de las instrucciones en todo el sistema (global) corresponde a un determinado entrelazado de las instrucciones de cada procesador.

El modelo SC es el que normalmente espera un programador, el más intuitivo. El modelo impone dos condiciones:

1. Hay que mantener el orden local en cada procesador. Esto implica que no se pueden desordenar las instrucciones LD y ST.

Hay que mantener, por tanto, las cuatro relaciones de orden siguientes, para cualquier dirección y en todos los procesadores:

wr >> rd; wr >> wr; rd >> rd; rd >> wr.

Por ejemplo, los dos primeros casos del siguiente ejemplo respetan el modelo SC, mientras que los otros dos no, porque no se mantiene el orden local (operaciones de memoria).

Page 170: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 156 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

P1 P2 En conjunto SC si SC no

a b

c d

a a a c b c d d c b c b d d b a

2. También hay que mantener el orden global, por lo que no puede ejecutarse ninguna instrucción de memoria hasta que finalice la anterior de cualquier procesador (y todas sus consecuencias). Para poder asegurar esta condición es necesario que las operaciones de memoria sean atómicas (write atomicity = todas las escrituras, en cualquier posición, deben aparecer en el mismo orden en cualquier procesador). No se admite, por tanto, esta situación:

En los ejemplos anteriores hemos puesto de manifiesto la necesidad de

estas condiciones. Así pues, con el modelo SC el problema de la consistencia desaparece de raíz, ya que se impone un orden estricto, tanto local como global, a todas las operaciones de memoria, junto con la atomicidad de dichas operaciones. El modelo de memoria es por tanto de orden estricto, y los programas paralelos se comportarán “tal como se espera”. En la figura aparece un esquema lógico de la estructura que impone este modelo.

5.2.1 Orden y atomicidad de las instrucciones de

memoria

Imponer orden local en un procesador es relativamente sencillo, al menos si sabemos cuándo ha terminado la operación anterior. La atomicidad en cambio es más complicada, dado que cada procesador utiliza una cache local. Para cumplir con el modelo SC se debe hacer lo siguiente:

tiempo

instrucción a

instrucción b

P P P P

MEM

orden

atomicidad

Page 171: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.2 CONSISTENCIA SECUENCIAL (SC, sequential consistency) ▪ 157 ▪

1. Hay que mantener el orden de las instrucciones LD y ST en cada procesador (sencillo de cumplir, ya que el orden de esas instrucciones corresponde a una sola unidad de control, la de cada procesador).

2. Además del orden, para poder asegurar la atomicidad, hay que esperar a que finalicen las operaciones de memoria de cada procesador antes de poder ejecutar la siguiente. El final de una lectura (LD) es simple de detectar: cuando se reciben los datos. Con las escrituras, en cambio, el problema es más complejo. Cuando se ejecuta un ST, el procesador no puede ejecutar otra instrucción de memoria hasta que la escritura y sus consecuencias (invalidación o actualización de las copias) en todos los procesadores se hayan ejecutado (write completion).

Para asegurar que se han efectuado todas las invalidaciones o actualizaciones es necesario complicar el protocolo de coherencia, añadiendo respuestas a dichas acciones: señales o mensajes de “confirmación” tipo ACK (acknowledgement). Tras actualizar sus copias, cada procesador “envía” un mensaje de ese tipo; al recibirse todos los mensajes, la operación se da por finalizada (problema: ¿cómo saber cuántas copias hay?)

3. Para asegurar la atomicidad hay que cumplir dos condiciones: (a) Por un lado, los cambios en una variable han de verse en el

mismo orden en todos los procesadores. Por ejemplo,

P1 P2 P3 P4

A = 2; B = 1;

A = 3; C = 1;

while (B ≠ 1) {}; while (C ≠ 1) {}; reg1 = A;

while (B ≠ 1) {}; while (C ≠ 1) {}; reg2 = A;

Si los cambios en A (2 y 3) llegan en distinto orden a P3 y P4, entonces el sistema no será consistente, ya que se asignarán valores diferentes a reg1 y reg2, con lo que la escritura de A no habrá sido atómica.

Cuando la red de comunicación del multiprocesador es un bus, el propio protocolo de coherencia (el snoopy) y una gestión adecuada del uso del bus permiten asegurar el orden de las escrituras. Como vamos a ver en un próximo capítulo, cuando se

1. INV

2. ACK 2. ACK

1. INV

Page 172: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 158 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

utilizan otro tipo de redes más generales, hay que utilizar directorios para mantener la coherencia de los datos y el orden de las escrituras.

(b) Y por otra parte, antes de ejecutar una operación de lectura hay que esperar a que finalice totalmente la última operación de escritura que se ejecutó sobre dicha variable (en general, en otro procesador), así como todas sus consecuencias.

Si el protocolo de coherencia es de invalidación, esta operación no es complicada: nuestra copia está invalidada, y por tanto tenemos que pedir una nueva copia, que recibiremos cuando haya terminado la operación. En cambio, si las copias se actualizan, el proceso de coherencia se vuelve más complejo: tras enviar la señal de actualización de la variable (1), hay que esperar a recibir todas las confirmaciones (2), tras lo cual hay que enviar una nueva señal indicando que ya se puede utilizar dicha variable (3), con lo que finaliza la operación de escritura. La complejidad del protocolo de actualización es la razón por la que no se suelen utilizar protocolos de este tipo.

5.2.2 Efectos en el hardware y en el compilador

Las condiciones que hemos impuesto para mantener la consistencia, orden y atomicidad, son muy “fuertes”: se complica el uso de la memoria cache y además no se pueden aplicar las optimizaciones más habituales en el caso de un solo procesador. Recuerda que mientras se efectúa una operación de memoria en un procesador, nadie puede efectuar otra operación en memoria, y las instrucciones de memoria vienen a representar un 25% - 35% del total.

Por ejemplo, debido a la necesidad de asegurar la atomicidad, no se pueden utilizar búferes de escritura, ya que ello supone en definitiva la posibilidad de adelantar las lecturas. De la misma manera, el compilador no puede efectuar las reordenaciones de código típicas, si con ello se modifica el orden de las operaciones de memoria. Y tampoco puede optimizarse el uso de la memoria mediante la utilización de registros, (para ahorrarnos operaciones LD/ST). Por ejemplo, esta optimización no funciona en un multiprocesador:

1. BC 1. BC

2. ACK 2. ACK

3. seguir 3. seguir

Page 173: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.3 MODELOS RELAJADOS (relaxed) ▪ 159 ▪

P1 P2 P1 P2 A = 1; B = A;

(2 ST / 1 LD)

A = 0;

r1 = 1; A = r1; B = r1;

(2 ST)

A = 0;

La variable B puede tomar los valores 0 o 1 en el programa original; en el segundo, en cambio, nunca se producirá el caso B = 0, ya que se ha eliminado la lectura de A (básicamente, un adelanto del LD). No es posible esa optimización en un modelo de consistencia secuencial.

Dado que el modelo impone condiciones muy restrictivas, podemos intentar no cumplir con alguna de ellas en determinados casos. Por ejemplo, podemos intentar ejecutar las instrucciones LD en modo especulativo, antes de que haya finalizado el anterior ST. Si finalmente todo va bien, seguiremos adelante sin más cuidados; pero si el bloque se ha anulado o actualizado en el camino, habrá que echar marcha atrás (roll-back, de manera similar a como se hace con las apuestas en los saltos). En todo caso, como vamos a ver, es posible mantener la consistencia del sistema en muchas situaciones sin utilizar tantas limitaciones.

5.3 MODELOS RELAJADOS (relaxed)

El conjunto de condiciones que impone en el modelo SC es suficiente para asegurar la consistencia, pero no estrictamente necesario. Además, desde el punto de vista de la eficiencia, las repercusiones sobre el sistema son grandes, al impedir muchas optimizaciones y obligar a esperar a la finalización global de las operaciones de memoria antes de comenzar una nueva. Por ello, y de cara a mejorar la eficiencia del sistema, se han propuesto varios modelos de consistencia más flexibles, en los que se eliminan algunas de las restricciones anteriores.

Analicemos las necesidades de orden de manera más fina. El orden de las instrucciones de memoria se reduce a estos cuatro casos:

rd >> rd25 rd >> wr 26 wr >> rd wr >> wr

25 Considerando que las caches no se bloquean en los fallos 26 Cuidado con los tres casos siguientes: si es la misma dirección, estamos ante un caso de dependencia

de datos.

Page 174: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 160 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

El modelo de consistencia secuencial impone el orden en los cuatro casos. Los modelos relajados, en cambio, permiten que no se respete alguno de ellos. Para definir un modelo de consistencia relajado hay que indicar:

• qué orden se respeta y cuál no entre las instrucciones de memoria. • si se cumple o no la atomicidad de las escrituras en memoria (ST), lo

que permitiría efectuar una lectura aunque no hayan concluido los efectos de la escritura anterior en todos los procesadores.

En todo caso, cuando se utiliza un modelo de consistencia relajado, tiene que existir siempre la posibilidad de dejar en suspenso las optimizaciones e imponer el orden estricto. Para ello se suelen utilizar nuevas instrucciones máquina del procesador (normalmente a través de funciones de biblioteca del sistema). Estas instrucciones se denominan barreras de ordenación (fence), y se utilizan como puntos de control. Una instrucción de este tipo impone un determinado orden en las instrucciones de memoria y asegura que las instrucciones posteriores no comienzan hasta que no hayan finalizado todas las anteriores.

Las instrucciones concretas tipo fence dependen del procesador en particular, y pueden llamarse MEMBAR, STBAR, SYNC... En general suelen ser de alguno de los siguientes tres tipos:

• Write-fence: para asegurar que todas las escrituras (ST) anteriores ha finalizado en todo el sistema antes de que comience ninguna escritura posterior (es decir, para imponer el orden wr >> wr).

• Read-fence: misma función que la anterior, pero con las lecturas (se utilizan normalmente para evitar el adelantamiento de los LD).

• Memory-fence: misma función, pero para ambas operaciones, rd y wr.

Por definición, si el modelo de consistencia es el secuencial, entonces todas las operaciones de memoria se tratan como instrucciones tipo fence.

5.3.1 Total Store Ordering (TSO) / Processor Consistency (PC)

El objetivo de esta primera optimización es “esconder” la latencia de las escrituras en memoria, y para ello se admite que se ejecute una instrucción LD aunque no haya finalizado un ST anterior; es decir, se permite el

Page 175: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.3 MODELOS RELAJADOS (relaxed) ▪ 161 ▪

adelantamiento de los LD: no se asegura el orden wr >> rd. La única diferencia entre los modelos TSO y PC es que en el caso del modelo Processor Consistency no se asegura que las operaciones de memoria sean atómicas.

El esquema de memoria correspondiente sería, esquemáticamente, el siguiente:

En el modelo TSO se utiliza una cola para las instrucciones ST (y SWAP,

T&S...), es decir, escrituras, donde se asegura el orden de dichas operaciones (FIFO). Las instrucciones LD, en cambio, pueden adelantar dicha cola (o cortocircuitar resultados), siempre que no haya una dependencia de datos. En cambio, un ST no puede adelantar nunca un LD, ni tampoco se pueden adelantar los LD entre sí. De esta manera, una instrucción LD bloquea el acceso a memoria de las siguientes instrucciones.

Por ejemplo, el significado de la ejecución de este programa es el siguiente en función del modelo de consistencia:

P1 P2

X = nuevo_valor; Y_copia = Y

Y = nuevo_valor; X_copia = X

SC → por lo menos una de ellas, Y_copia o X_copia, tendrá el valor nuevo. TSO → podría ocurrir que ni Y_copia ni X_copia tuvieran el nuevo valor.

Por definición, bajo el modelo TSO/PC no se mantiene la consistencia

secuencial y, por tanto, no se asegura que el comportamiento de los programas sea el “adecuado” en todos los casos. Tal vez sea necesario

búferes ST (FIFO)

LD ST

P P P P

MEM

ST ST ST

Page 176: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 162 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

imponer el orden estricto (SC) en algunos puntos del programa, para lo que habrá que utilizar las instrucciones especiales que hemos comentado (fence). Si el procesador no dispone de instrucciones de ese tipo, entonces pueden utilizarse instrucciones read-modify-write (por ejemplo, T&S) en lugar de los ST (LD) habituales, ya que esas instrucciones implican una lectura y una escritura, y por tanto no pueden desordenarse si el modelo de consistencia es TSO/PC:

ST ... LD → SWAP ... LD se pueden desordenar no se pueden desordenar

El modelo TSO es adecuado para aprovechar la latencia de las escrituras,

y bajo el mismo funciona bien la habitual sincronización mediante un flag: write A; write FLAG // read FLAG; read A. Ha sido utilizado en numerosas máquinas: Sequent Balance, Encore Multimax, (IBM 370), SparcCenter2000, SGI Challenge, Pentium Pro (PC), etc.

5.3.2 Partial Store Ordering (PSO)

En este modelo de consistencia, menos restrictivo que el anterior, se elimina también la restricción de orden entre escrituras; es decir, no se aseguran las relaciones de orden wr >> rd, wr. La implementación es similar a la del modelo anterior, pero las colas para las instrucciones ST no se gestionan en modo FIFO, con lo que no se asegura el orden de las escrituras.

Hay que tener cuidado, ya que al aplicarse este modelo puede no funcionar correctamente la típica sincronización productor/consumidor mediante un flag. Por tanto, hay que analizar con cuidado si merece la pena su aplicación, evaluando, como siempre, lo que esperamos ganar y lo que podríamos perder. Como en el caso anterior, en algunos momentos puede que sea necesario imponer orden a las operaciones de memoria (en este caso para mantener también el orden wr >> wr), para lo que se utilizan las instrucciones especiales de ordenación (fence).

Este modelo de consistencia se ha utilizado, por ejemplo, en el Sun Sparc PSO.

Page 177: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.3 MODELOS RELAJADOS (relaxed) ▪ 163 ▪

5.3.3 Modelos más relajados

El problema del orden (consistencia) de las instrucciones de memoria sólo aparece en los accesos a variables compartidas, y no en el resto. Más aún; en los siguientes dos casos, por ejemplo, no sería necesario asegurar el orden en todos los accesos a memoria:

P1 P2 P1 / P2 / ... / Pn

X = X + 1; Y = B + 1; flag = 1; ...

... while (flag == 0) {}; A = X / 2; B = Y;

... lock(cer); yo = i; i = i + N; j = j - 1; unlock(cer); ...

En realidad, bastaría con asegurar el orden en relación a las operaciones de sincronización; asegurado eso, da igual en qué orden se ejecuten el resto de las operaciones de memoria. (p.e., sólo habrá un procesador en la sección crítica).

Decimos que se utiliza programación sincronizada si el uso de las variables compartidas se “protege” mediante operaciones de sincronización, tal como aparece en los ejemplos anteriores. En caso contrario, es posible que aparezcan “carreras de datos” (data-races), haciendo que los resultados obtenidos dependan, por ejemplo, de la velocidad del procesador. Por eso, la mayoría de los programas paralelos utilizan alguna función de sincronización para “ordenar” el acceso a las variables compartidas: funciones lock y unlock, flags, etc.

De ser así, para mantener la consistencia (en su sentido más intuitivo), bastaría con asegurar el orden de las operaciones de memoria con relación a las de sincronización, junto con el de las de sincronización entre sí.

Nos interesa, por tanto, distinguir los accesos “estándar” a memoria (rd, wr) y los accesos a variables de sincronización (s). Así, junto con las relaciones de orden entre operaciones rd y wr, tendremos que mantener también estas otras:

rd, wr >> s s >> rd, wr s >> s

Para aplicar un tratamiento específico a las operaciones de sincronización, habrá que identificarlas convenientemente (por hardware y/o por software).

Page 178: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 164 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

5.3.3.1 Weak Ordering (WO)

En el modelo de consistencia Weak Ordering se admite cualquier orden en las operaciones de memoria que no sean de sincronización, mientras que se impone orden estricto a estas últimas (que se van a comportar como si fueran instrucciones de tipo fence). En resumen, antes de ejecutar una operación de sincronización hay que esperar a la finalización (global) de todas las operaciones de memoria anteriores; de igual manera, las operaciones de memoria posteriores deberán esperar a que finalice por completo la operación de sincronización.

Estas son pues las relaciones de orden a mantener:

rd / wr >> s; s >> rd / wr; s >> s.

Como en los dos casos anteriores, si se necesita imponer el orden estricto en una determinada zona del programa, una de dos: o se usan instrucciones fence o, si no existe esa posibilidad, se identifican como operaciones de sincronización las instrucciones LD o ST correspondientes.

5.3.3.2 Release Consistency (RC)

Se trata del modelo de consistencia más flexible. Como en el caso anterior, son las operaciones de sincronización las que van a marcar los puntos de ordenación del programa; entre ellas, los LD y ST pueden ejecutarse en cualquier orden. Pero además, las operaciones de sincronización se dividen en dos tipos: adquisición (acquire, sa) y liberación (release, sr). Las operaciones sa son lecturas (u operaciones RMW), y las operaciones sr escrituras (u operaciones RMW). Por ejemplo, una función de lock es una operación de sincronización de tipo acquire, mientras que unlock es de tipo release.

Junto con el orden entre operaciones de sincronización (s >> s), se deben mantener estos otros:

• las operaciones de memoria posteriores a operaciones de sincronización tipo adquisición (acquire) deben esperar a que terminen éstas; es decir, hay que mantener el orden sa >> rd / wr.

rd ...wr ...

sinc

rd ...wr ...

sinc

rd ...wr ...

Page 179: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.3 MODELOS RELAJADOS (relaxed) ▪ 165 ▪

• antes de ejecutar una operación de sincronización tipo liberación (release), el procesador debe esperar a que finalicen todas la operaciones de memoria anteriores; es decir, hay que mantener el orden rd / wr >> sr.

Estos dos últimos modelos de consistencia son adecuados en los casos de

planificación dinámica de las instrucciones (desorden/desorden), puesto que se acepta la finalización en desorden de las instrucciones LD y el adelantamiento de los ST. Los procesadores Alpha, IBM PowerPC, MIPS utilizan un modelo de consistencia de este tipo (en muchos casos no se aplica ningún modelo concreto, y se deja al usuario que defina el modelo que desea mediante el uso de instrucciones fence).

En la siguiente tabla se resumen las características principales de los

modelos de consistencia.

Modelo Orden de las operaciones de memoria Instrucc. para

imponer orden wr>>rd wr>>wr rd>>rd/wr sinc. wr atom.

SC todas

TSO todas MEMBAR, RMW

PC todas MEMBAR, RMW

PSO todas STBAR, RMW

WO todas SYNC

RC sa >> w/r w/r >> sr s >> s

REL, ACQ, RMW

rd ...wr ...

s_acq

rd ...wr ...

s_rel

rd ...wr ...

Page 180: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 166 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

En el ejemplo de la siguiente figura aparecen remarcadas las restricciones de orden que impone cada modelo.

SC

wr,rd,s >> wr,rd,s TSO/PC

– wr >> rd PSO

– wr >> wr WO RC

– rd >> wr, rd

rd

wr

sinc_a

wr

rd

sinc_r

wr

wr

= A

B =

sinc_acq

C =

= D

sinc_rel

E =

F =

= A

B =

sinc_acq

C =

= D

sinc_rel

E =

F =

= A

B =

sinc_acq

C =

= D

sinc_rel

E =

F =

= A

B =

sinc_acq

C =

= D

sinc_rel

E =

F =

= A

B =

sinc_acq

C =

= D

sinc_rel

E =

F =

5.4 RESUMEN Y PERSPECTIVAS

Para que los programas paralelos tengan una semántica clara, tanto el hardware como el programador necesitan que el multiprocesador tenga una “imagen de memoria” bien definida. A la imagen o interfaz de memoria del multiprocesador se le denomina modelo de consistencia.

Existen dos tipos de modelos de consistencia: el secuencial y los relajados. El primero, SC, impone el orden local y global de todas las operaciones de memoria, así como la atomicidad de las mismas. Los modelos relajados, en cambio, permiten el desorden de algunas de esas operaciones; por ejemplo, pueden adelantarse los LD (TSO), o los LD y los ST (PSO), o puede admitirse cualquier orden entre ellas pero respetando el orden con relación a las operaciones de sincronización (WO). Cuando se utilizan modelos de consistencia relajados, en algunos casos es necesario imponer el orden estricto, para lo que se utilizan instrucciones especiales denominadas fence.

Page 181: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

5.4 RESUMEN Y PERSPECTIVAS ▪ 167 ▪

Si consideramos el rendimiento del sistema, los modelos relajados debieran ser más eficientes que el modelo estricto SC, ya que en este caso no pueden aplicarse muchas de las optimaciones más habituales, y, en consecuencia, la eficiencia debiera ser menor. Pero como siempre, debemos analizar los aspectos positivos y negativos de la aplicación de modelos de consistencia relajados, ya que para poder aplicar estos modelos se necesita la colaboración del hardware y del software (nuevas instrucciones, identificar correctamente los puntos de ordenación dentro del programa, etc.).

Uno de los investigadores principales de estas cuestiones es Mark Hill. En su opinión, los multiprocesadores deberían utilizar SC como modelo básico, y, tal vez, ofrecer como alternativa un modelo relajado. ¿Por qué?

En los procesadores actuales es habitual el uso de ejecución especulativa de las instrucciones. Las instrucciones se ejecutan sin estar seguro de que hay que hacerlo. Cuando su ejecución se convierte en segura, se escriben los resultados, las instrucciones se dan por finalizadas y se retiran del procesador (commit); en caso contrario, si se comprueba que no había que haberlas ejecutado, entonces hay que deshacer el efecto de esas instrucciones (en muchos casos, ejecutando procedimientos de roll-back), y volver a un punto “seguro” en la ejecución del programa.

Siendo eso así, aunque el modelo de consistencia sea SC podrían aplicarse las optimizaciones habituales, en la medida en que se haga de manera especulativa; si no resultan adecuadas, tendremos la posibilidad de deshacerlas. ¿En qué se diferenciarían entonces ambos tipos de consistencia? Pues en que, en el caso de los modelos relajados, las instrucciones se retirarían antes del procesador, ya que no habría que esperar a saber si la reordenación efectuada ha sido correcta o no.

Siempre es necesario medir las hipotéticas ventajas de cualquier tipo de estrategia utilizando programas reales o bancos de pruebas. Algunos experimentos realizados muestran que los tiempos de ejecución pueden llegar a ser un 10% - 20% menores en el caso de los modelos relajados que en el modelo SC. ¿Merece la pena esa mejora? ¿Aceptan los diseñadores de middleware (software del sistema, aplicaciones en bajo nivel...) la complejidad inherente al uso de modelos de consistencia relajados? Por ejemplo, corresponde a los diseñadores de compiladores introducir las instrucciones fence (las estrictamente necesarias y no más, para no perder eficiencia); para facilitar la portabilidad del software hay que implementar adecuadamente todos los modelos para poder trabajar en plataformas

Page 182: COMPUTADORES PARALELOS - Gipuzkoako Campusa · COMPUTADORES PARALELOS. Computación de Alta Velocidad . A. Arruabarrena — J. Muguerza . Konputagailuen Arkitektura eta Teknologia

▪ 168 ▪ Capítulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

hardware diferentes; etc. Programar en paralelo es difícil en sí mismo, y más aún si hay que considerar modelos relajados de consistencia.

En resumen: el modelo SC es el estándar en todos los multiprocesadores, los problemas de consistencia se resuelven en hardware y son transparentes para el programador. Como segunda alternativa, el modelo TSO parece el adecuado para poder aplicar las optimizaciones más habituales (adelantar los LD) y sus efectos sobre el programador son bajos. Los modelos que eliminan cualquier restricción en el orden de las operaciones de memoria parecen más difíciles de justificar.