Sei sulla pagina 1di 182

COMPUTADORES

PARALELOS
Computacin de Alta Velocidad

A. Arruabarrena J. Muguerza
Konputagailuen Arkitektura eta Teknologia saila
Informatika Fakultatea Euskal Herriko Unibertsitatea

COMPUTADORES
PARALELOS
Computacin de Alta Velocidad

A. Arruabarrena J. Muguerza
Konputagailuen Arkitektura eta Teknologia saila
Informatika Fakultatea Euskal Herriko Unibertsitatea

septiembre 2012

NDICE

Introduccin ......................................................................... 1
Captulo 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-mquina ...................................................................... 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 ejecucin 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 mdulos de memoria ........................................ 22
1.3.2.1 Una sola operacin 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 clculo de los computadores vectoriales ............................ 30


1.4.1 Velocidad de clculo en funcin 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 aceleracin ....................................................... 33
1.4.1.3 Nv................................................................................................................. 34
1.4.2 Influencia del cdigo escalar. Ley de Amdahl. .............................................. 34

1.5 Tcnicas de compilacin para generar cdigo vectorial ........................... 37


1.5.1 Dependencias de datos entre instrucciones .................................................. 38
1.5.2 Vectorizacin ......................................................................................................... 40
1.5.2.1 Vectores de una dimensin .................................................................. 40
1.5.2.2 Vectores de N dimensiones ................................................................. 44
1.5.2.3 Condicin para vectorizar un bucle ................................................... 45
1.5.2.4 Test de dependencias ............................................................................ 46
1.5.3 Optimizaciones...................................................................................................... 50
1.5.3.1 Sustitucin global hacia adelante (global forward substitution) ...................50
1.5.3.2 Eliminacin de las variables de induccin......................................... 51
1.5.3.3 Antidependencias (DR, WAR) ...................................................................... 52

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 Expansin escalar (scalar expansion) ........................................................ 56
1.5.3.7 Fusin 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 mscara y vectores de ndices ................................................... 60
1.5.4.1 Uso de mscaras ..................................................................................... 60
1.5.4.2 Vectores de ndices ................................................................................ 61

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

Captulo 2. Computadores Paralelos (conceptos bsicos) ................. 69


2.1 Introduccin ......................................................................................................... 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 lgicamente compartida pero fsicamente 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

Captulo 3. Coherencia de los Datos en los Computadores

SMP ...................................................................................... 83

3.1 Presentacin del problema y revisin 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 comparticin ................................................................................................ 86
3.1.4 Definicin de la coherencia................................................................................ 87

3.2 Protocolos de coherencia snoopy .................................................................. 88


3.2.1 Estados de los bloques en la memoria cache y seales de control.......... 90
3.2.2 Protocolos de invalidacin ................................................................................. 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 invalidacin................................... 100
3.2.3 Protocolos de actualizacin ............................................................................. 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

vii

NDICE

3.3. Implementacin de los protocolos snoopy ................................................ 105


3.3.1 Problemas ............................................................................................................. 105
3.3.1.1 Directorio de la memoria cache........................................................ 106
3.3.1.2 Bferes de escritura .............................................................................. 107
3.3.1.3 Protocolo de peticin 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, seales BRQ y BGN ..................... 110
3.3.2.2 Deadlock, livelock, starvation ............................................................ 112

3.4 Snoopy jerrquico ............................................................................................. 113


3.4.1 Lecturas.................................................................................................................. 115
3.4.2 Escrituras ................................................................................................................ 116

Captulo

4. Sincronizacin de Procesos en los Computado-

res SMP.............................................................................. 119

4.1 Introduccin ....................................................................................................... 119


4.2 Exclusin mutua (mutual exclusion) ............................................................. 123
4.2.1 Instrucciones Test&Set y Swap ........................................................................ 125
4.2.1.1 Instruccin Test&Set ............................................................................. 125
4.2.1.2 Instruccin Swap ................................................................................... 125
4.2.1.3 Anlisis del trfico ................................................................................. 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 caractersticas ................................................................ 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 Instruccin 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 trfico .................................................................. 137
4.2.4.1 Tickets ...................................................................................................... 137
4.2.4.2 Vectores de cerrojos ............................................................................ 139

4.3 Sincronizacin "punto a punto" mediante eventos ................................... 141


4.4 Sincronizacin 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

Captulo 5. Consistencia de la Memoria en los Computadores Paralelos ................................................................ 149


5.1 Introduccin ....................................................................................................... 149

viii

NDICE

5.1.1 Sistemas de un solo procesador...................................................................... 149


5.1.2 Sistemas multiprocesador ................................................................................. 150
5.1.3 Semntica de los programas y orden de ejecucin 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 ms relajados ...................................................................................... 163
5.3.3.1 Weak Ordering (WO) .......................................................................... 164
5.3.3.2 Release Consistency (RC) ................................................................... 164

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

Captulo

6 La Red de Comunicacin de los Computadores


Paralelos. Comunicacin mediante Paso de
Mensajes. .......................................................................... 169

6.1 Introduccin ....................................................................................................... 169


6.2 Topologa 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
6.3.3.2
6.3.3.3
6.3.3.4
6.3.3.5
6.3.3.6

La red Omega ........................................................................................ 176


Encaminamiento en la red Omega ................................................... 178
Conflictos de salida y bloqueos ......................................................... 179
Otro patrn de comunicacin: broadcast. ..................................... 181
Otras redes ............................................................................................. 181
Resumen .................................................................................................. 183

6.4 Redes formadas por encaminadores de mensajes .................................... 184


6.4.1 Encaminadores de mensajes ............................................................................ 184
6.4.2 Topologas de red ms utilizadas .................................................................... 185
6.4.2.1 Redes de una dimensin: 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 topologas ....................................................................... 191
6.4.2.6 Los enlaces fsicos ................................................................................. 193

6.5 La comunicacin a travs de la red en los sistemas paralelos ............... 193


6.5.1 Los mensajes ........................................................................................................ 194

ix

NDICE

6.5.2 Patrones de comunicacin: con quin y cundo hay que


efectuar la comunicacin. ................................................................................. 195
6.5.3 Construccin 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 Eleccin del camino: esttico o adaptativo .................................... 203
6.5.5 Control del flujo de informacin ..................................................................... 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 bferes ................................. 210
6.5.6 Eficiencia de la comunicacin: latencia y throughput ............................... 214
6.5.6.1 Tiempo de comunicacin en la red .................................................. 215
6.5.6.2 Considerando el trfico en la red ...................................................... 217
6.5.6.3 Clculo del throughput mximo ........................................................ 219
6.5.6.4 Anlisis global ......................................................................................... 221
6.5.7 Problemas de la comunicacin ....................................................................... 222
6.5.7.1 Deadlock (interbloqueos) Canales virtuales. Giros
controlados (Turn model). Control de la inyeccin de
paquetes. Utilizacin de caminos seguros ..................................... 222
6.5.7.2 Problemas de livelock y starvation.................................................... 229
6.5.8 Protocolos de comunicacin ........................................................................... 230

6.6 Evolucin de los computadores paralelos ................................................... 232


Apndice. Clculo de las distancias medias en diferentes topologas .......... 235

Captulo

7. Coherencia de los Datos en los Computadores

DSM.................................................................................... 241

7.1 Introduccin ....................................................................................................... 241


7.2 Directorios de coherencia ............................................................................... 243
7.2.1 Introduccin y clasificacin ................................................................................ 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 Optimizacin del trfico de coherencia........................................................ 254
7.2.4 Atomicidad de las operaciones: carreras ...................................................... 257

7.3 Implementacin 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 Actualizacin de la memoria principal ............................................ 268
7.3.2 El protocolo de coherencia estndar SCI en la mquina NUMA-Q
de Sequent. ........................................................................................................... 269

NDICE

7.3.2.1
7.3.2.2
7.3.2.3
7.3.2.4
7.3.2.5

SCI: estados y operaciones ................................................................. 270


Lecturas .................................................................................................... 272
Escrituras .................................................................................................. 273
Actualizacin de la memoria principal ............................................ 277
Atomicidad y carreras .......................................................................... 277

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

Captulo

8. Paralelizacin de Bucles y Planificacin de

Tareas ................................................................................. 281

8.1 Introduccin ....................................................................................................... 281


8.1.1 Ideas bsicas sobre paralelizacin de bucles ............................................... 287

8.2. Estructuras bsicas 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 (sincronizacin global .................................................. 292
8.2.2.2 Bucles doacross (sincronizacin punto a punto) .......................... 293
8.2.3 Efecto de las antidependencias y de las dependencias de salida ........... 298
8.2.4 Atencin con las instrucciones if..................................................................... 299

8.3 Implementacin de la sincronizacin........................................................... 300


8.3.1 Sincronizacin 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
8.4.2
8.4.3
8.4.4
8.4.5
8.4.6
8.4.7

Eliminacin del efecto de las dependencias que no son esenciales ...... 304
Fisin de bucles ................................................................................................... 305
Ordenacin de las dependencias ................................................................... 306
Alineacin de las dependencias (peeling) .................................................... 307
Extraccin de threads independientes (switching) ..................................... 309
Minimizacin de las operaciones de sincronizacin ................................. 310
Tratamiento de bucles (reordenacin...) ........................................................ 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 Planificacin de bucles (scheduling) ............................................................... 316
8.5.1 Reparto de las iteraciones: consecutivo o entrelazado............................. 317
8.5.2 Planificacin esttica o dinmica .................................................................... 318
8.5.2.1 Planificacin esttica ............................................................................ 319
8.5.2.2 Planificacin dinmica: autoplanificacin (self/chunk
scheduling), autoplanificacin guiada (GSS) y trapezoidal
(trapezoid self scheduling) ........................................................................... 319

8.6 Secciones paralelas: Fork / Join ..................................................................... 323


8.7 Anlisis del rendimiento................................................................................... 325

xi

NDICE

Captulo

9. Computadores Paralelos de Alto Rendimiento.

Programacin Paralela: OpenMP y MPI


(introduccin). ..................................................................... 327

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


9.2 Programacin Paralela: OpenMP y MPI (introduccin) .......................... 332
9.2.1 OpenMP ................................................................................................................ 334
9.2.2 MPI ......................................................................................................................... 337

Introduccin

Qu tiempo har maana en esta ciudad? Cmo evolucionan las


galaxias? Cmo interaccionan los electrones en una molcula de clorofila?
Se comportarn de manera adecuada las alas de un avin en una
turbulencia? Para dar respuesta adecuada a esas y otras muchas preguntas,
cientfico/as e ingeniera/os utilizan potentes computadores, la herramienta
principal de cualquier laboratorio en la actualidad. Las aplicaciones tcnicocientficas requieren de grandes cantidades de clculo, casi de manera
ilimitada, y adems hay que obtener resultados en el menor tiempo posible,
(prever maana las lluvias torrenciales de hoy no sirve para mucho). A pesar
del espectacular incremento en la velocidad de clculo de los procesadores,
las necesidades van siempre muy por delante. A lo largo de la evolucin de
los computadores tres han sido las lneas principales que han permitido

INTRODUCCIN

aumentar de manera continuada la velocidad de los mismos: los avances en


la tecnologa electrnica, el desarrollo de nuevas estructuras o arquitecturas
de computadores, y el uso de tecnologas del software (compiladores, etc.)
cada vez ms eficientes.
Mediante la tecnologa electrnica se ha conseguido integrar en un slo
chip una cantidad ingente de transistores: hoy en da por encima de 1000
millones (y cada vez ms). A consecuencia de este avance, cada vez son ms
las "partes" del computador que se van integrando en un solo chip junto con
el procesador: unidades funcionales especficas, registros, memoria cache, e
incluso mltiples ncleos (core). Del mismo modo, la frecuencia del reloj
del procesador es cada vez ms alta (aunque la carrera para usar relojes cada
vez ms rpidos 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 ms 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 ejecucin de las
instrucciones es segmentada y se intenta ejecutar ms de una instruccin
cada ciclo); la jerarqua de memoria cache permite accesos ms rpidos, los
registros se organizan para optimizar el uso de los datos, etc.
Las tcnicas de compilacin tambin 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
rpidos que llegan a superar la velocidad de clculo de 10 Gflop/s para
muchas aplicaciones, tales como previsiones meteorolgicas, simulaciones
de procesos fsicos y qumicos, diseos de aeronutica, prospecciones
geolgicas, diseo de nuevos materiales, desarrollos diversos en ingeniera,
avances en biologa, gentica 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 aos, situndose en torno al 20%: la
velocidad que se puede conseguir con un procesador est llegando a sus
lmites fsicos (y econmicos). Por tanto, se necesita de desarrollar otro tipo
de estrategias para conseguir las velocidades de clculo Teraflop/s,
Petaflop/s, es decir, 1012, 1015 operaciones de coma flotante por segundo
que demandan las aplicaciones citadas.

INTRODUCCIN

El paso que hay que dar es bastante claro: utilizar muchos procesadores,
para repartir la ejecucin de un programa entre ellos; es decir, utilizar
sistemas paralelos. Adems, las tecnologas de fabricacin facilitan esta
posibilidad: construido un procesador (chip), se hacen fcilmente miles de
ellos y de manera relativamente barata. Por tanto, por qu no utilizar 100,
1000, 10 000... procesadores para resolver un problema? Tericamente, y si
supiramos cmo hacerlo, utilizando P procesadores podramos ejecutar un
programa P veces ms rpido. Por desgracia, esto no va a ser as, ya que van
a aparecer importantes problemas nuevos: cmo se reparte el trabajo entre
los procesadores? son independientes los procesos o hay que
sincronizarlos? cmo se implementa la comunicacin entre procesadores?...
Existen muchas maneras de estructurar un computador de P procesadores.
Algunas caractersticas sern comunes en todos ellos, y otras, en cambio, no.
Existen diferentes formas de clasificar estas arquitecturas o estructuras. De
entre ellas, la ms conocida o utilizada es, seguramente, la de Flynn (1966),
quizs por lo simple que es. En esta clasificacin se tienen en cuenta dos
parmetros: el nmero de flujos de instrucciones (es decir, el nmero de PCs
o contadores de programa) y el nmero de flujos de datos que operan
simultneamente. La siguiente figura recoge dicha clasificacin:
flujos de datos

uno

flujos de
instrucciones
muchos

uno

muchos

SISD

SIMD
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 clsicos de un slo
procesador (ordenadores personales, estaciones de trabajo). Aunque
en algunos casos dispongan de ms 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 bsqueda, descodificacin,
lectura de operandos, memoria, unidad aritmtica, escritura de
resultados, y en cada fase habr una instruccin (o varias, en el
caso de los procesadores superescalares). As pues, se utiliza

INTRODUCCIN

paralelismo a nivel de instruccin (ILP, Instruction Level


Parallelism). Adems, el procesador (con ayuda del hardware o del
compilador) es capaz de modificar el orden de ejecucin 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 simultneamente 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
mquinas de tipo SIMD, sobre todo en los aos 80-95, y para ciertas
aplicaciones, tales como clculo numrico, procesamiento de seal,
etc., ofrecen muy buen rendimiento.
Sin embargo, hoy en da 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 mquinas SISD, ya que los programas que
se ejecutan no son independientes.
Este es el modelo que permite obtener elevadas velocidades de
cmputo: computadores de paralelismo masivo, en los que P
procesadores (un nmero alto) colaboran en la resolucin 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.

INTRODUCCIN

Tal y como veremos en los prximos captulos, podemos hacer una


subclasificacin en el grupo de las mquinas 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 comunicacin 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
comunicacin entre procesos se realiza mediante paso de
mensajes.

A lo largo de los captulos del texto vamos a analizar las mquinas


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 especfica, destinada
al procesamiento de vectores, que ha conseguido un lugar destacado en la
historia de la computacin. En el captulo 2 haremos una breve presentacin
de los sistemas paralelos: principales modelos y arquitecturas, problemas
ms importantes, la ley de Amdahl, etc. En el captulo 3 analizaremos el
problema de la coherencia de los datos en sistemas SMP; en el 4, las
instrucciones y procedimientos bsicos 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 captulo 6,
analizaremos la topologa, estructura y funcionamiento de la red de
comunicacin de un sistema paralelo, as como la eficiencia de los
mecanismos de comunicacin entre procesadores. En el captulo 7
analizaremos nuevamente el problema de la coherencia de los datos, pero en
los sistemas DSM: los directorios de coherencia. Dedicaremos el captulo 8 a
presentar las tcnicas de paralelizacin de bucles y el reparto de tareas a los
procesadores. Finalmente, en el captulo 9 haremos un breve resumen de la
situacin actual de los sistema paralelos, analizando la lista top500, as como
una breve presentacin de las herramientas bsicas 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).

Computadores Vectoriales

1.1

QU ES UN COMPUTADOR VECTORIAL?

Como hemos comentado en la introduccin, las arquitecturas de tipo


MIMD son las ms adecuadas para resolver en paralelo aplicaciones de tipo
general. Existen, sin embargo, algunos problemas importantes, desde el
punto de vista del clculo 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 clculo cientfico la mayor parte
del tiempo de ejecucin se invierte en la ejecucin 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 ejecucin de ese


bucle ser muy alto, a pesar de su estructura tan simple. Si lo ejecutamos en
un procesador escalar, el cdigo ensamblador ser, por ejemplo, el siguiente:

Captulo 1: COMPUTADORES VECTORIALES

buc:

FLD
FLD
FADD
FST

F1,A(R1)
F2,B(R1)
F3,F2,F1
C(R1),F3

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

En un procesador escalar se ejecutara, en el mejor de los casos, una


instruccin por ciclo 1, por lo que para ejecutar una iteracin del bucle se
necesitaran 7 ciclos; por tanto, el tiempo de ejecucin de todo el programa
sera de TE = 7N.
El bucle anterior tiene dos caractersticas especficas. 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 parmetros:
direccin de comienzo: direccin de memoria del primer elemento
del vector.
longitud: nmero 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 posicin de memoria) se definira as:
direccin 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 Ingeniera es muy comn el uso de
vectores y el tiempo de ejecucin se invierte, principalmente, en la
ejecucin, una y otra vez, de bucles como el anterior. Por qu no definir una
arquitectura y un lenguaje mquina 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, quizs se podra conseguir algo ms de una instruccin por ciclo.

1.1

QU ES UN COMPUTADOR VECTORIAL?

LV
LV
ADDV
SV

V1,A(R1)
V2,B(R1)
V3,V1,V2
C(R1),V3

; leer el vector A
; leer el vector B
; sumar ambos vectores
; escribir el resultado en el vector C

En este nuevo juego de instrucciones, la instruccin LV V1,A(R1)


implicara lo siguiente (utilizando, a modo de ejemplo, el esquema de
segmentacin que se muestra 2):
LV V1,A(R1)

BD

AM

...

...

...

Podramos representar la ejecucin anterior, de manera simplificada, de la


siguiente forma:
LV V1,A(R1)

BD

AM

...

...

As pues, mediante una nica instruccin 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 algn otro impedimento, en cada ciclo
proporcionar un elemento del vector, que se irn escribiendo en un registro
vectorial.
El siguiente esquema presenta la ejecucin del programa anterior fase a
fase (las latencias de las unidades funcionales son un simple ejemplo):
LV V1,A(R1)

BD L AM M

LV V2,B(R1)

...

BD L AM M

...

BD .

...

BD L AM .

...

...

10

11

12

13

14

15

...

... 14+N

ADDV V3,V1,V2
SV C(R1),V3
1

ti

(N ciclos)

...

(N ciclos)

...
(N ciclos)

...

(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 ejecucin habituales: BD, bsqueda y descodificacin de la instruccin; L, lectura de los
operandos; AM, clculo de la direccin de memoria; A, una operacin en una unidad funcional; M, una
operacin en memoria; E, escritura del resultado en los registros. Cada instruccin utiliza solamente
las fases que necesita para su ejecucin.

10

Captulo 1: COMPUTADORES VECTORIALES

Si el modelo de ejecucin es ese, podemos hacer un anlisis sencillo para


obtener el tiempo de ejecucin del bucle (de manera simplificada; un poco
ms adelante formalizaremos este clculo): existe un tiempo de inicio ti
antes de que la ltima instruccin comience a escribir, y despus, para
terminar la ejecucin, se necesitan N ciclos, uno por cada elemento del
vector. Por tanto:
T V = ti + N
Si comparamos esta expresin con la que hemos obtenido para un
procesador escalar, la mejora es clara. Por ejemplo, si el nmero de
elementos de los vectores es N = 128, y si ti = 14 ciclos, tendramos los
siguientes tiempos de ejecucin:
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 control 3 debida al bucle, ya que, por definicin, ha
desaparecido el propio bucle. Por otro lado, slo se han ejecutado 4
instrucciones, y no las 7N que componan el bucle escalar. Esto implica que
el uso de la cache de instrucciones es mucho ms bajo, y, por consiguiente,
el trfico en el bus tambin.
Pero, por supuesto, todas esas ventajas no salen gratis. A decir verdad,
tenemos que analizar con ms detalle el esquema de ejecucin 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 estn
utilizando simultneamente la memoria y, adems, cada instruccin 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, despus
de la instruccin de direccin i se ejecuta la instruccin de direccin i+1, salvo en el caso de los
saltos. Cuando ejecutamos un salto no sabemos qu instruccin ser la siguiente hasta que el salto
termine, por lo que hay que parar al procesador (aunque existen muchas tcnicas para evitar esos
ciclos "muertos").

1.1

QU ES UN COMPUTADOR VECTORIAL?

11

1. Cuntos buses hay para acceder a memoria? El procesador y el


sistema de memoria se comunican mediante el bus de datos. Una
operacin vectorial de memoria va a ocupar el bus de datos durante N
ciclos (supongamos que se transfiere una palabra por ciclo). Por tanto,
si slo hubiera un bus, slo una instruccin podra acceder a memoria
en cada momento, y todas las dems deberan esperar a que sta
terminara. Por consiguiente, el tiempo de ejecucin no sera de orden
N, sino de kN (con k = 2, 3, 4..., nmero de instrucciones de memoria).
2. No habr conflictos en el uso de los mdulos de memoria? A pesar
de que el espacio de memoria est entrelazado entre los diferentes
mdulos de memoria, puede suceder que en un momento determinado
se necesite acceder a elementos de vectores almacenados en el mismo
mdulo. Si sucede esto, para poder comenzar un acceso habr que
esperar a que termine el anterior acceso al mismo mdulo, con lo que
aumentar el tiempo de ejecucin.
Queda claro que el sistema de memoria de un computador vectorial juega
un papel muy importante en el rendimiento final del sistema: hacen falta
mltiples buses, y la memoria debe estar entrelazada en muchos mdulos,
para reducir los conflictos de acceso a los mismos.

1.1.1.2

Unidades funcionales vectoriales

Analizando el esquema de ejecucin anterior, queda claro que las


unidades funcionales deben estar segmentadas. Una nica instruccin
(ADDV, por ejemplo) realiza N operaciones en la unidad funcional, una por
ciclo. Si no estuviera segmentada, no sera posible generar un dato por ciclo.
De la misma manera, parece necesario poder disponer de varias unidades
funcionales de cada tipo, ya que una instruccin ocupa cada unidad
funcional durante N ciclos.

1.1.1.3

Registros vectoriales

Qu es un registro vectorial? De qu tamao son? Cmo 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
tamao 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 sera de 64 (o 128) 64 = 4 (u 8) kilobits. Si nos fijamos

12

Captulo 1: COMPUTADORES VECTORIALES

en el tamao, se comprende fcilmente que no se disponga de un nmero


muy elevado de registros vectoriales. Normalmente dispondremos de 8-16
registros (16 8 = 128 kilobits). En algunas mquinas, el tamao 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.

registros vectoriales

U.F.

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


procesar es mayor que Lmax (64 o 128 elementos)? No hay ms remedio que
formar un bucle, y en cada iteracin 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 da, 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 irn
produciendo datos para escribir en el banco de registros vectoriales, y que
cada una de ellas necesitar muchos ciclos para escribir el vector resultado,
sern necesarios varios (muchos) buses de escritura (evidentemente, tambin
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 estn optimizados para procesar vectores, pero
en los programas reales, adems de procesar vectores, habr que procesar
cdigo escalar. Cmo se hace eso? Qu influencia tiene en la velocidad de
clculo? (como veremos, el efecto del cdigo escalar puede ser muy grande).

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 ejecucin de las


diferentes operaciones sera el siguiente (L = load; S = store; + = suma; i =
elemento del vector):
L0 +0 S0 / L1 +1 S1 / L2 +2 S2 / L3 +3 S3 / ... / LN1 +N1 SN1
Si lo ejecutramos vectorialmente (LV - ADDV - SV), el orden pasara a
ser el siguiente:
L0 L1 L2 ... LN1 / +0 +1 +2 ... +N1 / S0 S1 S2 ... SN1
Esto es, la ejecucin vectorial implica desordenar el cdigo 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 anlisis 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 terico muy elevado, en la realidad tenemos
que superar muchos problemas para poder llegar a esa velocidad de clculo.

1.1.2 Arquitectura y lenguaje mquina


Existen diferentes arquitecturas para los computadores vectoriales, casi
tantas como fabricantes. En los primeros diseos, los computadores
vectoriales no tenan registros, y todas las operaciones se hacan con los
operandos en memoria. A este modelo se le denomina "Memoria-Memoria"
(M/M). Pero pronto se aadieron 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 lgico, muy simple, de un
computador vectorial. Podemos distinguir dos secciones: la seccin escalar y
la vectorial. El procesador escalar se encarga de la bsqueda y
descodificacin de las instrucciones. Si la instruccin es escalar, la ejecuta l

14

Captulo 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 opcin, vamos a suponer que la unidad de
control es de tipo Tomasulo (desorden/desorden).

Memoria

Registros

Unidades
funcionales

Procesador
escalar
(completo)

Unidad de
direcciones
(datos)

(op.)

Control del
procesador
vectorial

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


la realidad tendremos una mezcla de cdigo vectorial y escalar. Por tanto,
tendremos que utilizar tanto instrucciones vectoriales como escalares. Las
instrucciones escalares son las habituales en cualquier procesador RISC. En
funcin del computador, existen diferentes juegos de instrucciones
vectoriales y de formatos de instrucciones; las ms habituales son las
siguientes (ms tarde veremos algunas otras):
OPV Vi,Vj,Vk

Vi = Vj OP Vk
(OP = ADD, SUB, MUL, DIV...)

Operacin 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...)

Operacin entre un vector y un escalar. El


resultado es un vector.

1.2

15

DEPENDENCIAS DE DATOS

LV Vi,A(Rj)

Se lee a partir de la direccin de memoria A+Rj


un vector, y se deja en el registro Vi (puede
haber ms 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 parmetros:


direccin de comienzo, longitud y paso. La direccin de comienzo se indica
en la propia instruccin 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 operacin de lectura o escritura. Para ello utilizaremos dos
registros especiales: VL (vector length), para indicar el nmero de elementos
del vector, su longitud, y VS (vector stride), para indicar el paso. Si el
contenido de VL es mayor que Lmax (tamao de los registros vectoriales),
slo se procesarn 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 cargarn 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 explcitamente el paso de
los vectores en la propia instruccin, utilizando para ello un registro de
propsito general.

1.2

DEPENDENCIAS DE DATOS

Al igual que sucede con los procesadores (super)escalares, la velocidad de


clculo de los procesadores vectoriales est limitada por las dependencias de
datos. Una instruccin depende de otra anterior si uno de sus operandos es el
resultado de dicha instruccin, por lo que deber esperar a que finalice antes
de poder ejecutarse. Ya sabemos que, en los procesadores escalares, para

16

Captulo 1: COMPUTADORES VECTORIALES

atenuar la prdida de rendimiento debida a las dependencias de datos se


utilizan cortocircuitos (forwarding) entre las unidades funcionales; una idea
similar se aplica tambin en los procesadores vectoriales.
Para los siguientes ejemplos utilizaremos el siguiente esquema de
segmentacin (Tomasulo):
LV/SV
ADDV

BD
BD

L
L

AM
A

M
A

M
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
A(i) = A(i) + 1
enddo

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

El bucle presenta dependencias de datos muy claras: LV ADDVI (V1) y


ADDVI SV (V2). Entonces cmo se ejecutar ese programa? Tenemos
dos alternativas: sin realizar encadenamiento entre las dos instrucciones, o
encadenndolas.
a. Si no se realiza encadenamiento, la segunda instruccin deber esperar
a que termine la primera, para poder leer el registro vectorial
correspondiente (V1). En la figura se muestra un esquema de
ejecucin, en el que se puede ver cundo se realizan las lecturas (L).
LV V1,A(R1)

BD L AM M M M E ... E
BD . . . . . ... . L A A E ... E

ADDVI V2,V1,#1

BD L AM . . ... . . . . . ... . L M M M E ...

SV A(R1),V2
ciclos

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


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

1.2

17

DEPENDENCIAS DE DATOS

LV V1,A(R1)

BD L AM M

...

BD .

...

BD L AM .

ADDVI V2,V1,#1
SV A(R1),V2

ciclos

(N cicl.)

...

...

(N cicl.)

...

...

En este segundo caso, el tiempo de ejecucin es TV = 13 + N ciclos.


Podemos analizar el mismo comportamiento de manera cualitativa. Por
ejemplo, la siguiente figura muestra un esquema de ejecucin muy
simplificado del programa anterior (LV / ADDVI / SV), en funcin de si se
encadenan o no las instrucciones:
LV

LV
ADDVI

ADDVI
SV

SV

sin encadenamiento: T ~ 3N

con encadenamiento: T ~ N

La diferencia entre ambas opciones es clara. En el primer caso, el tiempo


de ejecucin es del orden de 3N; en el segundo, en cambio, es de orden N.
Por ejemplo, para N = 64 el tiempo de ejecucin bajara de 13 + 364 = 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 instruccin anterior: la instruccin ADDVI con la instruccin
LV, o la instruccin SV con la instruccin ADDVI. En un caso ms general,
tendramos que poder encadenar una instruccin con dos instrucciones
anteriores. Veamos un ejemplo (C = A + B):
LV V1,A(R1)

... (N ciclos) ...

BD L AM M

... (N ciclos) ...

BD .

... (N ciclos) ...

10

11

12

13

14

15

BD L AM M

LV V2,B(R1)
ADDV V3,V1,V2
SV C(R1),V3

BD L AM
1

...

...

E
14+N

18

Captulo 1: COMPUTADORES VECTORIALES

La tercera instruccin (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 estn cargando en el registro vectorial). Qu se puede hacer?
Para poder efectuar el encadenamiento hay que coger un operando segn
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 simultnea del mismo registro (lo habitual en
las mquinas 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 solucin) y habr que esperar a
que finalice la escritura de ambos operandos.

1.2.2 Tablas de ejecucin de las instrucciones


Representar los esquemas de ejecucin 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 ejecucin: cuntos ciclos han pasado, desde el comienzo,
hasta el momento previo a iniciar la operacin en la UF. El inicio
puede ser tras la lectura de los registros, o mediante encadenamiento,
en cuyo caso indicaremos el nmero de ciclos entre [ ].
(La ejecucin 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 instruccin LV la tabla correspondiente sera:
BD

L
comienzo (3)

AM

M
lat. UF (3)

...

dato 1 (6+1)

...

dato N (6+N)

1.2

19

DEPENDENCIAS DE DATOS

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


sin encadenamiento
A = A + 1

inic.

lat. UF dato 1

con encadenamiento

dato N

inic.

lat. UF dato 1

dato N

6+1

6+N

6+1

6+N

ADDVI V2,V1,#1

6+N+1

9+N+1

9+2N

[7]

9+1

9+N

SV A(R1),V2

9+2N+1

13+2N+1 13+3N

[10]

13+1

13+N

LV V1,A(R1)

Si la ejecucin de las instrucciones no se encadena, la instruccin 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 instruccin SV: tiene
que esperar a que la instruccin ADDVI termine (9+2N), y entonces leer V2
y escribir en memoria.
Si la ejecucin 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 instruccin 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.

C = A + B

sin encadenamiento
inic. lat. UF dato 1 dato N

con encadenamiento
inic. lat. UF dato 1 dato N

LV V1,A(R1)

6+1

6+N

6+1

6+N

LV V2,B(R1)

7+1

7+N

7+1

7+N

7+N+1

10+N+1

10+2N

[8]

10+1

10+N

10+2N+1

14+2N+1 14+3N

[11]

14+1

14+N

ADDV V3,V1,V2
SV C(R1),V3

Nota: estamos aplicando un modelo didctico de ejecucin vectorial, y el objetivo


es mostrar el comportamiento general, no los detalles particulares. Lo computadores
comerciales utilizan estrategias similares, aunque los detalles de implementacin
pueden variar.

20

1.3

Captulo 1: COMPUTADORES VECTORIALES

DEPENDENCIAS ESTRUCTURALES

Despus 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 instruccin. Adems de las unidades funcionales, el recurso ms
importante en un computador vectorial es la memoria. Para poder utilizar la
memoria, primeramente hay que disponer de un bus libre. Cuntos buses
tenemos para acceder a la memoria? Por otro lado, se utilizan los propios
mdulos de memoria. Estn libres los mdulos que hay que utilizar? Si
estn ocupados, cunto tiempo hay que esperar?

1.3.1 Buses de memoria (unidades funcionales LV/SV)


La ejecucin de las instrucciones LV y SV implica una transferencia con
memoria en la que se utilizan los buses. Cuando se ejecuta una instruccin
LV o SV, el bus se ocupa durante N ciclos; mientras una instruccin est
utilizando el bus, la siguiente deber esperar hasta que se libere el bus. Por
tanto, si el computador no tuviera un nmero suficiente de buses, la
velocidad de clculo de la mquina no sera muy alta.
Analicemos la influencia del nmero de buses mediante el ejemplo
anterior (A = A + 1; LV / ADDVI / SV). Supongamos que la mquina puede
encadenar las instrucciones, pero que slo posee un bus para trabajar con
memoria (LV o SV) 4. En estas condiciones, cuando la instruccin SV quiere
empezar a escribir en memoria, en el ciclo de encadenamiento, el bus no est
disponible, ya que lo ocupa la instruccin LV (y lo mantendr ocupado
muchos ciclos). Por tanto, deber esperar hasta que termine la primera
instruccin (y se libere el bus) y leer entonces el registro en el que se estn
escribiendo los resultados (V2) 5.
Este sera el esquema de ejecucin:

Los buses de memoria pueden usarse tanto para una lectura como para una escritura; en algunas
mquinas, en cambio, los buses estn "dedicados": unos son slo para leer y otros slo para escribir.

Si no puede leerse un registro mientras se est escribiendo, entonces habr que esperar a que finalice
la escritura.

1.3

21

DEPENDENCIAS ESTRUCTURALES

LV V1,A(R1)
ADDVI V2,V1,#1

BD L AM M

M M

E E ...

BD . .

. .

L A

A E E ...

BD L AM .

. .

SV A(R1),V2

(N ciclos)

? .

...

...

(N ciclos)

...

bus ocupado...

...

L M M M E ...

(N cicl.)

libre

o, esquemticamente:
LV
ADDVI

T ~ 2N
SV

La tabla correspondiente a la ejecucin sera la siguiente:

A = A + 1
LV V1,A(R1)
ADDVI V2,V1,#1
SV A(R1),V2

un bus / encadenamiento
inic. lat. UF dato 1 dato N
3

6+1

6+N

[7]

9+1

9+N

[6+N]

9+N+1

9+2N

Repitamos el anlisis, pero con el segundo ejemplo que hemos visto antes.
En ambos casos, las instrucciones se encadenan, pero en el primer caso la
mquina cuenta con un solo bus de memoria, y en el otro caso cuenta con
dos buses.

C = A + B

un bus / encadenamiento
inic. lat. UF dato 1 dato N

dos buses / encadenamiento


inic. lat. UF dato 1 dato N

LV V1,A(R1)

6+1

6+N

6+1

6+N

LV V2,B(R1)

6+N

9+N+1

9+2N

7+1

7+N

ADDV V3,V1,V2

[10+N]

12+N+1

12+2N

[8]

10+1

10+N

SV C(R1),V3

[9+2N]

12+2N+1 12+3N

[6+N]

9+N+1

9+2N

Cuando slo hay un bus, la segunda instruccin LV no puede utilizar la


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

22

Captulo 1: COMPUTADORES VECTORIALES

mquina tiene dos buses, las instrucciones LV se ejecutarn a la vez, pero la


instruccin SV tendr que esperar.
Esquemticamente:
LV

LV
LV
ADDV

LV
ADDV
SV

un bus / encadenamiento: 3N

SV

dos buses / encadenamiento: 2N

La conclusin 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 ejecucin ser elevado.
En resumen, los resultados que hemos obtenido con ambos ejemplos son
los siguientes:
1.

2.

A = A + 1
(N = 64)
sin encadenamiento
encadenamiento / 1 bus
encadenamiento / 2+ buses

13 + 3N = 205 ciclos
9 + 2N = 137 ciclos
13 + N = 77 ciclos

C = A + B
(N = 64)
sin encadenamiento / 1 bus
sin encadenamiento / 3 buses
encadenamiento / 1 bus
encadenamiento / 2 buses
encadenamiento / 3 buses

16 + 4N = 272 ciclos
14 + 3N = 206 ciclos
12 + 3N = 204 ciclos
9 + 2N = 137 ciclos
14 + N = 78 ciclos

3,20 ciclos/dato
2,14 c/d
1,20 c/d

4,25 c/d
3,22 c/d
3,19 c/d
2,14 c/d
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 mdulos de memoria


1.3.2.1

Una sola operacin 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 mdulos; as, las direcciones i e i+1

1.3

23

DEPENDENCIAS ESTRUCTURALES

no corresponden al mismo mdulo de memoria, sino a mdulos


consecutivos. De esta manera es posible, por ejemplo, efectuar una
operacin simultnea en dos (en general nm, el nmero de mdulos)
direcciones consecutivas; si estuvieran en el mismo mdulo tendramos que
esperar a que finalizara una operacin antes de empezar con la siguiente.
Cuando se ejecuta una instruccin LV o SV se efectan N lecturas o
escrituras en memoria, una por ciclo. Para que se haga de manera eficiente,
es necesario que se acceda a mdulos que estn libres; si no, tendramos un
conflicto estructural, y no lograramos efectuar una operacin por ciclo.
Veamos el problema con un ejemplo. Hay que leer el vector A(A0:A15); la
memoria est entrelazada en 4 mdulos, y el vector se encuentra en mdulos
consecutivos (s = 1) a partir de m0. La latencia de la memoria es de 3 ciclos.
La situacin de la memoria segn se lee el vector A es la siguiente:
m0
A0
A4
A8
A12

m1
A1
A5
A9
A13

m2
A2
A6
A10

m3
A3
A7
A11

...

tiempo (ciclos)
m0
m1
m2
m3

...

...

La lectura comienza en m0, y sigue en m1, m2, m3, y se vuelve a m0,


para seguir leyendo ms elementos del vector. En ese momento, m0 est
libre, puesto que ya ha terminado el primer acceso, y por tanto no tendremos
ningn problema.
Pero si, por ejemplo, la latencia de la memoria fuera de 8 ciclos, al ir a
utilizar nuevamente m0 lo encontraramos ocupado, ejecutando todava la
operacin anterior. Tendramos, por tanto, que esperar a que finalizara antes
de poder seguir leyendo el vector. Como consecuencia del conflicto
estructural, el tiempo de ejecucin de la operacin sera ms alto.
El problema puede ser grave, en funcin de la definicin del vector. Por
ejemplo, si el paso del vector del ejemplo anterior fuera s = 4, entonces todos
los elementos del vector estaran en el mismo mdulo, m0: todos los accesos
significaran un conflicto, ya que cada acceso dura 3 ciclos.

24

Captulo 1: COMPUTADORES VECTORIALES

Para analizar si surgirn o no conflictos en memoria, hay que considerar


tres parmetros: el tiempo de acceso o latencia de la memoria tm, el
nmero de mdulos en que est entrelazada la memoria nm, y el paso
de los vectores s. Dos de esos parmetros, latencia y nmero de
mdulos, son decisiones de diseo: se deciden al construir la mquina 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 mdulos de memoria al acceder a un
vector (m0-m1-m2-...). Por tanto, para que no haya conflictos se debe
cumplir que:

nm t m
De esa manera, cuando hay que reutilizar un determinado mdulo ya han
pasado al menos nm ciclos, y por tanto estar libre.
Para el caso general, s > 1, hay que calcular cuntos mdulos se utilizan
en una determinada operacin. Por ejemplo, en el caso anterior, cuando s =
4, slo se utiliza un mdulo de memoria, siempre el mismo (m0). Puede
demostrarse fcilmente que el nmero de mdulos que se utilizan en una
operacin de memoria es:
nm
MCD(nm, s )

(MCD = mximo comn divisor)

As pues, y generalizado el resultado anterior, no habr conflictos en


memoria si el nmero de mdulos que se van a utilizar es mayor o igual que
la latencia:

nm
tm
MCD(nm, s )
Analizando la expresin anterior. se observa que la mejor situacin 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 mdulos de memoria. En los casos ms
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 podra haberlos para los casos de s par.
Existe una situacin ptima. Si el nmero de mdulos de memoria, nm, es
un nmero primo, entonces cualquier paso s ser primo con l (salvo sus
mltiplos). Por ejemplo, si nm = 5, no hay problemas con los vectores de

1.3

25

DEPENDENCIAS ESTRUCTURALES

paso s = 1, 2, 3, 4, 6, 7, 8... Por desgracia, cuando la memoria se entrelaza en


un nmero primo de mdulos, 17 por ejemplo, calcular el mdulo y la
direccin dentro del mdulo que corresponden a una palabra dada es una
operacin compleja (operacin que debe realizar el controlador de
memoria, para efectuar cualquier acceso a memoria), ya que habr que
efectuar una divisin para obtener el cociente y el resto (cuando nm = 2i, los
i bits de menos peso indican el mdulo, y el resto la direccin dentro del
mdulo). Debido a esa divisin, no se suele entrelazar la memoria en un
nmero primo de mdulos.
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 mdulos y una matriz de tamao 44, 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 estn en mdulos de memoria
diferentes, pero el acceso a cualquier columna es muy conflictivo, ya que
todos los elementos estn en el mismo mdulo 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 diagonales 6).
m0
A00
A10
A20
A30

sf = 1
sc = 4
sD = 5
sd = 3
6

m1
A01
A11
A21
A31

m2
A02
A12
A22
A32

m3
A03
A13
A23
A33

sin conflictos
conflictos (todos en m0)
sin conflictos
sin conflictos

m0
A00
A13
A22
A31

sf = 1
sc = 5
sD = 6
sd = 4

m1
A01
A10
A23
A32

m2
A02
A11
A20
A33

m3
A03
A12
A21
A30
-

sin conflictos
sin conflictos
conflictos
conflictos

Como hemos comentado, el ideal sera que nm fuera un nmero primo. Por ejemplo, si nm fuera 5,
los cuatro vectores del ejemplo (f, c, D y d) podran accederse sin problemas si se dejan los
correspondientes huecos (lo dejamos como ejercicio para el lector).

26

Captulo 1: COMPUTADORES VECTORIALES

1.3.2.2

Varias operaciones de memoria

Como acabamos de ver, la ejecucin de una instruccin vectorial de


memoria, LV o SV, puede producir problemas en el acceso a los mdulos de
memoria. Lo mismo ocurre cuando se estn ejecutando ms de una
instruccin de memoria. Aunque cada una de ellas no tuviera conflictos
consigo misma, es posible que existan colisiones entre ellas; es decir, que
una segunda instruccin quisiera utilizar un mdulo de memoria ocupado en
ese instante por otra instruccin.
Analicemos el problema mediante un ejemplo. Supongamos que la
memoria est entrelazada en 8 mdulos y que la latencia es 3 ciclos (con el
mismo esquema de segmentacin de los ejemplos anteriores). Hay
suficientes buses a memoria y las instrucciones pueden encadenarse. El
primer elemento de A esta en el mdulo m0 y el paso es s = 1.
2 buses / encadenamiento
inic. lat. UF dato 1 dato N

A = A + 1
LV V1,A(R1)

6+1

6+N

ADDVI V2,V1,#1

[7]

9+1

9+N

SV A(R1),V2

[10]

Como hemos visto antes, la instruccin SV puede encadenarse en el ciclo


10, e ir a memoria. Pero, cmo se encuentran en ese momento los mdulos
de memoria, libres u ocupados? El esquema siguiente muestra el uso de los
mdulos de memoria ciclo a ciclo. La instruccin LV comienza la lectura en
el ciclo 4, en el mdulo m0, por ejemplo. Supongamos, por simplificar el
problema, que el paso de A es 1. Por tanto, tras el mdulo m0 se acceder a
m1, m2..., m7, y nuevamente a m0, m1... Dado que la latencia es 3 ciclos, la
instruccin no tiene ningn conflicto consigo misma (nm tm).
t (ciclos)
mem.

m0
m1
m2
m3
m4
m5
m6

M
M

M
M
M

m7

M
M
M

M
M
M

M
M
M

10

M
M
M

11

12

13

14

15

16

17

18

M
-

M
M

M
M
M

m
M
M
M

m
m
M
M
M

m
m

M
M

M
M
M

M
M
M

19

M
M
M

1.3

DEPENDENCIAS ESTRUCTURALES

27

La instruccin SV puede encadenarse en el ciclo 10, para empezar en


memoria en el ciclo 11, en el mdulo m0 (hay que guardar el mismo vector,
A). Cmo se encuentra en ese momento ese mdulo? La instruccin LV est
en ejecucin, y mantiene ocupados varios mdulos. Primeramente debemos
calcular qu mdulo va a acceder LV en ese ciclo, para lo que basta con
saber cuntos ciclos lleva ya en memoria y de qu mdulo parti. En este
ejemplo, la distancia en ciclos es de 10 3 = 7, y dado que parti del mdulo
m0 (y que s = 1), en el ciclo 11 ir a utilizar el mdulo m7. Ese mdulo, por
tanto, no estar disponible.
Pero habr ms mdulos ocupados, ya que todava estarn ejecutndose
accesos que comenzaron en ciclos anteriores. Si la latencia de la memoria es
tm, se mantiene ocupados tm1 mdulos anteriores. En el ejemplo tm = 3,
por lo que tendremos dos mdulos ocupados, m6 y m5. Utilizando el mismo
razonamiento, es necesario dejar libres por delante otros tm1 mdulos, para
que la instruccin LV pueda seguir ejecutndose sin interferencias; en el
ejemplo, los mdulos m0 y m1 (si no, LV chocara con SV en el siguiente
ciclo).
As pues, en este ejemplo en el ciclo 11 estn ocupados o reservados los
mdulos <m5 m6 m7 m0 m1>. Si alguna instruccin quiere utilizar
esos mdulos, deber esperar a que se liberen. se es el caso de la
instruccin SV, que tiene que utilizar m0, y que tendr que esperar.
Cuntos ciclos? Tantos como la posicin que ocupa el mdulo a utilizar en
la lista de mdulos ocupados. En el ejemplo, 4 ciclos. En el ciclo 11 estn
ocupados los mdulos m5-...-m1; en el siguiente ciclo, por tanto, los
mdulos 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 instruccin 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
mdulo nm, siendo nm el nmero de mdulos de memoria):
a. Se calcula qu mdulo va a comenzar a utilizar la instruccin j cuando
la instruccin k quiere acceder a memoria:
(inik inij) + mdulo_inij

(ini = ciclo inicio en memoria)

b. Se crea la lista de mdulos ocupados, aadiendo tm1 mdulos por


delante y por detrs al mdulo anterior (tm, latencia de la memoria).
< tm1 mdulos | (inik inij) + mdulo_inij | tm1 mdulos>

28

Captulo 1: COMPUTADORES VECTORIALES

c. Si el primer mdulo de memoria que va a utilizar la instruccin k est


ocupado, se calcula el tiempo de espera, que habr que aadir al
tiempo de inicio de la instruccin (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 estn accediendo a memoria
sean iguales. En este caso, dado que en cada paso se avanzan s mdulos, por un
lado, habr que hacer (inik inij) s + mdulo_inij y luego habr que contar tm1
mdulos de s en s. En caso de que no coincida el paso de todas las instrucciones a
memoria el anlisis es ms complejo y, normalmente, en funcin de la mquina, no
se comenzar a ejecutar la segunda instruccin 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 instruccin SV quiere realizar
un encadenamiento en el ciclo 10. Dado que la instruccin LV est an en
memoria, no podr utilizar los siguientes mdulos: el mdulo (10 3) + 0 =
7, los dos anteriores, 6 y 5, y los dos siguientes, 0 y 1. Como necesita
acceder al mdulo 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,
habra que esperar a que la suma terminara de escribir en el registro V2).
2 buses / encadenamiento
A = A + 1
LV V1,A(R1)
ADDVI V2,V1,#1
SV A(R1),V2

inic.

mod. ocup.

t. esp.

lat. UF

dato 1

dato N

6+1

6+N

[7]

9+1

9+N

[10] ??

5 / 6 7 0 / 1

+4

17+1

17+N

En general, para calcular el nmero de ciclos que hay que esperar para
utilizar la memoria, hay que hacer el anlisis con todas las instrucciones que
estn en memoria, ya que cada una de ellas ocupar tm mdulos de memoria.
Como consecuencia de ello, no se pueden permitir ms de nm div tm
operaciones de memoria simultneamente, ya que con ese nmero de
instrucciones se ocupan todos los mdulos de memoria. Por ejemplo, para el
anterior caso (nm = 8 y tm = 3) no se pueden procesar simultneamente ms
de 8 div 3 = 2; estara de sobra, por tanto, un hipottico tercer bus a
memoria.

1.3

29

DEPENDENCIAS ESTRUCTURALES

De los prrafos 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 colocacin 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 ejecucin vectorial es el
tamao de los registros vectoriales. Los registros vectoriales se utilizan con
el mismo propsito 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 nmero de registros no muy alto, menor en todo
caso que el de registros escalares. Por otra parte, el nmero de buses de
escritura y lectura tambin ser limitado, y adems se mantienen ocupados a
lo largo de muchos ciclos (con la instruccin ADDV V3,V2,V1 se ocupan
tres buses durante N ciclos). Por ello, necesitaremos bastantes buses para
poder efectuar simultneamente ms de una operacin con registros.
Una tercera limitacin proviene del tamao de los registros vectoriales. Si
bien los vectores que procesa el usuario pueden ser de cualquier tamao, los
registros vectoriales suelen ser de un tamao limitado, habitualmente de 64128 palabras. Por tanto, con una instruccin vectorial no se pueden procesar
ms elementos que los correspondientes al tamao de los registros. Para
vectores ms largos hay que montar un bucle y procesar el vector en trozos
de tamao Lmax. A este procedimiento se le denomina strip mining.
El tamao 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
nmero de elementos indicado en VL; por el contrario, si VL > Lmax, entonces
se procesarn nicamente Lmax elementos. Por ejemplo:

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

MOVI
MOVI
mas: MOV

VS,#1
R1,#N
VL,R1

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

R2,R2,#Lmax
R1,R1,#Lmax
R1,mas

( tam. pal.)

30

Captulo 1: COMPUTADORES VECTORIALES

Segn la longitud de los vectores, puede ser que el ltimo (o el primer)


trozo que se procese sea ms pequeo que el resto. Por ejemplo, si tenemos
Lmax = 128 y N = 1000, en la ltima iteracin slo se procesarn 104
elementos (7 128 + 104 = 1000).
Todo ello va a repercutir en el tiempo de ejecucin de la operacin
vectorial (en la velocidad de clculo, por tanto). Si el tiempo de una
operacin vectorial puede darse como TV = ti + tv N, el hecho de que los
registros sean de tamao Lmax har que el tiempo de ejecucin se exprese
como:
N
TV =
(ti + tbuc ) + tv N
Lmax

N/Lmax indica el nmero de trozos a procesar (iteraciones del bucle), y tbuc


el tiempo necesario para el control del bucle (ahora el sumando inicial
tambin depende de N).
El proceso de strip mining es prcticamente inevitable cuando hay que
procesar vectores, ya que, en la mayora de los casos, la longitud de los
vectores es un parmetro que se decide en ejecucin. 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

TV = 8 (30+10) + 1500 = 1820 ciclos


3,64 ciclos/elemento (+ 19%)

pero
<

Lmax = 64

1.4

tbuc = 10 ciclos

VELOCIDAD DE CLCULO DE LOS COMPUTADORES VECTORIALES

Cuando hemos definido los computadores vectoriales hemos indicado el


deseo de construir una mquina de gran velocidad en la ejecucin de
determinados programas. Analicemos un poco ms despacio los parmetros
bsicos que definen la velocidad de clculo en estas mquinas.

1.4

VELOCIDAD DE CLCULO DE LOS COMPUTA-DORES VECTORIALES

31

1.4.1 Velocidad de clculo en funcin de la longitud


de los vectores
1.4.1.1

R y N1/2

Recordemos cmo se puede expresar el tiempo de ejecucin de un


programa en modo vectorial y en modo escalar:
en modo escalar

TE = te N

te = tiempo para ejecutar una iteracin

en modo vectorial 7

TV = ti + tv N

ti = tiempo de inicio
tv = tiempo para procesar un elemento del vector

Los tiempos de ejecucin se suelen dar en ciclos o en (nano)segundos


(basta multiplicar por el periodo del reloj). En la figura se representa el
tiempo de ejecucin vectorial en funcin de la longitud de los vectores, una
recta.
300

TV

250
200

pendiente = tb
150

2ti
100

TV = 30 + 2N
5

ti
0

N1/2
0

25

50

75

100

125

150

N (longitud de los vectores)


A partir de ah, definimos la velocidad de clculo o rendimiento
(performance) como el nmero de elementos que se procesa por unidad de
tiempo (ciclo o segundo):
RN =

N
N
=
TV ti + tv N

La velocidad de clculo se suele dar en Mflop/s (Mega FLoat OPeration /


second), es decir, cuntos millones de operaciones de coma flotante se
realizan por segundo, para lo que tenemos que considerar el nmero de
7

Sin considerar el tamao de los registros vectoriales.

32

Captulo 1: COMPUTADORES VECTORIALES

operaciones que se realizan con los vectores, OpCF 8, ya que en total se


ejecutarn N OpCF operaciones.
El tiempo de ejecucin 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
expresin anterior por la frecuencia de reloj (T = 1/F). Por tanto:
RN =

N
N
=
OpCF F Mflop/s
TV ti + tv N

(TV en ciclos, F en MHz)

Analicemos grficamente el comportamiento de la expresin anterior.

R/2

(rendimiento)

N1/2

N (nmero de elementos)

Tal como se observa en la figura, la funcin R tiene una asntota cuando N


tiende a . Aunque los vectores fueran muy largos, la velocidad de clculo
tiene un lmite. A ese valor mximo se le denomina R.

R = lim N RN =

1
OpCF F
tv

Tambin es habitual indicar la eficiencia del sistema, es decir, la fraccin


de la velocidad mxima que se consigue:
Eficiencia = R / R

en el intervalo [0, 1]

Por definicin, ninguna mquina puede alcanzar la velocidad mxima R.


Por ello, se suele utilizar otro parmetro ms: N1/2, tamao mnimo de los
vectores que permite alcanzar al menos la mitad de la velocidad mxima. De
acuerdo a la definicin, R(N1/2) = R/2; por tanto:
N1/2 / (ti + tv N1/2) = 1/tv 1/2
8

N1/2 = ti / tv

(entero superior)

En lo que a la velocidad de clculo respecta, no es lo mismo efectuar una suma con los vectores que
efectuar dos sumas y una multiplicacin.

1.4

VELOCIDAD DE CLCULO DE LOS COMPUTA-DORES VECTORIALES

33

Si N1/2 es muy alto, lo ms probable es que andemos lejos del valor


mximo de velocidad; en cambio, si es pequeo, no necesitaremos procesar
vectores muy largos para acercarnos a la velocidad mxima de clculo. De la
misma manera que hemos definido N1/2, podemos definir N3/4, N1/4, etc., es
decir, el tamao mnimo de los vectores para conseguir una determinada
fraccin de la velocidad mxima 9.

Los dos parmetros que definen la velocidad de clculo (performance) pueden


obtenerse mediante un experimento muy simple. Se ejecuta el programa vectorial
para diferentes valores de N, se mide el tiempo de ejecucin, y se dibuja la funcin
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 parmetro R es funcin del programa que se ejecuta. En todo caso, es


sencillo obtener la velocidad terica mxima (peak performance) que podra
conseguir un computador vectorial. El mximo lo obtendramos si tv = 1
ciclo y se utilizaran simultneamente 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, podra lograr 6 500 = 3000
Mflop/s = 3 Gflop/s. Sin embargo, ese valor no es representativo de un caso
real, ya que slo se puede conseguir en casos muy excepcionales. Lo ms
habitual es que tv > 1 y que no se estn utilizando todas las unidades
funcionales simultneamente.

1.4.1.2

Speed-up o factor de aceleracin

Para representar la velocidad de clculo de un computador vectorial


podemos efectuar esta otra comparacin: cuntas veces es ms rpida la
ejecucin del programa en modo vectorial que en modo escalar?
KV =

t N
TE
= e
T V ti + t v N

K =

te
tv

El comportamiento de la funcin KV es similar al de R, y obtiene un


mximo cuando N tiende a infinito. El parmetro K indica cuntas veces es
9

Si se prefiere, el tiempo de ejecucin y la velocidad de clculo pueden darse en funcin de los dos
parmetros que acabamos de definir, N1/2 y R:
TV = (N + N1/2) / R

RV = R (1 / (1 + N1/2/N))

34

Captulo 1: COMPUTADORES VECTORIALES

ms rpido 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 parmetro K sea grande, porque indica que el procesador
vectorial es muy rpido. Sin embargo, como vamos a ver enseguida, la
situacin no es tan clara como parece, ya que habr que ejecutar tambin
cdigo escalar junto con el vectorial.

1.4.1.3

NV

Los dos parmetros de calidad ms utilizados son R y N1/2, aunque


pueden plantearse otros. Por ejemplo, se obtiene siempre un tiempo de
ejecucin menor en modo vectorial que en modo escalar? Podemos calcular
el parmetro Nv, longitud de los vectores que hace que TE = TV.
t e N v = ti + t v N v

Nv =

ti
N1/ 2
=
te tv K 1

Por tanto, si los vectores a procesar son ms cortos que Nv (funcin de N1/2
y K), entonces no merece la pena ejecutar en modo vectorial, ya que la
ejecucin en modo escalar ser ms rpida.

1.4.2 Influencia del cdigo 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 tambin, junto al cdigo vectorial, cdigo escalar (no son habituales
los programas que se pueden expresar en un 100% en forma de cdigo
vectorial). Por tanto, para medir correctamente la velocidad de ejecucin, es
necesario contar con el tiempo de ejecucin del cdigo escalar.
Sea f la fraccin de cdigo que puede ser ejecutada vectorialmente y 1 f
la parte que hay que ejecutar en modo escalar. El tiempo de ejecucin del
programa se puede expresar como:
TVE = f TV + (1 f) TE
As pues, comparado con el procesador escalar, el factor de aceleracin
logrado ser:
KVE =

TE
TE
=
=
TVE
fTV + (1 f )TE

TE
f

TE
+ (1 f )TE
K

K
f (1 K ) + K

1.4

35

VELOCIDAD DE CLCULO DE LOS COMPUTA-DORES VECTORIALES

La expresin anterior se conoce como ley de Amdahl. Cuando f = 0 (todo


el cdigo es escalar), el factor de aceleracin es 1; y cuando f = 1 (todo el
cdigo es cdigo vectorial), el factor de aceleracin es KV, tal como hemos
definido anteriormente. Analicemos grficamente el comportamiento del
factor de aceleracin en funcin de f.
16

KV =

4
2

Tal como aparece en la grfica, para poder obtener factores de aceleracin


significativos es necesario que el factor de vectorizacin sea alto. Por
ejemplo, para KV = 16, si queremos que la ejecucin vectorial sea 8 veces
ms rpida, se necesita que f > 93%. Analizado desde otro punto de vista, si,
por ejemplo, f = 0,65, el factor de aceleracin no ser nunca mayor que 3,
aunque KV sea infinito; para KV = , el factor de aceleracin es 1 / (1f).
La vectorizacin del cdigo de un determinado programa es tarea del
compilador (con la colaboracin, tal vez, del programador). En la grfica
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 vectorizacin no es posible lograr altos
valores de speed-up, aunque KV sea muy grande. Por ello, la eficiencia del
proceso de compilacin vectorial es crucial para poder obtener el mximo
rendimiento de un computador vectorial.
Tal como hemos calculado antes el parmetro N1/2, en este caso se puede
obtener un parmetro similar, f1/2, factor de vectorizacin necesario para
obtener al menos la mitad de la velocidad mxima (KV /2).
KV
KV
=
f1 / 2 (1 KV ) + KV
2

f1 / 2 = 1

1
KV 1

36

Captulo 1: COMPUTADORES VECTORIALES

En general, por tanto, si se desea calcular los Mflop/s que realmente se


conseguirn con un determinado programa, hay que considerar tanto el
cdigo escalar como el vectorial:
RVE =

N
N
N
N
=
=
=
TVE
fTV + (1 f )TE
f (ti + tv N ) + (1 f )te N
f (ti + tv N ) + (1 f ) K tv N

Como siempre, para ponerlo en Mflop/s hay que multiplicar por el nmero
de operaciones en coma flotante realizadas, y por la frecuencia de reloj (si el
tiempo estaba en ciclos).
En algunos textos, la expresin anterior suele darse de la siguiente manera:
RN,f = R N f

donde N = 1 / [1 + N1/2/N]

f = 1 / [f + (1f) K N]

es decir, hay un rendimiento mximo R,1 cuando la longitud de los vectores es infinita y
el factor de vectorizacin 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 vectorizacin es f y no 1.
Pongamos un ejemplo. Un computador vectorial tiene los siguientes parmetros: R = 800
Mflop/s, N1/2 = 60, K = 10, f = 0,8 y N = 128.
Si fueran N infinito y f = 1, se obtendran 800 Mflop/s. Como N = 128, el primer lmite es N =
0,68. Adems, como f = 0,8 (y N = 128) tenemos un segundo lmite, f = 0,46. En
consecuencia, la velocidad de clculo que se consiga ser: 800 0,68 0,46 = 252 Mflop/s.

Conviene enfatizar nuevamente la influencia del cdigo escalar en el


rendimiento total del sistema. La siguiente figura presenta la comparacin de
dos mejoras efectuadas en un computador vectorial.
Factor de aceleracin (normalizado)

16

Ley de Amdahl

14

12

tv = 5 ns
te = 66,6 ns

10
8
6

tv = 10 ns
te = 33,3 ns

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

2
0
0

0.2

0.4

0.6

0.8

f (factor de vectorizacin)

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


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

1.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

37

mquina en la que se ha duplicado KV (la nueva mquina es dos veces ms


rpida). En cambio, si el programa no se puede vectorizar (f = 0, todo cdigo
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 atencin, si f se
mantiene en el intervalo [0,6 0,8], en ese caso no interesa que KV sea muy
alto, y resulta ms 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
caractersticas especficas.

1.5

TCNICAS DE COMPILACIN PARA GENERAR


CDIGO VECTORIAL

Los procesadores vectoriales ejecutan cdigo vectorial, pero, en general,


slo una parte de los programas puede ejecutarse de esa manera. A quin
corresponde detectar qu partes del cdigo son vectorizables y escribir el
correspondiente cdigo, al programador o al compilador? Lo ms adecuado
es que el trabajo del programador sea independiente de la mquina; se
programan algoritmos en alto nivel, y el correspondiente compilador
traducir esos programas al cdigo ms adecuado para la mquina en que se
vayan a ejecutar, teniendo en cuenta las caractersticas de la misma.
Afortunadamente, existen buenos compiladores vectoriales que generan
cdigo vectorial de manera eficiente, para lo que previamente analizan las
dependencias entre instrucciones y deciden qu partes del cdigo pueden
ejecutarse vectorialmente. En todo caso, siempre es importante la ayuda de
un programador inteligente, ya que a veces no es sencillo traducir
automticamente de alto nivel a cdigo 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 vectorizacin
altos. En caso contrario, la velocidad de procesamiento se alejar mucho de
los mximos tericos. En los siguientes apartados vamos a analizar las
estrategias principales que sigue un compilador vectorial para generar cdigo
vectorial. Utilizaremos las mismas o parecidas estrategias un poco ms
adelante, cuando tengamos que ejecutar un bucle entre P procesadores.

38

Captulo 1: COMPUTADORES VECTORIALES

1.5.1 Dependencias de datos entre instrucciones


Como ya hemos comentado, vectorizar implica, entre otras cosas,
determinada reordenacin del cdigo original. Sin embargo,
instrucciones no pueden reordenarse de cualquier manera, puesto que
que respetar las dependencias de datos. Recordemos brevemente los
tipos de dependencias de datos.

una
las
hay
tres

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


1:

A = B + C

2:

D = A

1
A

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


resultado de la instruccin 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
despus. En este ejemplo, la instruccin 1 debe escribir antes que la
instruccin 2 lea el operando.
Las dependencias RAW no pueden evitarse, puesto que son
intrnsecas al algoritmo que se quiere ejecutar. En algunos casos,
pueden resolverse mediante cortocircuitos; en caso contrario, habr
que esperar a que se ejecute la operacin anterior.
Antidependencias (WAR write-after-read, DR)
1
1:

A = B + C

2:

B = D

Existe una antidependencia entre las instrucciones 1 y 2, puesto que


un operando que necesita la instruccin 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 instruccin 1 antes que escribir B en la 2.
Las antidependencias no son dependencias fuertes, y en muchos
casos desaparecen con una correcta reordenacin del cdigo.

1.5

39

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

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


1
1:

A = B + C

2:

A = D

Existe una dependencia de salida entre las instrucciones 1 y 2, ya que


la instruccin 2 va a escribir en la misma variable que la 1. La
representamos en el grafo mediante una flecha con un pequeo
crculo, 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 ordenacin del cdigo vectorial.
Recuerda: sea cual sea el tipo, las dependencias implican un orden
determinado de ciertas operaciones: qu hay que hacer antes y qu despus.
En los prrafos anteriores hemos visto las dependencias entre
instrucciones individuales. Pero el cdigo 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 iteracin. Definimos distancia de una
dependencia como el nmero 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
iteracin, la distancia es 0; si es en la siguiente, la distancia es 1, etc. En los
bucles de ms de una dimensin, la distancia se representa como un vector
de distancias, con un elemento por cada dimensin del bucle. Por ejemplo:
i

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

A, 2

A, 2

A, 1
i=0

i=1

i=2

A, 1

do i = 2,
do j = 1,
1: A(i,j)
2: C(i,j)
enddo
enddo

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

A, (0, 1)

A, (0, 1)

A, (2, 1)

A, (2, 1)

2
grafo de dependencias

espacio de iteraciones

40

Captulo 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
anlisis de dependencias (no olvides que vectorizar implica desordenar el
cdigo), y generar el grafo de dependencias. En este grafo se representan
las dependencias entre instrucciones de la instruccin i a la j, para todas
las iteraciones del bucle. En los casos de ms de una dimensin, tambin 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 Vectorizacin
1.5.2.1 Vectores de una dimensin
Antes de formalizar las tcnicas de vectorizacin, veamos algunos
ejemplos 10.
1.5.2.1.1 Primer ejemplo
do i = 0, N-1
A(i) = B(i) + C(i)
enddo

No existe ningn tipo de dependencia entre las instrucciones del bucle,


por lo que puede escribirse en forma vectorial, sin problemas:
MOVI
MOVI

VL,#N
VS,#1

LV
LV
ADDV
SV

V1,B(R1)
V2,C(R1)
V3,V1,V2
A(R1),V3

; longitud de los vectores


; paso de los vectores (stride)

En algunos lenguajes, el cdigo 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, nmero de elementos; y z, paso del vector.

10 Salvo que se indique lo contrario, los vectores son de tamao N (o NN); la direccin A indica el
primer elemento del vector, A0; A+1 indica el siguiente elemento, etc. (sin considerar el tamao 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).

1.5

41

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

El cdigo escalar original y el vectorial que acabamos de escribir no son


exactamente equivalentes. El bucle escalar utiliza la variable i para
controlar el nmero de iteraciones e indicar los elementos del vector, por lo
que al acabar el bucle i contendr el valor correspondiente a la ltima
iteracin, y, aunque no es habitual, tal vez se utilice dicha variable ms
adelante en el programa. El compilador vectorial tiene que generar cdigo
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 N1. Lo mismo habr que hacer con el resto de variables similares.
Por claridad, vamos a omitir el cdigo correspondiente a esas operaciones.
1.5.2.1.2 Segundo ejemplo
i

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

A, 0
A, 0

2
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 instruccin a
la segunda, y la dependencia se produce en la misma iteracin, como se
observa en el espacio de iteraciones.
En algunos textos, la dependencia anterior se indica de la siguiente manera: 1 = 2;
el smbolo = indica que la dependencia es de distancia 0 (si la distancia es mayor
que 0, se utiliza el smbolo <).

La dependencia no implica ningn problema, y el cdigo se puede


vectorizar de la siguiente manera:
MOVI VL,#N
MOVI VS,#1
(1)

(2)

LV
LV
ADDV
SV

V1,B(R1)
V2,C(R1)
V3,V1,V2
A(R1),V3

;A = B + C

SV

D(R1),V3

; no hay que leer A, ya que est en V3

42

Captulo 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
A, 1

A, 1

1 < 2

Aunque el grafo de dependencias es similar al del ejemplo anterior, ahora


las dependencias van de iteracin a iteracin: hay que utilizar en la segunda
instruccin de la iteracin i el resultado de la primera instruccin de la
iteracin i1. Aunque se observa una cadena de dependencias en el espacio
de iteraciones, las dependencias son entre instrucciones diferentes: 1i 2i+1.
Nuevamente, el cdigo puede vectorizarse sin problemas:
MOVI VL,#N-1
MOVI VS,#1
(1)

(2)

LV
LV
ADDV
SV

V1,B+1(R1)
V2,C+1(R1)
V3,V1,V2
A+1(R1),V3

LV
SV

V4,A(R1)
D+1(R1),V4

; se escibe el vector A1AN-1


; se lee de memoria el vector A0AN-2

Claramente, en esta ocasin no se puede aprovechar en la segunda


instruccin 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

i
A, 1

A, 1

En este bucle existe una antidependencia, de la segunda instruccin 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
instruccin antes que leer el vector A(i+1) en la segunda.

1.5

43

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

Formalizaremos este caso un poco ms 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
SV

V1,A+1(R1)
D(R1),V1

(1)

LV
LV
ADDV
SV

V2,B(R1)
V3,C(R1)
V4,V2,V3
A(R1),V4

; adelantar la lectura de la instruccin 2

; escribir el resultado de la instruccin 1

Como puede observarse, el cdigo vectorial respeta la antidependencia


original.
1.5.2.1.5 Quinto ejemplo
i

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

1
A, 0
A, 0

B, 1

B, 1

En este bucle aparece el problema ms grave de vectorizacin. Las


dependencias forman un ciclo en el grafo: la instruccin 2 necesita los datos
producidos por la 1 (vector A), y la primera instruccin 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 ms tpico de un ciclo de dependencias es una recurrencia: un
ciclo de una nica instruccin. Por ejemplo:
do i = 3, N-1
1: A(i) = A(i-3) * 3
enddo

A, 3

En cada iteracin, se necesita como operando el resultado producido tres


iteraciones antes. Est claro que una recurrencia no puede vectorizarse:
cmo leer con una instruccin LV V1,A(R1) todo un vector, si
todava no se han generado los elementos del vector?

44

Captulo 1: COMPUTADORES VECTORIALES

Vectores de N dimensiones

1.5.2.2

Los vectores de los ejemplos anteriores son de una dimensin. Pero,


cmo se vectoriza, por ejemplo, una operacin con matrices?
do i = 0, N-1
(todos los vectores son de tamao [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 cmo 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
ms 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 quedara as:
MOVI R2,#N
MOVI VL,#M
MOVI VS,#1
buc: LV
LV
ADDV
SV

; nmero de filas
; longitud de las filas
; paso

V1,B(R1)
V2,C(R1)
V3,V1,V2
A(R1),V3

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

s=1
0,0

0,1

0,M-1

1,0

1,1

1,M-1

N-1,M-1

i
; siguiente fila
; una fila menos

N-1,0 N-1,1

Despus 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.

1.5

45

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

Si se quiere vectorizar la matriz por columnas, el cdigo sera el


siguiente:
MOVI
MOVI
MOVI

R2,#M
VL,#N
VS,#M

buc: LV
LV
ADDV
SV

V1,B(R1)
V2,C(R1)
V3,V1,V2
A(R1),V3

ADDI
SUBI
BNZ

R1,R1,#1
R2,R2,#1
R2,buc

; nmero de columnas
; longitud de las columnas
; paso

A
s=M

0,0

0,1

0,M-1

1,0

1,1

1,M-1

N-1,M-1

i
; siguiente columna
; una columna menos

N-1,0 N-1,1

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


siguiente columna basta con incrementar (+1) la direccin de comienzo 11.
Cuando se utiliza esta segunda opcin 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 dimensin), para escoger la ms adecuada en
funcin de las dependencias de datos entre las instrucciones.

1.5.2.3

Condicin 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. Basndose en ello, debe decidir si el bucle es vectorizable o
no y cmo. A menudo, para generar cdigo vectorial es necesario reordenar
el cdigo original. Al hacerlo, claro est, el compilador debe respetar el
orden de ejecucin que imponen las diferentes dependencias de datos: la
dependencia x y indica que alguna operacin de la instruccin x debe ir
antes que alguna de la y.
Por desgracia, no todos los bucles son vectorizables. Cmo saber cundo
s y cundo 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 estn almacenada en memoria por filas, tal como,
por ejemplo, se hace en C; en Fortran, en cambio, las matrices se guardan por columnas.

46

Captulo 1: COMPUTADORES VECTORIALES

En esos casos, el compilador generar cdigo vectorial para el bucle,


manteniendo en algunos casos el orden original de las instrucciones, y
cambindolo en otros para respetar las dependencias. La condicin anterior
no implica que no se pueda vectorizar el bucle cuando existan ciclos de
dependencias, puesto que, como vamos a ver, pueden aplicarse ciertas
tcnicas que deshacen dichos ciclos.
Aunque las dependencias formen ciclos en el grafo de dependencias,
normalmente slo algunas instrucciones del bucle tomarn parte en dichos
ciclos. Por ello, aunque no se puedan vectorizar todas la instrucciones del
bucle, el compilador debe intentar vectorizar el mayor nmero posible de
operaciones; las instrucciones que presenten problemas se ejecutarn
escalarmente, y el resto vectorialmente (loop fission). Por ejemplo:
MOVI VL,#N-1
MOVI VS,#1

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

B, 0

V1,B+1(R1)
A+1(R1),V1

MOVI R3,#N-1
B, 1

(puede vectorizarse la primera instruccin,


pero no la segunda, debido a la dependencia, una
recurrencia)

1.5.2.4

LV
SV

buc:

FLD
FST
ADDI
SUBI
BNZ

F1,B(R2)
B+1(R2),F1
R2,R2,#1
R3,R3,#1
R3,buc

Test de dependencias

Como hemos comentado, el primer paso del proceso de vectorizacin es el


anlisis 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 (sera 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.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

47

compilacin, por lo que el compilador no tiene informacin suficiente para


tomar una decisin, 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 definicin


que hemos dado de vector: la distancia entre dos elementos consecutivos es
constante. Ms adelante veremos cmo 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 ecuacin:
L1 i1, i2 Z L2

a i1 + b = c i2 + d

es decir, hay que saber si existen dos valores i1 e i2, dentro de los lmites de
iteracin del bucle, para los que coincidan las direcciones de acceso al
vector.

ai+b
ci+d

L1 i1

i2

L2

La expresin anterior es una ecuacin diofntica, y encontrar una solucin


general a la misma es muy complejo. Sin embargo, puede afirmarse que:
No existe dependencia (la ecuacin anterior no tiene solucin),
si (d b) / MCD(a, c) Z, es decir, si no es un entero.
Este test se conoce como el test del mximo comn divisor (MCD). No es
el nico test que aplican los compiladores para analizar las dependencias,
pero es suficiente para los casos ms habituales.

48

Captulo 1: COMPUTADORES VECTORIALES

El test del MCD indica cundo no hay dependencia, no cundo la hay.


Esto es, si el resultado es un nmero entero, las ecuaciones tienen solucin,
pero para saber si existe, o no, dependencia habr que analizar las soluciones
y comprobar que se encuentran dentro de los lmites del bucle. Para ello, a
menudo es suficiente con analizar los trozos del vector que accede cada
instruccin: si no se solapan, entonces no hay dependencia; pero si se
solapan, entonces s que puede haberla y habr que hacer un anlisis ms
detallado 12. Se pueden diferenciar tres casos:

L1

L2

L1

(1)

L2
(2)

L1

L2

(3)

En el primer caso, no hay dependencia; es decir, la hipottica solucin de


la ecuacin est fuera de los lmites del bucle (dentro de los lmites, 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 comn al que acceden (el tipo de dependencia variar en funcin del
tipo de operacin de cada acceso). El tercer caso es el ms complejo. Puede
existir dependencia entre las dos instrucciones; adems, 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 tendramos un
problema similar). Por tanto, estas instrucciones no se pueden vectorizar,
algunos elementos hay que leer antes de escribir sobre ellos y otros despus
de que se hayan escrito (quizs se pueda dividir el bucle en dos partes, en
funcin del punto de cruce, y utilizar tcnicas distintas en cada parte para
generar el cdigo).
Veamos algunos ejemplos:
(1)

do i = 1, 100
A(2*i) = ...
... = A(2*i+1)
enddo

(1 0) / MCD(2, 2) = 1/2

Por tanto, no hay dependencias entre las dos instrucciones; en este caso,
una instruccin 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.

1.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

(2)

do i = 5, 100
A(i-5) = ...
... = A(2*i+90)
enddo

49

(90 (5)) / MCD(2, 1) = 95

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

(3)

A0 ... ... A95


A100 ... ... A290

do i = 1, 100
A(3*i+100) = ...
... = A(2*i-1)
enddo

(100 (1)) / MCD(3, 2) = 101

Podra haber una dependencia; los intervalos de acceso son los siguientes:
wr:
rd:

A103 ... ... A400


A1 ... ... A199

Los dos intervalos tienen un trozo en comn, por lo que puede haber una
dependencia; y en este caso la hay: por ejemplo, la escritura de la
iteracin i = 1 en (A103) se lee en la iteracin i = 52.
(4)

do i = 1, 100
A(6*i+3) = ...
... = A(3*i+81)
enddo

(81 3) / MCD(6, 3) = 26

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

A9 ...

... A603
A84 ... ... A381

Un intervalo est dentro del otro; si existe dependencia, seguramente ser


de dos tipos. Por ejemplo, en la iteracin i = 2 se lee el elemento A87, que
luego se va a escribir (una antidependencia); pero, en la iteracin i = 28,
se lee el elemento A165, que es el resultado de la escritura de la iteracin i
= 27 (una dependencia verdadera).

Atencin. El paso de los accesos a memoria se puede indicar en dos


sitios: en la definicin de los lmites 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
A(i) = ...
B(2*i+5) = ...
enddo

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

50

Captulo 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 tcnicas sencillas que permiten reducir
o anular el impacto negativo de las dependencias y/o vectorizar parcialmente
el bucle. Analicemos, por tanto, las principales tcnicas de optimizacin.

1.5.3 Optimizaciones
El proceso de compilacin es esencial en la obtencin de altas velocidades
de clculo en un computador vectorial. No hay que olvidar que de no obtener
un factor de vectorizacin elevado el rendimiento de la mquina ser
bastante bajo (ley de Amdahl). Acabamos de ver cul es la condicin 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 intrnsecas a la operacin que se realiza, sino que estn
relacionadas con la manera en que se indica dicha operacin (por ejemplo,
las antidependencias o las dependencias de salida). En esos casos, es posible
efectuar pequeas transformaciones del cdigo original que facilitan la
vectorizacin 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 clculo.

1.5.3.1

Sustitucin 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 instruccin 3?

1.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

51

Las dos definiciones, NP1 y NP2, que se han hecho antes del bucle son un
obstculo para poder tomar una decisin. 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 anlisis
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 decisin es clara: no existe antidependencia entre 1 y 2, porque


los ndices de los vectores (i y L+1) nunca sern iguales; de la misma
manera, no existe recurrencia en la instruccin 3, porque L+1 L+2.
Esta tcnica se aplica para deshacer la definicin de cualquier constante.

1.5.3.2 Eliminacin de las variables de induccin


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 anlisis sencillo de cmo se accede a
los vectores nos indica que no existe tal dependencia. La evolucin de las
variables j y k con relacin a i es la siguiente:
i= 1
j= 7
k= 2

2
12
5

3
17
8

4
22
11

5
27
14

...
...
...

52

Captulo 1: COMPUTADORES VECTORIALES

Los valores que toman j y k forman una progresin aritmtica, y no hay


problema en redefinirlas de la siguiente manera:
j=5i+2

k=3i1

Las variables que forman una serie aritmtica en funcin del ndice del
bucle se conocen como variables de induccin. Eliminando las variables de
induccin, 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 comn encontrar variables auxiliares de este
tipo en los bucles de clculo, y, por tanto, el compilador tendr que
detectarlas y sustituirlas por las funciones correspondientes, para poder
realizar el anlisis 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
dbiles, y normalmente su efecto en la vectorizacin del cdigo puede
eliminarse con pequeas transformaciones del cdigo original.
Por ejemplo:
do i = 0, N-2
1: A(i) = B(i) + C(i)
2: D(i) = A(i) + A(i+1)
enddo

1
A, 0

A, 1

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
instruccin debe leer el vector A(i+1), antes que la primera instruccin
escriba A(i) (si no, leeramos 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:

1.5

53

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

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

A, 1

T, 0

A, 0

En la nueva versin, el grafo de dependencias del bucle no presenta


ningn 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 instruccin correspondiente. Slo si no tuviramos un registro
disponible llevaramos el vector a memoria.
As quedar el cdigo vectorial:
MOVI VL,#N-1
MOVI VS,#1

1.5.3.4

(2/0)
(1)

LV
LV
LV
ADDV
SV

V1,A+1(R1)
V2,B(R1)
V3,C(R1)
V4,V2,V3
A(R1),V4

(2)

ADDV V5,V1,V4
SV
D(R1),V5

; se aadelanta la lectura de A+1

; se utiliza lo que se ley antes (V1)

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

1
A, 0

A,2

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


dependencia de salida. Si no se efecta alguna transformacin, el bucle no es
vectorizable. Para mantener el significado del bucle, la segunda instruccin
tiene que efectuar la escritura antes que la primera; o, lo que es equivalente,
hay que atrasar la escritura de la primera instruccin, as por ejemplo:

54

Captulo 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

T, 0

T, 0

A, 2

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 ms tarde a
memoria. As quedar el bucle vectorial:
MOVI VL,#N-2
MOVI VS,#1

1.5.3.5

(1)

LV
V1,B(R1)
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

; instrucccin 1, salvo la escritura

; escritura de la instruccin 1

Intercambio de bucles (loop-interchanging)

Los vectores de dos dimensiones (en general, de n dimensiones) pueden


vectorizarse de ms de una manera, segn su definicin (por filas, por
columnas...). Para escoger una de ellas, hay que tener en cuenta las
dependencias entre instrucciones. Por ejemplo:
j

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

1
A, (0, 1)

Adems del grafo de dependencias (slo hay una instruccin en el bucle,


por lo que de haber alguna dependencia ser consigo misma), hemos
dibujado las dependencias en el espacio de iteraciones, para ver cmo se

1.5

55

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

reparten en el tiempo. De dicho grafo es fcil concluir que el cdigo no


puede vectorizarse tal como est escrito, es decir, por filas, ya que en la
iteracin j se necesitan los resultados de la iteracin j1. Pero igualmente se
observa que no hay inconveniente en vectorizar la operacin por columnas,
de esta manera:
j
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.
j

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

1
A, (0, 1)

2
B, (1, -1)

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
d1 (0, 1)
d2 (1, -1)

- tras intercambiar los bucles


d1 (1, 0)
no hay problemas
d2 (-1, 1) esto no es posible

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


bucles.

56

Captulo 1: COMPUTADORES VECTORIALES

En algunos casos, es necesario aplicar fisin e intercambio de bucles para


poder vectorizar el bucle. Por ejemplo,
j

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

A, (1, 0)

i
B
A

2
B, (0, 1)

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 instruccin (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 instruccin (vector A) la
vectorizaremos por filas, y la segunda (vector B) por columnas,
intercambiando los bucles.
Sea como sea, slo se intercambian los bucles si con ello se facilita la
vectorizacin del cdigo 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 pequeos, por lo que el rendimiento no ser alto (es una funcin
de N). Sin embargo, si se cambia el orden se procesarn 10 vectores de 100
elementos, con lo que se obtendr una mayor velocidad de proceso.

1.5.3.6

Expansin 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.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO 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 vectorizacin 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 cdigo 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 ningn problema para vectorizar el bucle anterior.
Esta tcnica, convertir un escalar en un vector, se conoce con el nombre de
expansin 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 iteracin del bucle:
suma = suma(N-1)

1.5.3.7

Fusin de bucles (loop fusion)

Con esta optimizacin se intenta fundir dos (o ms) 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

58

Captulo 1: COMPUTADORES VECTORIALES

Los dos programas del ejemplo son idnticos, pero el segundo es ms


sencillo de ejecutar. Para empezar, el compilador puede aprovechar en la
segunda instruccin las operaciones de la primera, leyendo el operando de
un registro (no haremos SV Z y luego LV Z); adems de ello, todo el cdigo
asociado con la ejecucin del bucle slo se ejecutar una vez
(direccionamiento, longitud y paso de los vectores...).
De todas maneras, no es seguro que el compilador efecte esta
optimizacin automticamente, puesto que para ello debera realizar el
anlisis de dependencias ms all del bloque bsico.
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 =
Z(i)
enddo
do i =
R(i)
enddo

1.5.3.8

1, L
= X(i) + Y(i)
1, L
= Z(i+1) + 1

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

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 tamao de
los vectores es pequeo, puede ser interesante "juntar" dos (o ms) 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 pequeos. Para aprovechar mejor el tamao 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

1.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO 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 tcnicas de optimizacin que acabamos de ver son las ms habituales,


aunque existen otras. Sin embargo, ms de una vez ocurre que lo que parece
muy simple de vectorizar resulta muy complejo de hacer automticamente
(para el compilador). En esos casos, la ayuda del programador resulta el
camino ms sencillo. Esa ayuda suele efectuarse mediante pseudoinstrucciones para el compilador, al que se le indica qu trozos de cdigo
debe traducir a cdigo vectorial sin preocuparse del anlisis de
dependencias. Veamos un ejemplo:
do i = a, b
X(i) = Y(i) + X(i+M)
enddo

Sin ms informacin, el compilador no puede vectorizar el bucle, porque


puede existir una recurrencia en X, en funcin 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
sustituira por su valor). Sin embargo, puede ser que el usuario tenga
informacin extra sobre la variable M. Por ejemplo, tal vez sabe que se trata
de un parmetro fsico que siempre es positivo (o que, por ejemplo, se acaba
de ejecutar M = A(i) * A(i)). Si es as, bastara con indicarle al
compilador que vectorizara el bucle, sin ms.
Por otro lado, el compilador podra tambin 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 cdigo vectorial; la segunda,


en cambio, escalarmente.

60

Captulo 1: COMPUTADORES VECTORIALES

1.5.4 Vectores de mscara 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 clculo cientfico: el uso de mscaras y los vectores de paso
variable.

1.5.4.1

Uso de mscaras

En ms de una ocasin, 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 sabramos cmo


ejecutar vectorialmente ese bucle, pero es un caso tan habitual que tiene una
solucin especfica: el uso de un registro de mscara. El registro de
mscara (VM, vector mask) es un registro vectorial booleano (1/0) especial,
que guarda el resultado de una operacin lgica sobre vectores. Todas la
operaciones vectoriales toman en consideracin el registro VM para decidir
qu elementos del vector hay que procesar y cules no.
El procesador dispone de instrucciones especficas para trabajar con el
registro de mscara. Por ejemplo:
SxxV

V1,V2

Compara dos vectores, elemento a elemento, y deja los


resultados (1/0) en el registro de mscara VM (xx =
operacin de comparacin: EQ, NE, GT...).

SxxVS V1,F1

Igual, pero utilizando un escalar para la comparacin.

CVM

Clear vector mask, para inicializar la mscara.

POP

R1,VM

Cuenta el nmero de bits activados en el registro de


mscara; deja el resultado en R1.

Usando esas instrucciones, podemos ejecutar vectorialmente el bucle


anterior de la siguiente manera:

1.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO VECTORIAL

MOVI
MOVI
MOVI

61

VL,#N
VS,#1
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 instruccin SGTVS compara los elementos de V1 con el contenido de


F1, y el resultado se deja en el registro VM. Las operaciones vectoriales
siguientes slo tendrn efecto en las posiciones de los vectores indicadas en
el registro VM. La instruccin CVM inicializa nuevamente el registro VM (los
valores concretos dependen del computador).
Ten en cuenta que el tiempo de ejecucin no cambia; cuando se ejecuta
ADDVS, nicamente se enmascaran las escrituras en el registro destino.
Tambin 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 mquinas en cambio, se enmascara tanto la operacin en
la unidad funcional como la escritura en el registro.

1.5.4.2

Vectores de ndices

En muchas aplicaciones cientficas se utilizan estructuras de datos muy


grandes (por ejemplo, una matriz de 10.000 10.000 elementos). Sin
embargo, tal vez slo haya que procesar unos pocos elementos de esas
estructuras. Un ejemplo podra ser el de la figura.
s=3
5
14

4
1

Aunque la matriz es muy grande, slo se van a procesar los elementos


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

62

Captulo 1: COMPUTADORES VECTORIALES

siguiente) de los vectores que hemos utilizado ha sido constante. Sin


embargo, el vector que definimos en la figura no tendra un paso constante.
Por tanto, no podramos 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: cmo 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 mtodo 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 operacin 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 ejecucin: se ejecuta la operacin indicada.
3. Fase de difusin (scatter): se llevan los resultados de la operacin
vectorial a memoria, a las posiciones correspondientes, utilizando
nuevamente el registro de ndices.
Para efectuar las operaciones de agrupamiento y difusin 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, ...,


(Lmax1)R1.

Por ejemplo, analicemos este bucle:


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

1.5

TCNICAS DE COMPILACIN PARA GENERAR CDIGO 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
MOVI
CVI
MULV

VL,#M
R1,#1
V4,R1
V5,V4,V4

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

; 0, 1, 2, 3...
; registro de ndices: i*i
; direccionamiento indexado

Para indicar los ndices hemos utilizado el registro V5, en el que hemos
cargado previamente los resultados de la funcin i*i. Despus, hemos
utilizado el modo de direccionamiento indexado (base + vector de ndices)
para acceder al vector.
El modo de direccionamiento indexado puede utilizarse tambin, por
ejemplo, para ejecutar el bucle del apartado anterior if (B(i)>5)
then A(i) = A(i) + 1 de la siguiente manera:
MOVI
MOVI

VL,#N
VS,#1

MOVI
F1,#5
LV
V1,B(R1)
SGTVS V1,F1

; generar mscara (VM)

MOVI
CVI
POP
MOV
CVM

; create vector index: 0, 1, 2... teniendo en cuenta VM


; p.e.: VM = 10011101 V2 = 03457
; contar bits a 1 en el registro VM (5)
; cargar el registro VL (nmero de elementos)
; inicializar mscara

R2,#1
V2,R2
R1,VM
VL,R1

LVI
V3,A(V2)
ADDVI V4,V3,#1
SVI
A(V2),V4

; utilizar V2 como registro de ndices,


; y procesar solamente VL elementos

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


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

64

1.6

Captulo 1: COMPUTADORES VECTORIALES

RESUMEN

Un computador vectorial es una mquina especficamente diseada para el


procesamiento de vectores (o, visto de otra, manera para la ejecucin 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 incluirn ambos tipos de cdigo, vectorial y escalar, por lo
que es necesario que el computador ejecute cdigo escalar eficientemente).
El conjunto de instrucciones de estas mquinas incluye instrucciones
vectoriales (LV, ADDV, SV...) que permiten la ejecucin de una operacin
vectorial completa sobre todos los elementos de un vector con una sola
instruccin. Las caractersticas arquitecturales bsicas de un procesador
vectorial son: el uso de registros vectoriales, el encadenamiento entre
instrucciones, un gran ancho de banda con memoria (mltiples buses de
acceso a memoria) y una memoria entrelazada en muchos mdulos. As, el
modelo de ejecucin lleva a que el tiempo de ejecucin 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 bsicas de rendimiento de un procesador vectorial ejecutando
un determinado programa son R (velocidad de clculo con vectores de
longitud infinita) y N1/2 (tamao mnimo de los vectores para conseguir al
menos la mitad de la velocidad mxima). Tal como ocurre con otros modelos
de proceso, la velocidad pico (peak performance) de un procesador vectorial
no es un parmetro 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 clculo vectorial (start-up) sea un parmetro muy
importante a considerar.
Sin embargo, hay varios factores que limitan el rendimiento. Por una
parte, el hardware el tamao de los registros vectoriales, el nmero de
buses a memoria, el nmero de unidades funcionales. Pero el parmetro
que ms puede llegar a reducir el rendimiento de una mquina vectorial es el
factor de vectorizacin, f: fraccin de cdigo que se ejecuta en modo
vectorial.
Por ello, no es posible olvidar el papel que un buen compilador debe
realizar en este tipo de mquinas. El compilador vectorial es el responsable
de generar cdigo vectorial a partir de un cdigo escalar estndar, y debe
lograr el factor de vectorizacin ms alto posible. En caso contrario, y tal

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 tcnicas de vectorizacin son ya clsicas y las aplican todos los
compiladores. Esas estrategias se basan en el anlisis de las dependencias
entre instrucciones, y son comunes a los compiladores que intentan
paralelizar el cdigo para ser ejecutado en sistemas con ms 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 computacin, los computadores
vectoriales han estado siempre a la cabeza de las mquinas ms rpidas en
clculo cientfico, aunque la evolucin de los sistemas multiprocesador ha
relegado a estos procesadores a un segundo lugar. Pioneros en el uso de
tecnologas avanzadas (ECL) y aportando soluciones arquitecturales
novedosas, han marcado, hasta hoy en da, la referencia de velocidad de
cmputo.
Aunque el primer computador vectorial fue el CDC STAR-100 (1972), el
computador que marc la historia de este tipo de mquinas fue el Cray-1
(1975), en la que se utilizaron por vez primera los registros vectoriales y el
encadenamiento. Junto a ello, tomando en consideracin los resultados de la
ley de Amdahl, utilizaba un procesador escalar de gran velocidad (el ms
rpido del momento). En todo caso, y por limitaciones tecnolgicas del
momento, slo dispona de una unidad vectorial, (es decir, slo poda
ejecutar una instruccin LV o SV a la vez).
En 1981, la casa CDC pone en el mercado el CYBER 205, evolucin
natural del computador STAR: segua manteniendo el modelo M/M, pero
dispona 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 mquina de CRAY fue el Cray X-MP; una evolucin natural
del computador anterior (reloj ms rpido, ms buses a memoria, posibilidad
de utilizar ms de un procesador). Pronto aparece en el mercado el Cray-2: 4
procesadores, 156 MB de memoria DRAM (palabras de 60 bits), reloj ms
rpido, pero latencias ms altas (ciclos) en las unidades funcionales, sin
encadenamiento, y un nico bus; no era una gran alternativa, salvo por su
gran memoria.

66

Captulo 1: COMPUTADORES VECTORIALES

En los 80 aparecen en el mercado los superminicomputadores, mucho ms


baratos que los anteriores. Entre ellos el C-1 y C-2 (2 procesadores) de
Convex. El xito de estas mquinas, adems 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 despus aparecen
los Hitachi S810 y NEC SX/2. En general, estas mquinas japonesas podan
lograr velocidades pico muy altas, pero los tiempos de inicio de las
operaciones vectoriales (start-up) eran muy altos, lo que los haca 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 evolucin del X-MP (8 procesadores,
reloj ms rpido). La casa Cray contina adelante y ofrece el C90 16
procesadores a 240 MHz (y 15 millones de dlares) y, ms tarde, el T90.
Comercializa tambin el J90, una versin CMOS, ms barata (1 milln $).
Ms tarde, la casa Cray comercializ el computador vectorial Cray Inc.
SV1(ex), sucesor del J90 y del T90, en el que se utiliza tecnologa CMOS (y
se abandona definitivamente la rpida y cara ECL, tal como hicieron en su
da Fujitsu y NEC). Se trata de un multiprocesador de memoria compartida
que, en su configuracin mxima, utiliza 128 procesadores vectoriales, a 450
MHz y 1,8 Gflop/s. Otra caracterstica a destacar es el uso de una cache
comn de 256 kB para escalares y vectores. En anteriores diseos 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, evolucin natural
del VPP700: reloj ms rpido (300 MHz) y 16 vector pipes de tipo multiplyand-add. En teora, por tanto, cada procesador es capaz de lograr 9,6
Gflop/s. El procesador escalar va a 1,2 Gflop/s. En su configuracin mayor,
se trata de un multicomputador de memoria distribuida de 128 procesadores,
en el que la comunicacin punto a punto se efecta a 1,6 GB/s, utilizando
como red de comunicacin un full distributed crossbar.
Finalmente, otra mquina japonesa ms: el NEC SX-5/16A, un
multiprocesador vectorial de memoria distribuida. Sus caractersticas
principales son: reloj a 313 MHz, 16 unidades vectoriales, 10 Gflop/s por
procesador. La versin SX-6 de ese procesador fue la base del
supercomputador Earth Simulator.

1.6

RESUMEN

67

Todas las mquinas citadas han sido siempre las ms rpidas del
momento, pero tambin, con diferencia, las ms caras. La evolucin de los
microprocesadores en los ltimos aos, junto con el uso del paralelismo, ha
ido arrinconando a este tipo de arquitecturas, con lo que, en un futuro
cercano, parece que jugarn un papel cada vez menor en el campo del
clculo cientfico. Para ello, habr que aprender a programar y utilizar los
sistemas de muchos procesadores de manera eficiente, para aprovechar su
gran potencial de clculo. 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 da, los procesadores vectoriales aparecen como nodos
especializados de un sistema paralelo ms general. En ese tipo de sistemas,
MPP (massive parallel processors) hay que buscar el futuro del clculo
paralelo: miles de procesadores colaboran en la resolucin de un problema y
se comunican entre ellos mediante una red de comunicacin de gran
velocidad. En dicha red, algunos procesadores estn especializados en
determinado tipo de clculo, por ejemplo, clculo vectorial.
Siempre es posible, en todo caso, encontrarse con sorpresas en la
evolucin de los computadores. En el top500 de junio de 2002 (lista de las
500 mquinas ms rpidas del mundo, que se publica dos veces al ao), se
produjo un cambio significativo. En contra de la lnea seguida en los ltimos
aos, el nmero 1 de la lista fue un (multi)computador vectorial: Earth
Simulator. Se trataba de un computador japons de propsito especfico 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) haba 41
computadores vectoriales.
En 2009 disponemos de una nueva versin de dicha mquina (1280
procesadores NEC SX-9, de 350 millones de transistores, a 3,2 GHz) que ha
logrado una velocidad de clculo de 122,4 TF/s. Cada CPU dispone de una
unidad superescalar (de 4 vas) y una unidad vectorial con las siguientes
caractersticas: 72 registros vectoriales de 256 elementos y 8 conjuntos o
pipes de unidades funcionales vectoriales (+, , /, lgicas, mscaras y
LV/SV). Cada chip puede alcanzar los 102,4 GF/s.

68

Captulo 1: COMPUTADORES VECTORIALES

El nuevo Earth Simulator fue el nmero 22 de la lista top500 de junio de


2009, pero es ya la nica mquina vectorial de la lista; parece, por tanto, que
la arquitectura vectorial tiende a desaparecer.
En el ltimo captulo haremos un repaso de la situacin de la lista top500
y de las principales mquinas, arquitecturas y tendencias que en ella
aparecen.

Computadores Paralelos
(conceptos bsicos)

2.1

INTRODUCCIN

Aunque los procesadores son cada vez ms rpidos, existen numerosas


aplicaciones para las que la velocidad de clculo de un nico procesador
resulta insuficiente. La alternativa adecuada para esas aplicaciones es el uso
de paralelismo. Con el trmino paralelismo se indica que la ejecucin de un
determinado programa se reparte entre muchos procesadores, que trabajan
simultneamente.
Pueden utilizarse diferentes niveles de paralelismo. Por ejemplo, se
explota el paralelismo a nivel de instruccin (ILP) cuando se segmenta la
ejecucin de las instrucciones de un programa: en un momento dado, se
estn ejecutando muchas instrucciones a la vez, pero en fases de ejecucin
diferentes. Tambin puede explotarse el paralelismo en los datos. El ejemplo
con ms xito de esa alternativa son los computadores vectoriales que
acabamos de analizar. En todos esos casos (y en otros similares, como
VLIW), slo existe un contador de programa o PC, es decir slo se ejecuta

70

Captulo 2: COMPUTADORES PARALELOS (conceptos bsicos)

un programa bajo una nica unidad de control. En los prximos captulos


vamos a estudiar el paralelismo a nivel de programa, es decir, vamos a
analizar cmo repartir la ejecucin de un programa entre P procesadores. Si
fabricar rplicas de un procesador es un proceso relativamente sencillo y
barato, por qu no utilizar P procesadores para intentar ejecutar un
programa P veces ms rpido?
Recordemos un momento la clasificacin de Flynn de los computadores,
que toma en cuenta el nmero 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 simultneo de unidades funcionales
debido a la segmentacin de la ejecucin de las instrucciones.
SIMD: un solo flujo de instrucciones (un contador de programa), pero
muchos flujos de datos. En funcin del uso de la memoria, pueden
diferenciarse dos familias: los procesadores vectoriales (memoria
compartida) y los procesadores en array de memoria distribuida.
MIMD: mltiples flujos de datos y de instrucciones. Se trata del
verdadero modelo de paralelismo, en el que existen muchos
programas en ejecucin simultanea. ste es el tipo de mquina que
vamos a analizar a partir de ahora.
Antes de ello, una pequea precisin acerca del uso de P procesadores,
puesto que podemos tener diferentes alternativas, en funcin 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 transmisin de datos entre los usuarios).
Cada programa se ejecuta segn el modelo de un nico procesador.
Tolerancia a fallos. En funcin de la aplicacin, existen diferentes
maneras de hacer frente a los fallos del sistema. Por ejemplo, se repite
la ejecucin 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 mquina 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 funcin para
que el servicio ofertado est siempre disponible (high availability).

2.2

71

COMPUTADORES DM-SIMD

Se ejecuta el mismo programa, repetido en todos los procesadores,


pero con datos diferentes; por ejemplo, para hacer mltiples
simulaciones independientes de un proceso fsico en menor tiempo
(mejora del throughput). O, en los servidores, para poder atender a
mltiples peticiones simultneas (por ejemplo, en una base de datos).
Para ejecutar un programa P veces ms rpido (high
performance). ste es el tipo de aplicacin que nos interesa.
Comparado con los anteriores casos, la diferencia fundamental va a
estar en la comunicacin entre procesos, que va a ser mucho ms
intensiva y que habr que efectuar en tiempos muy breves
(microsegundos). Esta comunicacin es una parte de la ejecucin y se
produce como consecuencia de ejecutar en paralelo. Existen diferentes
arquitecturas de este tipo, en funcin del nmero de procesadores, el
nivel de acoplamiento entre ellos (frecuencia de la comunicacin),
capacidad y complejidad de los procesadores, mecanismos de
sincronizacin y control, tamao de las tareas, etc.
En los prximos captulos vamos a analizar las principales caractersticas
y problemas de este nuevo modelo de ejecucin. Pero antes de ello, vamos a
definir los principales conceptos y terminologa de esta rea.

2.2

COMPUTADORES DM-SIMD

Acabamos de analizar los computadores vectoriales, mquinas SIMD de


memoria compartida. Aunque no son nuestro objetivo, vamos a hacer un
breve resumen de las caractersticas 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 instruccin (la misma en todos los
procesadores en el caso de los arrays) se procesan mltiples datos.
Red de comunicacin

Computador
front-end

Procesador
de control

Pr + M + I/O

Array de clculo

72

Captulo 2: COMPUTADORES PARALELOS (conceptos bsicos)

Las caractersticas 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 rpidos. En el caso de los
procesadores serie, de 1 bit, se procesan datos de cualquier tamao,
siendo la latencia proporcional al tamao de los mismos.
- Control: el control es centralizado. Todos los procesadores ejecutan la
misma instruccin en el mismo momento (lock-step), sobre datos
diferentes. Si es necesario, la ejecucin puede controlarse mediante
mscaras, que indican en qu procesadores s y en cules no se debe
ejecutar la instruccin 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 cdigo 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 comunicacin facilita la
comunicacin entre los procesadores; las redes ms 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
seales y de imgenes, o cierto tipo de simulaciones (Montecarlo...).
Aunque el espacio de memoria sea comn, 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".

2.3

73

COMPUTADORES MIMD

ILLIAC IV, Solomon, CM1, BSP, DAP, Quadrics Apemille, procesadores


sistlicos... son algunas de las mquinas ms 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
caractersticas principales de estos sistemas.

2.3

COMPUTADORES MIMD

Como ya hemos comentado, en un sistema MIMD las aplicaciones se


reparten en mltiples procesos que se ejecutan en diferentes procesadores.
Desde el punto de vista de la arquitectura del sistema, la primera cuestin a
aclarar sera: cmo 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 comunicacin entre procesos es relativamente sencilla,
utilizando para ello variables compartidas en la memoria comn. Para pasar
un dato de un proceso a otro, basta con dejar el dato en una determinada
posicin de memoria, donde lo leer el proceso destino.
Procesadores (+ MC)

P0

P1

Pp1

Red de comunicacin

M0

Mm1
Memoria principal

sistema
E/S

74

Captulo 2: COMPUTADORES PARALELOS (conceptos bsicos)

Para conectar los procesadores y la memoria se utiliza una red de


comunicacin. La red ms sencilla es el bus; se conoce perfectamente su
funcionamiento y no es difcil 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. Tambin
pueden utilizarse otro tipo de redes, que analizaremos ms adelante. Para
simplificar, vamos a suponer que la red de comunicacin es un bus.
A este tipo de arquitectura se le conoce habitualmente con el nombre de
multiprocesador, y tambin como SMP (symmetric multiprocessor), UMA
(uniform memory access) o sistemas paralelos de alto grado de
acoplamiento. Dada la red de comunicacin, un bus, el nmero 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 comn: todas las direcciones son locales y hacen referencia
a la memoria propia del procesador. Por ello, la comunicacin entre procesos
no puede hacerse, como en el caso anterior, mediante posiciones comunes de
memoria. As, la comunicacin se realiza mediante paso de mensajes,
utilizando para ello la red de comunicacin. Si Pi debe enviar datos a Pj,
formar con ellos un mensaje y lo enviar a la red; los controladores de la
red se encargarn de ir retransmitiendo el mensaje hasta que llegue a su
destino.
Nodos:
Procesador (+ MC) + Memoria principal + E/S + Contr. comunic.

P0

Pp1

E/S

E/S

K
Red de comunicacin

2.3

COMPUTADORES MIMD

75

El objetivo de este modelo es conseguir paralelismo masivo, es decir,


poder utilizar un nmero grande de procesadores. Por ello, no se utiliza un
bus como de red de comunicacin, sino redes tales como mallas y toros de 2
y 3 dimensiones, hipercubos, rboles, etc., que analizaremos ms adelante.
A este tipo de arquitectura se le conoce como multicomputador (o
tambin como sistema dbilmente acoplado, MPP o Massively Parallel
Processors...).

2.3.3 Memoria lgicamente compartida pero


fsicamente distribuida (distributed shared memory)
Existe una tercera alternativa, que corresponde a una mezcla de las dos
anteriores. Cuando el espacio de memoria es comn, la programacin de
aplicaciones suele resultar ms sencilla, pero la memoria se convierte en un
cuello de botella: se producen grandes atascos de trfico, provocados por los
procesadores del sistema, que tienen que acceder a la memoria comn a
travs de una red tipo bus. Cuando la memoria es privada en cada
procesador, este problema desaparece, pero la comunicacin entre
procesadores es ms compleja y tambin lo son los modelos de
programacin.
Un anlisis sencillo de los programas muestra que los procesadores no
hacen un uso homogneo de la memoria, es decir, no acceden con la misma
probabilidad a cualquier posicin de memoria; ello permite pensar en una
alternativa mixta: compartir el espacio de memoria pero distribuirla
fsicamente 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 jerrquica: los accesos locales sern rpidos, pero los externos
sern mucho ms lentos, puesto que hay que salir a la red de comunicacin.
Esperamos, en todo caso, que el acceso a la memoria local sea mucho ms
frecuente que a la memoria remota, y que la red de comunicacin se utilice
principalmente para la comunicacin 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 tambin como MPP.

76

Captulo 2: COMPUTADORES PARALELOS (conceptos bsicos)

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 mayora de
los supercomputadores actuales los nodos que forman el sistema, y que se
conectan mediante una red de comunicacin, no son simples procesadores,
sino pequeos 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 diseo especfico, han aparecido en el mercado con
fuerza los sistemas formados por hardware sencillo y barato: computadores
de propsito general conectados entre s mediante redes ms o menos
sencillas derivadas de las tecnologas 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 cmputo y una red de
comunicacin (junto con el software de gestin y programacin adecuado).
La eficiencia del cluster ejecutando cdigo paralelo ser funcin de ambos,
nodos y red. En el caso ms simple, los nodos son simples PCs y la red de
comunicacin es (Gigabit) Ethernet. Ese tipo de sistema se conoce como
Beowulf; es la opcin ms barata, pero tambin la de menores prestaciones,
aunque ofrece buenos resultados en aquellos casos en los que la
comunicacin entre procesos no es relevante.
Para conseguir clusters ms eficientes, pueden usarse pequeos sistemas
SMP como nodos de clculo y redes de comunicacin ms sofisticadas
(Myrinet, Infiniband, Quadrics); cuando el nmero de procesadores de
cada nodo del cluster es mucho mayor que el nmero de nodos, el sistema se
conoce tambin con el nombre de constellation.
Todos los fabricantes ofrecen hoy en da diversos tipos de clusters en sus
catlogos (custom clusters) como una alternativa interesante para conseguir
mquinas de alto rendimiento a un coste razonable. Adems, 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.

2.4

ALGUNOS PROBLEMAS

2.4

77

ALGUNOS PROBLEMAS

En cualquiera de sus estructuras, un computador MIMD presenta


numerosos problemas nuevos para resolver. Por ejemplo:
Gestin del sistema: la mquina construida a partir de mltiples
procesadores o, incluso, computadores autnomos, debe aparecer al
usuario como un nico sistema integrado. Van a ser necesarios para
ello nuevos sistemas operativos especficos, mecanismos adecuados
para la gestin distribuida de las tareas, nuevas herramientas de
monitorizacin, controles de seguridad avanzados, etc. Son todas ellas
cuestiones muy importantes, pero no las trataremos en este texto.
Reparto de tareas. Sabemos cmo repartir un programa secuencial
entre P procesadores? En algunos casos ser muy sencillo; por
ejemplo, es muy fcil repartir entre N procesadores la ejecucin del
bucle do i = 1,N {A(i) = A(i) + 1}; cada uno ejecuta una
iteracin del bucle, cualquiera de ellas, ya que todas las iteraciones
son independientes y por tanto da igual cmo se haga. Pero en los
casos ms 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 mquina paralela. En general, la programacin paralela es ms
compleja que la programacin 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 ms rpido, ya que la tarea ms larga ser la
que marque el tiempo final de ejecucin. El reparto de carga puede ser
esttico en tiempo de compilacin o dinmico en tiempo de
ejecucin. El primero es ms sencillo y no aade sobrecargas a la
ejecucin del programa, pero es ms difcil mantener el equilibrio de
la carga de trabajo. El segundo es ms costoso en tiempo de ejecucin,
pero permite repartos ms 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, cmo se enteran del nuevo
valor de la variable el resto de procesos? es decir, cmo se mantienen

78

Captulo 2: COMPUTADORES PARALELOS (conceptos bsicos)

"coherentes" los datos compartidos? Como veremos en los prximos


captulos, la solucin depende de la arquitectura del sistema.
Comunicacin. Cuando hablamos de paralelismo,
entre procesos es el tema principal. Y junto a
comunicacin (sobre todo en los sistemas DSM
sistema paralelo, el tiempo de ejecucin de un
modelarse como:

la comunicacin
ello, la red de
o MPP). En un
programa puede

Tp = Tej + Tcom
donde Tej representa el tiempo de ejecucin real y Tcom el de
comunicacin. El tiempo de ejecucin se reduce (en teora) con el
nmero de procesadores, pero el de comunicacin en cambio, crece.
La siguiente figura muestra una simplificacin de ese compartimiento.

Tp

Tcom

Tej

Nm. procesadores

Como se observa en la figura, no siempre es una buena solucin


utilizar un nmero elevado de procesadores, ya que las necesidades de
comunicacin pueden echar por tierra cualquier otra ventaja. Es
necesario por ello encontrar un punto de equilibrio.
Un tipo especial de comunicacin es la sincronizacin. 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 sincronizacin pueden generar mucho trfico en la red y momentos
de gran congestin en el acceso a variables comunes. Analizaremos
este problema un poco ms adelante.
Considerando el reparto de tareas y la comunicacin, suelen
distinguirse diferentes tipos o niveles de paralelismo:
paralelismo de grano fino (fine grain): las tareas que se reparten
entre los procesadores son "pequeas", y la comunicacin entre
ellas es muy frecuente, aunque no se intercambian mucha
informacin.

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 slo se comunican
entre ellas de vez en cuando, aunque en esos casos se intercambia
gran cantidad de informacin.

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 ms rpido cuando se utilizan P procesadores.
Para comparar sistemas de un solo procesador y de P procesadores suelen
utilizarse dos parmetros: el factor de aceleracin (speed-up) y la eficiencia
(efficiency).
El factor de aceleracin mide cuntas veces ms rpido se ha ejecutado un
determinado programa, es decir:
fa = Ts / Tp
donde Ts es el tiempo de ejecucin 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 mximo factor de


aceleracin posible.
En el mejor de los casos, tendremos que Tp = Ts / P; es decir, que el
programa se ejecuta P veces ms rpido usando P procesadores:
fa = Ts / (Ts / P) = P
efic = fa / P = 1
Se trata, en todo caso, de la situacin ideal que, debido a mltiples
problemas reparto no equilibrado de la carga, comunicacin,
sincronizacin... es difcil de lograr 13. En todo caso, aunque no logremos
13 En algunos casos, pueden conseguirse factores de aceleracin superlineales, es decir, mayores que P.
En general, son debidos a otros factores, ya que, adems de P procesadores, el sistema paralelo
dispone de ms memoria, ms capacidad de entrada/salida, etc. Tal vez los datos/programas que no
caban en la memoria de un procesador, s quepan ahora en todo el sistema, con lo que, como
sabemos, se ahorrar tiempo.

80

Captulo 2: COMPUTADORES PARALELOS (conceptos bsicos)

que el factor de aceleracin sea P, deberamos conseguir que creciera


linealmente con el nmero de procesadores (o, lo que es equivalente, que la
eficiencia fuera constante, independiente de P): si se duplica el nmero de
procesadores, que se duplique tambin el factor de aceleracin.
No todos los programas presentan esas caractersticas, ya que no podemos
olvidarnos de la ley de Amdahl. Tal como ha ocurrido en el caso de los
computadores vectoriales, los programas ms habituales no pueden
ejecutarse completamente en paralelo: siempre queda una parte del cdigo
que hay que ejecutar en serie (o en un nmero reducido de procesadores).
Como ejemplo, supongamos que una fraccin del cdigo, f, puede
ejecutarse en P procesadores, mientras que el resto, 1f, debe ejecutarse en
un nico procesador. En ese caso, el tiempo de ejecucin debe escribirse as:
Tsp = f Tp + (1f) Ts

(en general, Tsp =

f
i =1

Ts
)
i

Si no consideramos el tiempo de comunicacin, y tomamos el mejor caso,


Tp = Ts / P, entonces el speed-up o factor de aceleracin ser:
fa = Ts / Tp = P / [ P (1f) + f ]
Por ejemplo, si P = 1024 y f = 0,98, entonces fa = 47,7, muy lejos del
hipottico 1024. Como se muestra en la siguiente figura, el factor de
aceleracin se satura, con una asntota de valor 1 / (1f), y queda muy lejos
del comportamiento lineal.

2.5

81

RENDIMIENTO DEL SISTEMA PARALELO (leyes de Amdahl y Gustafson)

De acuerdo a la ley de Amdahl, el efecto de la parte de cdigo que haya


de ejecutarse en serie es muy grande cuando el nmero de procesadores es
grande. Si se cumple en la realidad lo que pronostica dicha ley, va a ser muy
difcil conseguir factores de aceleracin (speed-up) altos. Como acabamos
de ver en el ejemplo anterior, basta que un 2% del cdigo tenga que
ejecutarse en serie para que el factor de aceleracin 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. Dnde est el
error? Cuando hemos planteado la ley de Amdahl hemos considerado la
siguiente hiptesis: se utilizan P procesadores para hacer que un
determinado algoritmo se ejecute ms rpido. Pero en realidad, muchas
veces lo que ocurre es que se utilizan P procesadores para ejecutar un
problema de tamao ms grande en el mismo tiempo. Por ejemplo, se
ejecutan ms ciclos de simulacin o se hacen anlisis considerando una red
de ms puntos, etc. En resumen, se mantiene el tiempo de ejecucin, no las
dimensiones del problema.
Se ha podido comprobar experimentalmente que cuando se hace crecer el
tamao del problema (por ejemplo, se usan matrices ms grandes) no suele
crecer el tamao del cdigo que se debe ejecutar en serie (al menos no en la
misma proporcin). Esto es equivalente a decir que al crecer el tamao del
problema crece tambin f (no es un valor constante). Si es as, para calcular
el factor de aceleracin deberamos comparar estas dos situaciones:

trozo que hay que


ejecutar en serie

trozo que se puede


ejecutar en paralelo

(1f) Ts

f Ts

1 procesador
problema de mayor tamao

f Ts P

(1f) Ts
en paralelo

P procesadores

(1f) Ts

f Ts / P

tamao del problema constante

en paralelo

(1f) Ts

f Ts

tiempo de ejecucin constante

82

Captulo 2: COMPUTADORES PARALELOS (conceptos bsicos)

Por tanto, cuando el tiempo de ejecucin se mantiene constante:


Ts = (1f) Ts + f Ts P
Tp = (1f) Ts + f Ts = Ts

fa = Ts / Tp = (1f) + f P

La expresin que acabamos de obtener para el factor de aceleracin se


conoce como ley de Gustafson, y es lineal con P, lo que asegura que se
pueden conseguir factores de aceleracin elevados. Por ejemplo, como en el
caso anterior, si P = 1024 y f = 0,98, el factor de aceleracin que se consigue
resulta ser fa = 1003,5.
Como comparacin, la siguiente figura muestra la evolucin con P del
factor de aceleracin en su doble versin, para el caso f = 0,9.

En la realidad, y para un programa dado, el factor de aceleracin concreto


estar en algn punto entre esos dos extremos.
En los siguientes captulos 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 sincronizacin, el modelo de consistencia, la red de comunicacin,
y las estrategias de paralelizacin de bucles. En el ltimo captulo
presentaremos brevemente el mercado de sistemas paralelos de alta
velocidad, algunas de las implementaciones de ms xito, as como una
pequea introduccin a las herramientas ms utilizadas para programar
aplicaciones paralelas (OpenMP y MPI).

Coherencia de los Datos


en los Computadores SMP

3.1

PRESENTACIN DEL PROBLEMA Y REVISIN


DE CONCEPTOS

La velocidad de ejecucin 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 jerrquica: registros, cache interna, cache externa,
memoria principal, (discos...). Cada uno de los niveles es un subconjunto del
nivel superior. Los registros son los ms rpidos y cercanos al procesador,
pero su capacidad es pequea (por ejemplo, 128 registros de 64 bits); en el
extremo opuesto tenemos la memoria principal, de alta capacidad (ya con 2 o
ms GB) pero de tiempo de respuesta mucho mayor (p.e., 50 ns).

84

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

El funcionamiento de la jerarqua 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 ms rpida los datos que
prevemos va a necesitar el procesador. sa es por tanto la funcin de la
memoria cache: tener preparados los datos (instrucciones) que "pronto" o
con ms "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
bloques 14 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 estn en las caches del sistema sean todas
iguales, es decir, que sean coherentes. De no asegurarse la coherencia de los
datos, los procesos no podrn utilizar variables compartidas, ya que nunca
estarn 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 tambin aparece en los sistemas con un solo
procesador, ya que tambin en ese caso se utilizan copias de los datos: una
en la memoria cache y otra en la memoria principal 15. El problema sin
embargo no es complicado de resolver, ya que ambas copias estn 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
polticas 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 tamao del bloque directamente relacionado con el nivel de
entrelazado de la memoria. El trmino ingls para bloque suele ser line.
15 En los procesadores actuales la memoria cache est dividida en dos o tres niveles, por lo que el
nmero de copias de un determinado bloque de datos puede ser mayor.

3.1

PRESENTACIN DEL PROBLEMA Y REVISIN DE CONCEPTOS

85

coherente. Ello implica que todas las escrituras se efectan tambin en


memoria principal, lo que requiere ms tiempo.
Write-back (WB): slo se modifica la copia de la memoria cache, y se
mantiene la memoria principal con el valor antiguo. El objetivo es
reducir el nmero de accesos a memoria, y con ello el trfico 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 informacin
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 gestin 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 operacin de escritura. En esa operacin se
modificarn varios bloques de datos; qu habra 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
operacin y mantener la coherencia (flush de la cache). En todo caso, las
operaciones de E/S son de muy baja frecuencia en comparacin con las
operaciones del procesador sobre la cache.

3.1.2 Coherencia de los datos en los multiprocesadores de memoria compartida (SMP)


El problema de la coherencia es mucho ms peliagudo en los
multiprocesadores de memoria compartida. La comunicacin 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 sern 2 (las correspondientes

86

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

a la jerarqua de memoria) sino P+1, siendo P el nmero de procesadores.


Adems, y aqu est el problema principal, cualquier procesador puede
efectuar una modificacin en dichos bloques, en su cache local. Tal como
hemos visto, las polticas de escritura WT o WB permiten gestionar la
coherencia de los datos en el caso de un procesador, pero cmo hacer lo
mismo con el resto de las memorias cache del multiprocesador? cmo saber
si otro procesador ha modificado el bloque en su cache, y por tanto ya no es
vlida nuestra copia? Por definicin, el problema de coherencia slo 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 comunicacin entre procesos), dado que no se harn copias, pero
dicha decisin puede tener un efecto severo en el rendimiento del sistema, ya
que todos los accesos de los procesadores a dichas variables tendrn que
hacerse en memoria principal: crecer mucho el trfico en el bus y, en
consecuencia, debido a los conflictos en el acceso al bus, subirn 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 instruccin por ciclo, y el 2% de las mismas
son operaciones de memoria, LD/ST, sobre variables compartidas. Los datos son de 8
bytes. Cuntos 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 slo los datos compartidos.
Por tanto, 8 procesadores generarn un trfico de 1024 MB/s para acceder a las
variables compartidas, el mximo 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 trfico en el bus.

3.1.3 Falsa comparticin


El problema de coherencia aparece con las variables compartidas. Las
variables privadas slo estarn, como mucho, en una cache, y no significan
ningn problema nuevo.

3.1

PRESENTACIN DEL PROBLEMA Y REVISIN 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 ms de un
procesador, aunque todas las variables del bloque sean privadas. Por
ejemplo:
Bloque de datos de 4 palabras
X

Variables del
procesador Pi

Variables del
procesador Pj

Aunque las variables son privadas estn 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 comparticin (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 Definicin 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 definicin no muy formal, se introducen dos conceptos: la propia
coherencia qu valor se obtiene, y la consistencia cundo se ver en
la variable el valor que ha escrito otro procesador. sta segunda cuestin
la analizaremos un poco ms 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 ningn 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 condicin que tambin hay que cumplir en el caso de los sistemas
con un solo procesador.

88

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

2. Considerando todos los procesadores, la operacin Pi_rd_A debe


devolver siempre lo escrito por la ltima operacin Pj_wr_A, si es que
ha pasado "suficiente tiempo" desde que se realiz. sta es, ms o
menos, la definicin 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 funcin de la arquitectura de la
mquina. Como hemos visto en el captulo 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 comunicacin ms
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 captulo vamos a analizar los protocolos de coherencia ms
habituales en los sistemas SMP (y en el captulo 7 analizaremos los
directorios de coherencia).

3.2

PROTOCOLOS DE COHERENCIA SNOOPY

Como hemos comentado en el captulo anterior, la memoria de los


sistemas SMP est "concentrada" en un solo sitio, y los procesadores utilizan
normalmente un bus 16 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 (fisgn). Puesto que la memoria y
los procesadores se conectan mediante un bus, una red centralizada, todas las
operaciones con la memoria principal son pblicas, es decir, que cualquier
procesador puede ver lo que otros estn haciendo (LD, ST) dado que tambin
l est conectado al bus. La funcin 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 informacin de control. En
16 Vamos a utilizar el modelo ms simple de bus, en el que slo se procesa una peticin de uso del bus y
no se admite otra hasta finalizar con la anterior. En general, los buses de los sistemas multiprocesador
son ms complejos.

3.2

89

PROTOCOLOS DE COHERENCIA SNOOPY

funcin de la informacin 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 travs del bus el
nuevo valor de la palabra modificada.
En la siguiente figura aparece un ejemplo de ambas alternativas.
P2

P1
MC1

A = 43

P2

P1

wr A,#3

wr A,#3

A=4

MC2

MC1

MP

A = 43?

Invalidacin

A = 43

A = 43

INV A

MC2

BC A,3

MP

A = 43?

Actualizacin

En el primer caso, el procesador P1 va a modificar la variable A en su


cache, de 4 a 3. Efecta la escritura y enva una seal de control especial al
bus, INV, para invalidar la copia de P2; como consecuencia de ello, slo
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 seal 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 funcin de la poltica de escritura que se utilice: en todas las escrituras si
se usa WT, y slo en algunas ocasiones si se utiliza WB.
Ya hemos comentado que la coherencia de los datos se mantiene por
bloque, y para ello se aaden 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 slo aparece una palabra, un bloque contiene siempre varias palabras.

90

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

para los bloques. Un autmata finito (el snoopy) se encargar en cada cache
de ir modificando los estados de los bloques en funcin 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


seales 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 slo se
usan algunos de ellos, como vamos a ver. Los estados se definen de acuerdo
a dos caractersticas: el nmero 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 mquina a mquina).
I Invlido (invalid)
Un bloque est en estado I si la informacin que contiene no es vlida;
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 tambin
el smbolo (-). 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 adems 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) mltiples 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).

3.2

91

PROTOCOLOS DE COHERENCIA SNOOPY

O Propietario (owner, shared dirty)


Existen (o pueden existir) mltiples 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
(atencin, esas copias no son coherentes con memoria principal).
Para definir los estados del bloque basta con usar tres bits. Por ejemplo:
vlido
(valid)

modificado
(dirty)

compartido
(shared)

Estado

I
E
S
M
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, slo se aade un bit ms al directorio.
Una mquina 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
definicin anterior, para lo que hay que tomar en consideracin 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 peticin 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 funcin del tipo de protocolo. Adems, si ha sido un fallo, hay
que pedir el bloque de datos correspondiente.

92

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

2 Acciones que se observan en el bus (seales de control enviadas por


los otros snoopy), como resultado de operaciones de otros
procesadores. El nmero, nombre y tipo de las seales depende de la
implementacin 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 peticin (BR en el bus
de control, y la direccin en el bus de direcciones). Todos los
snoopy locales tienen que considerar esta seal para adecuar el
estado del bloque (si tienen una copia del mismo).
INV: invalidate

[ en los protocolos de invalidacin ]

Se escribe una palabra en la cache y, por tanto, hay que eliminar


todas las copias de dicho bloque. Se enva al bus de control la
seal INV y al bus de direcciones la direccin del bloque a
anular. Todos los snoopy tienen que responder adecuadamente a
la seal, anulando, en su caso, la copia del bloque.
BC: broadcast

[ en los protocolos de actualizacin ]

Se escribe una palabra en la cache, por lo que hay que actualizar


todas las copias de dicha variable. Se activa la seal BC en el bus
de control, y se pone la direccin de la variable en el de
direcciones y nuevo valor en el de datos). Todos los snoopy
tienen que responder adecuadamente a la seal, actualizando, en
su caso, la variable correspondiente.
En algunos casos hay que activar ms de una seal 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 seales que
acabamos de definir son simplemente una opcin, y las implementaciones
de las mismas pueden ser diferentes; por ejemplo, en lugar de activar dos
seales de control a la vez, puede utilizarse una tercera seal que indique
ambas acciones: RdEx (o BRinv), "lectura exclusiva".
3. Otras seales de control
Las acciones anteriores tienen como consecuencia que se modifique el
estado de los bloques en la cache. Adems de ellas, tambin

3.2

PROTOCOLOS DE COHERENCIA SNOOPY

93

aparecern 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 poltica 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 seal de control no es
estrictamente necesaria, ya que puede utilizarse para ello la seal
INV (o BC), porque al escribir la memoria principal tambin hay
que invalidar (o actualizar) el resto de copias (usaremos el * para
indicar una transferencia de slo una palabra).
Un protocolo de coherencia snoopy es un algoritmo distribuido en el que
colaboran P autmatas finitos distribuidos en P procesadores. Utilizando los
estados que acabamos de describir, permite trabajar con mltiples 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 travs del bus, y, en funcin de
ellas, decidir el estado de los bloques de datos y generar las seales de
control adecuadas.
Pueden definirse muchos algoritmos de coherencia diferentes, utilizando
algunos o todos los estados anteriores, y diferencindose entre ellos por la
poltica de escritura: write-through, write-back, o mezclas de ambos (en
funcin del nmero de copias, del nmero de escrituras, de la jerarqua 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 invalidacin


Cuando se realiza una escritura en un bloque, y la coherencia se mantiene
mediante un protocolo de invalidacin, se eliminan todas las copias de ese
bloque que haya en el sistema. Los protocolos ms simples son de slo dos
estados (I-E o I-S) y utilizan como poltica de escritura WT, pero no son
demasiado eficientes. Por ello, vamos a analizar protocolos de al menos tres
estados y que utilizan WB como poltica de escritura siempre que es posible.

94

3.2.2.1

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Un protocolo de tres estados, MSI

(Silicon Graphics)

Uno de los protocolos ms 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 poltica de escritura es, por
tanto, write-back (se utiliza el estado M), aunque no se admite ms de una
copia de un bloque que no est actualizado en la memoria principal.
Para definir un protocolo de coherencia (una autmata de estados finitos),
hay que definir las transiciones entre los estados de los bloques de datos y
las seales de control que se generan en dichas transiciones, en funcin, 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 seales de control que se
detecten en el bus (BR e INV). Todo ello se refleja en la siguiente tabla:
Estado siguiente / Seales de control

acierto

fallo

Estado
presente

PR

I, -

S
M

PW
BR

BR

BR,INV

INV

S
S

Trfico (datos)
MP MC:
MC MP:

//
//

BR
BW

INV

I
BW

BW

I S, M
M S, I (+reemplazo)

Los protocolos tambin se pueden representar mediante un grafo, tal como


aparece en la siguiente figura.
PR - PW

M
BR (BW)
PR - BR

INV (BW)

PW (INV)

S
INV

PW (BR,INV)
PR (BR)

I, -

3.2

PROTOCOLOS DE COHERENCIA SNOOPY

95

Las lneas 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 seales de control que aparecen
en el bus, debidas a lecturas y escrituras de otros procesadores. Entre
parntesis y en cursiva aparecen las seales de control que se envan al bus.
Cuando el procesador lee una variable que est en la cache (PR), el estado
del bloque no se modifica ni se generan seales 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 seal 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 seal 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 seales 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 seal BR. El bloque solicitado podra
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 haba antes, coherentes, se le aade una ms.
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 opcin 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 seal INV en el bus, la decisin es muy
simple: si el bloque de datos est en la cache, hay que eliminarlo (I). Dado su
efecto, la seal de invalidacin INV tiene preferencia frente a la seal BR
cuando ambas se activan a la vez. Como la poltica de escritura es write-

18 El estado S no implica que necesariamente tenga que haber ms copias en el sistema; es decir,
aunque es seguro que en algn momento s ha habido ms de una copia, pueden haber sido
reemplazadas todas ellas, quedando una sola copia, en estado S. Adems, en este protocolo la primera
copia tambin se carga en estado S, ya que no se utiliza el estado E (una sola copia).

96

Captulo 4: SINCRONIZACIN 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.
Trfico en el bus compartido
El trfico que se genera en el bus que conecta los procesadores y la
memoria en un multiprocesador es un aspecto crtico 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 clculo bajar. Por ello, un protocolo de coherencia adecuado debe
intentar reducir dicho trfico, para que se pueda conectar el mayor nmero
de procesadores al bus.
En la parte inferior de la tabla de transiciones de estados del protocolo se
muestra el trfico 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
seal 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 seal BW (cuando
un bloque en estado M pasa a estado S, se anula o se reemplaza).
De dnde 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
trfico en el bus, pero s el tiempo de acceso, porque traerlo desde otra cache
va a ser ms rpido. 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), adems 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

3.2

97

PROTOCOLOS DE COHERENCIA SNOOPY

La segunda opcin es bastante ms adecuada ya que reduce a la mitad el


trfico en el bus y la latencia de la operacin.
En el caso de que quien solicita el bloque vaya a efectuar una escritura, se
podra eliminar la escritura del bloque en MP, y efectuar nicamente la
transferencia MC1 (M) MC2 (M), ya que, despus 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 aade un cuarto estado, E, con
el objetivo de minimizar el nmero de invalidaciones.
El estado E nos asegura que en todo el sistema slo hay una copia del
bloque (recordemos que el estado S no distingue entre el nmero de copias
que hay del bloque) y que, adems, es coherente con la informacin que hay
en la memoria principal. Para distinguir entre los estados E y S se introduce
una nueva seal 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 haba al menos una copia previamente en el sistema).
La tabla de transiciones correspondiente al autmata de coherencia es la
siguiente:

Estado siguiente / Seales de control

acierto

fallo

Estado
presente

I, -

PR
nsh: E
sh:

PW
BR

Trfico (datos)
MP MC:
MC MP:

BR

INV

BR,INV

INV

BR
BW

//
//

BW

I E, S, M
M S, I (+reemplazo)

BW

98

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

PR - PW

M
INV (BW)

BR (BW)
PW

PW (INV)
PR - BR

PR

BR
sh

nsh

INV
PW (BR,INV)

INV
PR (BR)

I, -

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 lnea de control sh, de manera que cuando aparece
en el bus la seal BR todos los snoopy mirarn en los directorios de sus
caches para comprobar si tienen una copia de ese bloque o no, y, en caso
afirmativo, activarn la seal 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 pasarn a estar en
estado S, y a partir de ah la evolucin 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 sern
copias nicas) de los compartidos, y, as, reducir el trfico en el bus. Si se
hace una escritura sobre un bloque que est en estado E, el bloque pasar al
estado M, sin generar trfico (si hubiera estado en estado S, tendramos que
haber activado la seal de invalidacin junto con la direccin del bloque). Es
decir, no se enva la seal INV cuando no hay copias del bloque que se va a
modificar. No hay que olvidar que la mayora de los bloques de datos sern
privados, y slo algunos de ellos sern compartidos.

3.2

99

PROTOCOLOS DE COHERENCIA SNOOPY

3.2.2.3

El protocolo Berkeley, MOSI

Como ltimo ejemplo de protocolos de invalidacin, analicemos el


protocolo Berkeley; utiliza los estados I, M, S y O, y la poltica de escritura
es write-back "siempre". Recuerda que el estado O (propietario, owner) se
utiliza para poder tener mltiples copias de un bloque no coherente con
memoria principal (en los dos protocolos anteriores slo se permita una
copia no coherente, en estado M). Como no se diferencian los estados E y S,
no se usa la seal sh. La tabla de transiciones y el grafo del protocolo son
las siguientes:
Estado siguiente / Seales de control

acierto

fallo

Estado
presente

PR

I, -

PW
BR

BR

BR,INV

INV

INV

Trfico (datos)
MP / MC MC:
MC MP:

BR
BW

INV

BW

BW

I S, M
M, O I (+reemplazo)

//
//

PW (INV)
PR - BR

PR - PW

O
BR

INV (BW)

INV (BW)

PW (INV)
PR - BR

S
INV
PW (BR,INV)

PR (BR)

Con el nuevo estado O es posible tener mltiples copias de un mismo


bloque no coherentes con MP pero coherentes entre s (esto permite utilizar
una poltica de escritura write-back en todos los casos). En el protocolo
anterior haba que efectuar la transicin M S cuando era requerida una

100

Captulo 4: SINCRONIZACIN 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 estn en estado S en todas las caches del multiprocesador,
entonces sern coherentes con MP; pero si una de las copias est en estado
O, entonces las copias sern coherentes entre s, pero no lo sern 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 estarn en estado S, retomarn la definicin inicial de estado
S (coherentes con MP).
En lo que al trfico se refiere, no hay ningn 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 peticin, pasando los
datos de la memoria cache al bus para que se puedan leer y cancelando la
lectura que se haba solicitado a la MP.

3.2.2.4

Resumen de los protocolos de invalidacin

En los apartados anteriores hemos analizado algunos protocolos de


coherencia de invalidacin, 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 poltica de escritura, etc.
No hemos descrito todos los que existen, ni mucho menos. No hay
problema, por ejemplo, para definir un protocolo de invalidacin que utilice
los cinco estados. Otro ejemplo bastante conocido es el protocolo writeonce, en el que se utilizan ambas polticas de escritura (WT y WB) segn los
casos: WT cuando se escribe por primera vez en el bloque y WB para las
sucesivas escrituras. Tambin se pueden definir protocolos que tienen en
cuenta la jerarqua de memoria a la hora de definir los estados (por ejemplo,
el procesador Alpha). Todos ellos se dejan como ejercicio.

3.2

PROTOCOLOS DE COHERENCIA SNOOPY

101

3.2.3 Protocolos de actualizacin


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 actualizacin. En los protocolos
de invalidacin, 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
actualizarn el bloque con el nuevo valor de la variable.
Para poder actualizar una variable en el resto de las caches, se utiliza una
seal de control denominada BC (broadcast), y junto con ella se pondr en el
bus la direccin de la variable y el nuevo dato. Los controladores de las
caches procesarn esas seales y, cuando corresponda, realizarn 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 obtendramos mejores
resultados con este tipo de protocolos, en realidad va a depender de la
aplicacin que se est ejecutando. En los casos en los que un bloque de datos
se reutilice sistemticamente en los diferentes procesadores, la actualizacin
ser ms eficiente, pues se mantiene la copia del bloque en las caches; en
cambio, si el bloque no se va a reutilizar, quizs se est actualizando ese
bloque sin sacar ningn rendimiento a esas actualizaciones (hubiera sido
mejor invalidar el bloque la primera vez). No hay que olvidar que para
transmitir los datos de la actualizacin hay que utilizar el bus y, adems,
mientras se est actualizando una cache el procesador local no puede
utilizarla y deber esperar.
Los protocolos de actualizacin 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 actualizacin bastante conocidos.

3.2.3.1

El protocolo Firefly, MSE(I)

(Archibald & Baer 85) (DEC)

Este protocolo de actualizacin utiliza los estados E, M y S. La poltica de


escritura es write-back con los bloques privados y write-through con los

102

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

compartidos. Esto es, cuando slo 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 tambin la memoria
principal. Como el protocolo distingue entre los estados E y S, el bus de
control cuenta con la seal sh (shared): sh = 1 hay copias de dicho
bloque en alguna otra cache; sh = 0 no hay copias.
Las transiciones entre estados y las seales de control de este protocolo se
muestran en la siguiente tabla y en el grafo correspondiente:
Estado siguiente / Seales de control

acierto

fallo

Estado
presente

PR
nsh: E

sh:

PW
nsh:

BR

sh:

BR

BR
M
S BR,BC

M
nsh:
sh:

BC

E
S

BC

S
BW

Trfico (datos)
MP / MC MC:
MC MP:
MC MC*MP*:

BW

(I) E, S, M
M S (+reemplazo)
(I) S(wr); S E, S(wr)

//
//
//

BR
BW
BC

PR - PW

nsh

BR (BW)

PW (BR)

(-)

sh

(BC)

PW
PW (BC)
PR

sh

E
BR
nsh

PW (BC) nsh

PR
BR - BC

sh

PR (BR)

(-)

Cuando se va a cargar un bloque nuevo en una cache, el estado del bloque


va a depender de la seal 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

3.2

PROTOCOLOS DE COHERENCIA SNOOPY

103

ambos estados, que indican que slo hay una copia del bloque de datos, se
les aplica la poltica de escritura write-back. En cambio, si se detecta que hay
una o ms copias del bloque en el sistema (sh), el estado del nuevo bloque
ser S, y la posterior poltica 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 funcin de la seal 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 dems; aprovechamos as la escritura
para actualizar el estado (aunque slo quede una copia, el estado ser E y no
M, porque la poltica de escritura con las copias compartidas es siempre la
misma: hay que actualizar la memoria principal).
Desde el punto de vista del trfico, el caso ms interesante es la transicin
(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, adems de traer el bloque hay que
actualizar la memoria principal y todas las copias del mismo. Por tanto,
cuando se genera la seal BC tambin hay que actualizar (una palabra) la
memoria principal (es decir, cumple la misma funcin que la seal BW* que
definimos anteriormente). Por ello, para reducir el trfico de actualizacin,
en el caso escritura/fallo antes de generar la seal BC se espera a obtener la
respuesta de la seal sh; si no, podramos genera la seal BC desde el
comienzo de la operacin.

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 variacin


de este protocolo se utiliza en las mquinas Sun Sparc/Server). Al igual que
en el protocolo Berkeley, el estado O permite aplicar la poltica write-back
en todos los casos. Se mantiene la seal sh (shared), para distinguir entre
los estados E y S y as poder reducir el nmero de actualizaciones (BC).
En la siguiente tabla y su grafo correspondiente se presenta este protocolo.

104

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Estado siguiente / Seales de control

acierto

fallo

Estado
presente

PR
E
S

nsh:

sh:

PW
nsh:

BR

sh:

BR

BR
M
O BR,BC

M
nsh:
sh:

BC

M
O

M
nsh:
sh:

BC

M
O

BC

Trfico (datos)
MP / MC MC:
MC MC*:
[ MC MP:

(I) E, S, M, O
(I), S, O(wr) O
M, O (I) ]

BR
//
BC
//
reempl. //

(-)
nsh

PW (BR)

sh

(BC)
nsh

PW
PR - PW

M
BC

BC

(BC)
nsh

PW

PR

PW sh (BC)
PR - BR

BR

sh

PW

BR

nsh

PR
BR - BC

sh

PR (BR)

(-)

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 transmisin de una palabra de
una cache a otra; esto es, una operacin de broadcast para actualizar las
copias del bloque.

3.3

IMPLEMENTACIN DE LOS PROTOCOLOS SNOOPY

105

3.2.4 Resumen de los protocolos de tipo snoopy


En los prrafos anteriores hemos presentado los principales protocolos de
tipo snoopy, tanto los de invalidacin como los de actualizacin. A pesar de
que al principio surgieron muchos protocolos diferentes, hoy en da los
principales son los que hemos comentado (o variantes de los mismos).
Adems, debido a ciertos inconvenientes en la implementacin, los
protocolos de actualizacin casi no se utilizan. Por tanto, los ms utilizados
son los protocolos de invalidacin de 3 o 4 estados, en los que se utiliza la
poltica de escritura write-back.

3.3

IMPLEMENTACIN 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 autmatas que hemos
analizado (o variaciones de los mismos) son los que se utilizan en todos los
multiprocesadores. La lgica que tienen que ejecutar los controladores de
coherencia es bastante simple, tanto en el caso de invalidacin como en el de
actualizacin. Pero la implementacin distribuida de esa lgica 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 situacin, 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 autmata 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 ms importante: la necesidad de asegurar que no se
mezclarn, en el tiempo, operaciones de coherencia de dos (o ms)
procesadores sobre un mismo bloque, produciendo resultados incorrectos.
Para mostrar los problemas y las soluciones, analicemos cmo 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.

106

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

P
data

addr

contr

MC
tags +
state

Bus side
controller

Cache data RAM

proc.

snoopy

compar.

tag

compar.

state

Cmd

tags +
state

Processor
side
controller

to controller

Write-back buffer

to controller

Data buffer

Addr

Addr

Cmd

system bus

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 orgenes 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 seal 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 nmero
de interferencias posible: si el procesador est utilizando la cache, el snoopy
se retrasar (y, como consecuencia, todas las transferencias de los dems
procesadores); si es el snoopy el que est utilizando la cache, entonces ser

3.3

IMPLEMENTACIN 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 (bsquedas, 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 tambin
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 ms baja.

3.3.1.2

Bferes de escritura

Cuando la poltica 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 comn es hacer lo siguiente: en
lugar de efectuar las dos operaciones en este orden <actualizar (BW) / leer el
bloque nuevo (BR)>, se efectan en el orden contrario: primero traer el
bloque nuevo y, despus, 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 perdera esa informacin); esta
copia se hace en el bfer 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 bfer de escritura, normalmente aprovechando ciclos
libres del bus.
Esta mejora es comn tambin en los sistemas de un solo procesador, pero
en los multiprocesadores los bferes de escritura se deben de tratar con
cuidado. Cuando un snoopy tiene que efectuar una bsqueda para saber si un
determinado bloque est en la cache, adems de en el directorio de la cache
deber buscar tambin en el/los bfer/es de escritura. Por tanto, el hardware
de bsqueda, los comparadores, se debe duplicar: uno para el directorio de la
cache y otro para cada bfer de escritura (figura anterior).

108

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

3.3.1.3

Protocolo de peticin de bus

Cuando un procesador pone una peticin en el bus, por ejemplo BR, debe
esperar a la respuesta de los dems 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, cunto tiempo hay que
esperar hasta estar seguro de que todos han respondido? Las estrategias ms
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
decisin se toma en el tiempo mximo (ser el hardware del sistema el
que determine ese tiempo), pero, a cambio, es el mtodo ms simple
para implementar (Pentium quad/HP/SUN).
Esperar un tiempo variable (un handshake). Para reducir el tiempo de
espera, se detecta cundo responde el ltimo snoopy, y la decisin se
toma en ese momento. As, en la mayora de los casos la decisin se
toma antes del tiempo mximo, 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 optimizacin tpica
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 todava no haba terminado) o se bloquea la
respuesta de la memoria hasta que todos los snoopy respondan (SGI
challenge).
Aadir un bit ms 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 solucin 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 ms seales en el bus de control. Por un lado, la seal sh, que
ya hemos utilizado, para saber si existen o no copias de un bloque de datos.
Del mismo modo, es conveniente tener otra seal similar, dirty, para
indicar si el bloque est modificado en alguna cache. Por ltimo, es
interesante tener otra tercera seal, inh (inhibir), para poder abortar los
accesos a memoria principal.

3.3

IMPLEMENTACIN DE LOS PROTOCOLOS SNOOPY

3.3.1.4

109

Atomicidad: estado del controlador snoopy

Para terminar con el anlisis 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 operacin es atmica si se ejecuta toda la
operacin, desde el comienzo hasta terminar, sin ningn tipo de interferencia
de ningn otro procesador.
El procedimiento para mantener la coherencia no es atmico por
definicin, 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 atmicas; es decir,
no se procesa otra peticin hasta haber terminado con la anterior no se
segmenta 19. En los sistemas de un procesador, para trabajar con el bus se
utilizan protocolos de comunicacin similares a ste (por ejemplo, para una
escritura):
procesador
peticin-bus
...
direccin, control
...
datos

controlador del bus

concesin-bus

recibido

De esta manera, el controlador del bus establece orden y prioridades en el


uso del mismo. En los multiprocesadores el control del bus es ms
complicado, por un lado porque hay muchos procesadores conectados al bus,
y por otro porque los controladores de las caches son ms complicados, para
poder hacer las funciones del snoopy. Adems, aunque ayuda, el hecho de
que el bus sea atmico 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 cmo 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.

110

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

3.3.2 El protocolo Illinois y la atomicidad


3.3.2.1

Carreras: estados transitorios, seales BRQ y BGN

El protocolo Illinois es un protocolo de invalidacin de tipo MESI (lo que


vamos a presentar se podra aplicar a los dems protocolos). Este protocolo
utiliza la seal sh, para saber si los bloques estn compartidos o no.
Analicemos el siguiente caso. Dos procesadores comparten un bloque de
datos, cuyas copias estn en estado S. Los dos hacen una escritura a la vez en
dicho bloque. Cmo se resuelve el problema 20? Cmo 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 atmico)?
Por ejemplo, los procesadores P1 y P2 envan la seal 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 estara en los
estados M y S en dos caches simultneamente). Pero despus de hacer esto,
la seal enviada al bus, INV, no ser suficiente, ya que ahora debera enviar
tambin la seal BR. La consecuencia que podemos extraer est clara: el
controlador del snoopy no se puede quedar esperando, sin hacer nada ms, a
la respuesta a su peticin; tal vez tenga que cambiar la peticin realizada si
entre tanto otro procesador ha querido hacer una operacin sobre el mismo
bloque de datos.
A este problema se le denomina "carrera" (race), y para solucionarlo, se
suelen introducir ms estados en el protocolo de coherencia, denominados
estados "transitorios". Estos nuevos estados no estn 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 especfico, 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
todava no se ha terminado.
Tal y como hemos comentado, vamos a suponer que las operaciones en el
bus son atmicas, y para ello vamos a introducir dos seales de control en el
protocolo:

20 O, por ejemplo, el caso de dos escrituras simultneas 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 colocarn en estado M.

3.3

IMPLEMENTACIN DE LOS PROTOCOLOS SNOOPY

111

- peticin:

BRQ (bus request)

peticin de utilizacin del bus

- respuesta:

BGR (bus grant)

permiso para utilizar el bus

Para secuencializar operaciones que se quieren realizar simultneamente,


antes de utilizar el bus hay que efectuar una peticin 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 transicin 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 funcin de
la seal 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 podra darse, a la
vez, la misma transicin 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 seal de aceptacin BGR; por tanto, el bloque pasar a
estado M, y se generar la seal INV.
- Se detecta la seal 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 autmata pasar al

112

Captulo 4: SINCRONIZACIN 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).
(M, E M)

PW y acierto

En este caso no tendremos ningn problema; como nuestra copia es la


nica, se escribe y se modifica el estado, si es que estaba en E.
PR - PW

M
BR (BW)

BGR (BR,INV)

INV (BW)

BGR (INV)

IM
INV

SM

PW
PW (BRQ)
PR

BR

PR - BR

sh

nsh

BGR (BR)
INV

PW (BRQ)

ISE

INV

PR (BRQ)

I, -

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 tpicos. En el campo de las comunicaciones, el interbloqueo
est relacionado con la ocupacin 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 ms).
Mientras tanto, el controlador del procesador P2, que ha conseguido el bus,

113

ha ejecutado la operacin BR; por desgracia, el bloque que l quiere lo tiene


P1, y adems, en estado M. Consecuencia: el procesador P1 no le enviar el
bloque, porque est esperando la seal 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 autmatas de los snoopy no
pueden dejar de espiar el bus ni en los estados transitorios. Si estando en un
estado transitorio observa una situacin como la descrita en el ejemplo
anterior, deber dar la respuesta adecuada.
Al igual que sucede en otros contextos (por ejemplo, en la comunicacin
entre procesos), adems del interbloqueo existen otros problemas, entre ellos
los denominados livelock y starvation. El problema de livelock indica que se
ha llegado a una situacin en la que los procesos no estn 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 traern el bloque e invalidarn 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 operacin
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 peticin del acceso al bus, porque siempre se le
adelantan los dems. En el ejemplo de protocolo que acabamos de analizar
estos dos problemas estn resueltos.
En resumen, los protocolos de coherencia se deben de disear con mucho
cuidado, para evitar todo ese tipo de problemas y para que funcionen bien y
de manera eficiente en cualquier situacin. Al tratarse de protocolos
distribuidos entre P procesadores, cumplir con esas caractersticas puede
resultar complejo.

3.4

SNOOPY JERRQUICO

Para llevar a cabo la comunicacin entre los procesadores de un


multiprocesador hemos utilizado un bus. Como ya sabemos, el nmero de
procesadores que se pueden conectar en un bus es limitado, y ste es el
principal inconveniente de la utilizacin de un bus como red de
comunicacin. Pronto analizaremos ms formas (redes) de conectar los
procesadores, pero en este momento vamos a analizar otra red de
interconexin, que es una evolucin natural del bus: la jerarqua de buses.

114

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Un bus jerrquico es un rbol de buses, en el que en las hojas estn los


multiprocesadores (unidos mediante un bus) y en los dems "nodos" no hay
ms que buses y controladores. Estos ltimos se encargan de gestionar la
informacin por toda la red. Como vemos en la siguiente figura, se organiza
un tipo de cluster en el que los nodos son pequeos sistemas SMP.
SMP
P
snoopy local

C
B1
MP

MP

B2
hardware para la
coherencia global

Supongamos que tenemos una jerarqua 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 ms
apropiado es distribuir fsicamente la memoria, y de esta manera se llega a
un sistema NUMA (non-uniform memory access): el tiempo de acceso es
diferente en funcin de dnde est situada la posicin 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, cmo mantener la coherencia en todo el sistema?
Tal y como veremos ms adelante, la solucin que se utiliza en los
sistemas que no utilizan una red de interconexin centralizada (un bus o
similar) son los directorios de coherencia. Cuando se utiliza una jerarqua de
buses, se utilizan unos controladores snoopy especiales que hacen la funcin
de directorios, espiando y conectando dos niveles de bus, y decidiendo si hay
que pasar la informacin 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 trados a las
caches locales. Por supuesto, la informacin que se queda dentro de un nodo
concreto (MP y cache) no afecta a los dems nodos, y ser el snoopy local el
que se encargue de mantener la coherencia.

3.4

SNOOPY JERRQUICO

115

Vamos a dividir el monitor de coherencia o directorio en dos partes:


KL: controlador de coherencia que guarda informacin referente a los
bloques locales que se encuentran copiados en memorias cache
remotas (solamente los estados, no los datos, ya que el nmero de
bloques que pueden estar "fuera" puede ser muy grande).
KR: controlador de coherencia que guarda informacin de los bloques
remotos que se encuentran en las caches locales (una "cache" que
guarda datos y estados, aunque con los estados sera suficiente; si
estn los datos, se reduce el trfico en el bus, pero se aumenta la
necesidad de memoria).
Cmo 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 seal
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
operacin comn 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 situacin 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
peticin se pondr en el bus B2. Cuando el controlador KR del
nodo que tiene la copia del bloque detecte la peticin 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

116

Captulo 4: SINCRONIZACIN 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 peticin 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).
MC

MC
IS

MC

MS

ES

rd, fallo

MP
BR @

MC

3
B1

B1

5
KL

KR

KL

MS

KR

ES
MS

ES
B2

MP

2. La referencia es del espacio de direccionamiento remoto.


KR no responde. Por tanto, no est en alguna otra cache del nodo. La
peticin se pasa al bus B2. El controlador KL correspondiente a esa
direccin detectar la peticin 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 informacin
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, adems 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 operacin de escritura ST A en un bloque que est en estado S.

3.4

117

SNOOPY JERRQUICO

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 operacin se
desarrollar de la siguiente manera:
1. Se pone el bloque en estado M y se genera una seal de invalidacin
(INV) en el bus B1.
2. El controlador KR del nodo N1 ve que es una referencia remota; por
tanto, pasa la seal INV al bus B2.
3. El controlador KR de N2 invalida su copia y pasa la seal INV al bus
B1 (con lo que se invalidarn 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.

N1
wr A

N2
MC

MC
SM

SI

MP
INV A

INV A

B1

N3
MC

SI

SI

MP
B1

MC

INV A

MC

MC

MP
A

B1

3
KL

KL

KR

SI

SM
2 INV A

KR

B2

INV A

KL

KR

SM
3
INV A

En general, cuando la memoria es compartida pero est fsicamente


distribuida no es sencillo mantener la coherencia de los datos. Los
controladores de coherencia son dispositivos complejos y de gran tamao, 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 jerarquas 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 solucin al
problema de la coherencia: el directorio, tal como veremos en el captulo 7.

Sincronizacin de Procesos
en los Computadores SMP

4.1

INTRODUCCIN

En una mquina MIMD, la ejecucin de los programas se divide en P


procesos o hilos, que se ejecutan en paralelo en los procesadores del sistema.
En general, la ejecucin de esos procesos no es completamente
independiente, sino que se comunican entre ellos, bien sea para pasarse datos
o para sincronizar su ejecucin. En este captulo vamos a analizar las
necesidades de sincronizacin entre procesos que se ejecutan en paralelo en
una mquina SMP de P procesadores.

120

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Para presentar el problema de la sincronizacin entre procesos podemos


utilizar un ejemplo muy sencillo. Supongamos que se va a ejecutar este
cdigo, en paralelo, en dos procesadores, P1 y P2 (inicialmente, A = 0) 21:
P1

P2

...
ST A,F1
...
...

...
...
...
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 efecte P1 terminarn apareciendo en P2; sin embargo, no
sabemos cundo 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
debera indicar de alguna manera que P2 debe leer A despus de que la haya
modificado P1, y no antes. Algo similar debera 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 lgico. En general, en estos casos se
utiliza la sincronizacin por eventos, para avisar a un proceso (consumidor)
que se ha generado un dato en otro proceso (productor).
La necesidad de sincronizacin 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

P2

...
LD
R1,CONT
ADDI R1,R1,#1
ST
CONT,R1
...

...
LD
R1,CONT
ADDI R1,R1,#1
ST
CONT,R1
...

Qu valor tendr la variable CONT tras ejecutar el cdigo anterior en


ambos procesadores? Aunque no existan problemas de coherencia, el
21 Para simplificar el cdigo, en los ejemplos de este captulo utilizaremos el modo de direccionamiento
absoluto. Como es habitual, el contenido del registro R0 es siempre 0.

4.1

121

INTRODUCCIN

resultado no est claro. Por ejemplo, ambos procesos ejecutan a la vez el


cdigo 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. Dnde est el problema? La
variable compartida CONT se ha accedido de manera no adecuada,
habindose intercalado en el tiempo las operaciones de P1 y P2 sobre dicha
variable. Cul sera la solucin? Tambin 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 ejecucin en
paralelo sea el esperado. De hecho, aunque el cdigo se ejecute en dos
procesadores, ese trozo de cdigo se debera ejecutar en serie. Dicho de otra
manera, la ejecucin de ese cdigo debe ser atmica.
En el ejemplo anterior, los dos procesos slo comparten una variable,
sobre la que se efecta una operacin muy simple (+1), pero en general se
ejecutan ms operaciones sobre las variables compartidas. Por eso, algunos
trozos de cdigo de los procesos paralelos tienen que definirse como
secciones crticas, y hay que controlar de manera adecuada que slo un
proceso ejecute simultneamente dicho cdigo, para lo que suelen utilizarse
variables de tipo cerrojo, que funcionan como semforos a la entrada de las
secciones crticas, 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
sincronizacin entre procesos puede resolverse por software o por hardware.
Si se hace en hardware, suele ser ms rpida pero menos flexible; si se hace
por software (bibliotecas), se suelen obtener soluciones ms flexibles. Hoy
en da se utiliza una mezcla de ambos tipos; por una parte, se aaden
instrucciones especiales al lenguaje mquina, y, por otra, utilizando esas
instrucciones se escriben diferentes funciones de sincronizacin.
Las estrategias bsicas de sincronizacin son dos: exclusin mutua
(mediante funciones lock/unlock) y sincronizacin por eventos (punto
a punto, mediante indicadores o flags, o global, mediante barreras). En las
operaciones de sincronizacin, los procesos esperan hasta que ocurra una
determinada accin (que se abra el cerrojo, que se active un flag...). Como

122

Captulo 4: SINCRONIZACIN 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 accin;
mientras tanto, el procesador no realiza ninguna tarea til. En los casos de
bloqueo, en cambio, el sistema operativo efecta 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 sincronizacin). Ambos mecanismos, espera activa y bloqueo, son
adecuados, y escogeremos uno u otro en funcin de las circunstancias
concretas de la aplicacin y de la mquina (tiempo a esperar, latencia del
cambio de contexto, existencia de otros hilos o threads para ejecutar...);
tambin 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 quin es la responsabilidad de escribir las rutinas de sincronizacin?
En general, el programador utilizar las rutinas de sincronizacin de la
librera 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 situacin, situacin que puede variar mucho de
programa a programa o dentro del mismo. Por ejemplo, hay que dar solucin
eficiente al caso de un nico procesador que desea entrar en una seccin
crtica o al caso de P peticiones simultneas de entrada. Una funcin de
sincronizacin que d buen resultado en el primer caso, tal vez no lo d en el
segundo.
Como hemos comentado, la sincronizacin no es algo intrnseco 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 trfico de control
especfico. Por ello, un mecanismo de sincronizacin adecuado debe cumplir
algunas condiciones, entre las que cabe destacar:
Baja latencia: se debe gastar el menor tiempo posible en efectuar la
operacin de sincronizacin, sea cual fuera la situacin del programa;
por ejemplo, no se debera perder tiempo en el cerrojo de una seccin
crtica cuando sta est libre y no hay competencia en la entrada.
Trfico limitado: el trfico que se genera en el acceso y uso de las
variables de sincronizacin debe ser el mnimo posible, para evitar
saturar la red de comunicacin.

4.2

EXCLUSIN MUTUA (mutual exclusion)

123

Buena escalabilidad: tanto la latencia como el trfico no deben crecer


(al menos no demasiado) con el nmero de procesadores del sistema.
Poco coste de memoria: no se debe utilizar mucha memoria para la
sincronizacin.
Igualdad de oportunidades: todos los procesos deben tener las
mismas oportunidades de resolver sus peticiones de sincronizacin;
deben evitarse situaciones en las que, por ejemplo, un determinado
proceso no consiga nunca entrar en una seccin crtica, mientras que
otros lo hacen una y otra vez (starvation).
Definido el problema, analicemos las principales estrategias de
sincronizacin.

4.2

EXCLUSIN MUTUA (mutual exclusion)

Se utiliza la exclusin mutua para controlar la ejecucin de un trozo de


cdigo que, aunque est replicado en P procesos, no puede ser ejecutado por
ms de un proceso simultneamente. Ese trozo de cdigo forma una seccin
crtica y nunca debe haber ms de un proceso ejecutndolo. Para proteger el
acceso a una seccin crtica se utilizan dos funciones especficas, lock y
unlock, que manejan una variable de tipo cerrojo, y que hacen las veces de
un semforo.
El cerrojo puede tomar dos valores: 0 y 1, Si el cerrojo vale 0 (abierto), no
hay problema alguno para ejecutar la seccin crtica; en cambio, si el cerrojo
vale 1 (cerrado) hay que esperar, ya que otro proceso est ejecutando en ese
momento la seccin crtica.
Dos funciones se ejecutan con la variable cerrojo. El proceso que entra en
la seccin crtica cierra el cerrojo (lock), y, al finalizar la ejecucin de la
seccin crtica, lo abre (unlock). Antes de entrar en la seccin crtica, 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
seccin crtica para que los procesos la ejecuten siempre de uno a uno:
lock()
[ seccin crtica ]
unlock()

La exclusin mutua puede lograrse tambin por medio del hardware. Por
ejemplo, se pueden dedicar algunas lneas del bus de control para utilizarse

124

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

como variables cerrojo hardware (or-wired, como la seal sh). Sin embargo
no se suele utilizar esa solucin, sino que las funciones lock y unlock se
implementan en software. Veamos cmo podran escribirse esas dos
funciones (CER es una variable tipo cerrojo):
funcin lock(CER)
lock: LD R1,CER
BNZ R1,lock

; saltar si no es 0

funcin unlock(CER)
unlock: ST CER,R0
RET

ADDI R2,R0,#1 ; R2 := 1
ST CER,R2
; cerrar cerrojo
RET

Antes de entrar en la seccin crtica 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 seccin crtica,
cerrando previamente el cerrojo. Finalmente, al terminar de ejecutar el
cdigo de la seccin crtica se ejecuta la funcin unlock para abrir el
cerrojo (CER = 0).
Sin embargo, aunque las rutinas anteriores podran ser adecuadas en el
caso de un sistema con un solo procesador (en funcin de cmo se reparta el
tiempo de ejecucin), no funcionan bien en un sistema multiprocesador.
Cul es el problema? El mismo que tiene la seccin crtica, la falta de
atomicidad. El uso (lectura / escritura) de la variable CER no es atmico,
por lo que no se puede impedir que dos procesos pasen a la seccin crtica.
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 crticas necesitamos disponer de
instrucciones atmicas de tipo RMW (read-modify-write) que permitan
efectuar una operacin de lectura y escritura sobre una variable (el cerrojo)
en modo atmico. Mientras se est ejecutando una operacin 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 estn pensados para ser utilizados en entornos
multiprocesador de memoria compartida. Veamos las principales
instrucciones atmicas RMW.

4.2

125

EXCLUSIN MUTUA (mutual exclusion)

4.2.1 Instrucciones Test&Set y Swap


Como primera opcin para gestionar cerrojos, vamos a analizar dos
instrucciones similares. En ambos casos se ejecuta una operacin de tipo
RMW: se lee una variable en memoria, se modifica, y se vuelve a escribir en
memoria, sin ninguna interferencia (operacin atmica).

4.2.1.1

Instruccin Test&Set

Es una instruccin atmica RMW, la ms antigua, que realiza la siguiente


operacin:

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 instruccin T&S, las dos funciones de un cerrojo
(cerrar/abrir) pueden hacerse as:
lock: T&S R1,CER
BNZ R1,lock

unlock: ST CER,R0
RET

RET

La instruccin T&S asegura que slo un proceso leer CER = 0, ya que


junto a ello, de manera atmica, 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 seccin crtica hay que abrir el cerrojo, y para ello es
suficiente con escribir un 0 en la variable CER, con una operacin "estndar"
de escritura, ya que en la seccin crtica slo hay un proceso.

4.2.1.2

Instruccin Swap

La instruccin 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 atmico entre el contenido de un registro y una posicin
de memoria:
SWAP R1,CER

R1 MEM[CER];

126

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Para efectuar un cerrojo, basta con cargar previamente un 1 en el registro.


stas seran las correspondientes rutinas lock y unlock:
lock: ADDI R1,R0,#1
l1:
SWAP R1,CER
BNZ R1,l1

; R1 := 1

unlock: ST CER,R0
RET

RET

4.2.1.3

Anlisis del trfico

Tal como hemos visto, las instrucciones de tipo RMW permiten controlar
el acceso a una seccin crtica, pero tenemos que analizar si se hace de
manera eficiente o no. Como hemos comentado, las funciones de
sincronizacin deben ser de latencia baja y generar poco trfico, todo ello, a
ser posible, independiente del nmero 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 (invalidacin) para mantener la coherencia de los datos.
Cada vez que un proceso ejecuta la instruccin 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 seccin crtica;
sin embargo, si en ese momento hay muchos procesos efectuando la misma
operacin, el prximo intento en todos los procesadores ser un fallo en
cache (se ha anulado la variable cerrojo). Todos los procesos, ms o menos a
la vez, pedirn el bloque de datos correspondiente, por lo que se generar un
trfico de datos muy alto en el bus, ms alto cuanto mayor sea el nmero
de procesos esperando entrar en la seccin crtica. Como consecuencia, las
latencias (el tiempo de respuesta) de dichas operaciones crecern mucho.
En la siguiente figura puede observarse una simulacin de dicha situacin.
Al principio, el procesador P0 est en la seccin crtica y otros cuatro
procesadores esperan para entrar. P0 abandona la seccin crtica y escribe
CER = 0 (unlock), por lo que invalida todas las copias de dicha variable.
Los otros cuatro procesos, a la vez, pedirn (BRQ) el bloque que contiene
CER, para poder ejecutar T&S. Al ser una instruccin atmica, el controlador
del bus sirve las peticiones de manera "ordenada" (FIFO en la figura). Para
indicar la atomicidad, hemos puesto la ejecucin de la instruccin T&S entre
corchetes.

4.2

127

EXCLUSIN MUTUA (mutual exclusion)

Simulacin de la entrada a una seccin crtica


Sincronizacin: Test&Set (TS)
BRQ = peticin de bloque / x = invalidado /

transmisin de un bloque de datos

P0 C=0 INV

P1 ? x [TS BRQ

TS INV]

P2 ? x [TS BRQ. . . . . . . . . . . .

SECCIN CRTICA

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. . . . . x BRQ. . . . . . . . . .

TS INV] [TS. .

repetir y repetir

Trfico de datos (bloques)


Para que entre un procesador en la sec. cr.
Al salir de la seccin crtica

P + (P 1) k veces
1

La conclusin de la simulacin es clara: mientras la seccin crtica se


mantiene ocupada, los procesos que intentan entrar estn 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 trfico en el bus. No
hay que olvidar que ese trfico no corresponde al algoritmo que se ejecuta
sino al hecho de ejecutarlo en paralelo.
As pues, aunque la funcin lock anterior formalmente funciona bien, y
en situaciones de poca competencia no da problemas, pero cuando la
competencia por entrar en la seccin crtica es alta el proceso se degrada
mucho, es decir, no es escalable. Pueden plantearse, sin embargo, algunas
mejoras en el diseo de las rutinas de acceso a la seccin crtica, intentando
reducir el trfico y la latencia.

4.2.1.4

Procedimiento Test&Set with backoff

La fuente del trfico que se genera en el bus est en la instruccin T&S


(que efecta siempre una escritura). Por tanto, deberamos limitar el nmero
de veces que se ejecuta dicha instruccin.
Una primera alternativa sera esperar un cierto tiempo entre dos
operaciones de T&S:
T&S t. de espera T&S t. de espera ...

128

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

es decir, si no es posible entrar en la seccin crtica en un momento


determinado, no intentarlo una y otra vez, generando trfico y sin poder
entrar, sino esperar un cierto tiempo para aumentar la probabilidad de
encontrar libre la seccin crtica.
El tiempo de espera entre intentos no debera ser muy alto, para evitar
tener el proceso parado cuando ya se ha liberado la seccin crtica, ni muy
bajo, para no intentar entrar en vano. Es decir, hay que tomar un
compromiso entre reducir el trfico (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, nmero de intentos realizados para entrar en la seccin
crtica: 0, 1, 2...), lo que genera la siguiente secuencia de tiempos de espera:
t0 = k

t1 = k c

t 2 = 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

4.2.1.5

esp:

CALL ESPERA(t1)
[t1 := ...]
JMP lock

unlock:

ST CER,R0
RET

; t1 = tiempo de espera
; calcular nuevo valor para t1

Procedimiento Test-and-Test&Set

Veamos una segunda alternativa para reducir el trfico. 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 seccin crtica si no se va a modificar su
contenido?

4.2

129

EXCLUSIN MUTUA (mutual exclusion)

La idea es dividir la operacin de sincronizacin en dos fases. En la


primera parte, simplemente se analiza el contenido del cerrojo, y para ello
basta con utilizar una instruccin de lectura estndar. Repetiremos esa
operacin todas las veces que sea necesario hasta encontrar el cerrojo
abierto. En ese momento, se ejecuta una operacin de T&S, intentando cerrar
de manera atmica el cerrojo. Slo 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 slo escribir una vez.
Los procesos que estn intentando acceder a la seccin crtica no generan
trfico en el bus mientras la seccin crtica est ocupada. Las invalidaciones
(y, por tanto, la necesidad de tener que traer bloques de datos) slo ocurrirn
en dos ocasiones: cuando uno de los procesos cierra el cerrojo (escribe un 1)
y cuando el proceso que termina la ejecucin en la seccin crtica lo abre
(escribe un 0).
A esta estrategia de sincronizacin se le conoce como Test-and-Test&Set,
y las rutinas de control de la variable cerrojo son las siguientes:

lock:

LD R1,CER
BNZ R1,lock

; fase de test

T&S R1,CER
BNZ R1,lock

; fase de test-and-set

RET
unlock: ST CER,R0
RET

En comparacin con el uso simple de la instruccin T&S, cuando se utiliza


el procedimiento Test-and-Test&Set el trfico generado se reduce
notablemente. La siguiente figura muestra una simulacin de dicha estrategia
de sincronizacin. Al inicio, todos los procesos estn 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 vern que el cerrojo est abierto (CER = 0), y ejecutarn T&S
(atmico), pero slo uno lograr pasar a la seccin crtica; el resto volver a
la fase de test, ya que encontrarn el cerrojo cerrado.

130

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Simulacin de la entrada a una seccin crtica


Sincronizacin: Test-and-Test&Set
BRQ = peticin de bloque / x = invalidado /

transmisin de un bloque de datos

P0 C=0 INV

P1 LD x BRQ

LD [TS . . . . . . . . . TS INV]

P2 LD x BRQ. . . . .

LD [TS . . . . . . . . . x BRQ

P3 LD x BRQ. . . . . . . . . . . .

TS INV] LD. . . . x BRQ. . . . . . . . . .

LD [TS . . . . x BRQ. . . . . . . . . . . .

P4 LD x BRQ. . . . . . . . . . . . . .

SECCIN CRTICA

LD . . . . . . . . .

TS INV] LD . . . . x BRQ. .

LD . . . . .

LD [TS x BRQ. . . . . . . . . . . . . . . . . . . . . . . . . TS INV] LD . . . . . . . . . . . . . .

Trfico de datos (bloques)


Para que entre un procesador en la sec. cr. P + (P 1) + (P 2)
Al salir de la seccin crtica
1
En total 3P 2
Para que entren P

(3P 2) =
p =1

P(3P 1)
2

3P 2
2

El trfico que se genera es de orden P2, siendo P el nmero de procesos


que est intentando acceder simultneamente a la seccin crtica; no es por
tanto muy escalable, por lo que el bus se saturar con facilidad al crecer P.
Adems, el trfico 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 slo se ejecuta una vez la instruccin T&S).

4.2.1.6

Resumen de caractersticas

Como hemos visto, las funciones ms simples de tipo T&S para controlar
el acceso a una seccin crtica generan mucho trfico de sincronizacin
cuando existe alta contencin en el acceso a la seccin crtica mientras sta
est ocupada. Pero por otra parte, resultan muy adecuadas en casos de baja
contencin: son muy simples, tienen una latencia muy pequea (pocas
instrucciones) y no generan trfico.
Se utiliza muy poca memoria, ya que basta con una variable,
independientemente del nmero de procesos. Desde el punto de vista del

4.2

EXCLUSIN MUTUA (mutual exclusion)

131

equilibrio en el reparto de recursos, no se establece ningn tipo de poltica de


asignacin 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 contencin en la entrada a la seccin crtica va a ser
muy baja (o cuando el nmero de procesadores del sistema es muy pequeo).
La estrategia T&S-BO tiene un comportamiento similar, aunque genera
menos trfico y es, por consiguiente, ms escalable.
Test-and-T&S es el mecanismo ms adecuado de los tres. En situacin de
alta competencia, mantiene el trfico 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 cuestin relacionada con la funcin lock. Es necesario
llevar a la cache la variable cerrojo de la funcin 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 sera ms
cmodo 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 seran a la
memoria principal, a travs del bus, con lo que la latencia en casos de baja
contencin sera mucho ms alta.

4.2.2 Instrucciones Load Locked / Store Conditional y


Compare&Swap
Acabamos de comentar el problema que presenta una funcin de lock
basada en la estrategia Test-and-T&S: cuando ejecutan la instruccin T&S
todos los procesos efectan una escritura en la variable cerrojo, se invalidan
entre todos ellos y se genera un hot spot de trfico, ya que todos los procesos
solicitan en un intervalo muy corto de tiempo el bloque de datos
correspondiente. Que el trfico sea muy alto no se puede aceptar cuando el
nmero de procesadores crece, por lo que hay que intentar algo ms para
reducir dicho trfico.

132

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

4.2.2.1

Instrucciones LL y SC

Cada vez es ms habitual en los procesadores que las operaciones


atmicas necesarias para la sincronizacin se repartan en dos instrucciones,
que, usadas de manera adecuada, permiten realizar una operacin atmica
RMW sobre una variable. Adems de las dos instrucciones, se utiliza un flag
hardware para saber que la operacin se ha ejecutado de manera atmica.
Las dos instrucciones especficas para sincronizacin son: LL Load Locked
(o linked) y SC Store Conditional.
La instruccin LL efecta una lectura en memoria, pero tiene un efecto
lateral: en un registro (latch) especial que slo se usa para sincronizacin (le
llamaremos LSin) se guarda la direccin accedida y un flag, para indicar
que se ha ledo dicha posicin en un modo especial.
LL R1,CER

R1 := MEM[CER];
LSin[dir] := CER; LSin[flag] := 1;

La instruccin SC efecta una escritura condicional en memoria, para lo


que primero analiza el latch LSin. Si contiene la direccin que se quiere
escribir y el flag est en 1, entonces efecta la escritura en memoria y enva
una seal especial de invalidacin de dicho flag a todos los procesadores,
que al recibirla pondrn a 0 el flag si est asociado a la direccin indicada.
En cambio, si el flag est desactivado, entonces la escritura no se ejecuta. En
ambos casos, se devuelve un cdigo 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 cmo se utilizan ambas instrucciones para gestionar la entrada a


una seccin crtica. La operacin se realiza en dos o tres pasos:
1 Se lee la variable de sincronizacin (cerrojo) mediante la instruccin
LL, con lo que se guarda la direccin accedida y se activa el flag de
sincronizacin en el latch de sincronizacin.
2 Si es necesario, se efecta clculo o se procesan variables.

4.2

133

EXCLUSIN MUTUA (mutual exclusion)

3 Se intenta escribir en la variable cerrojo (normalmente los resultados


del segundo paso), mediante la instruccin SC. Si el flag de
sincronizacin asociado a la direccin del cerrojo est activado, se
realiza la escritura y se anulan, adems de la variable cerrojo, todos
los flags del sistema correspondientes a la direccin de la variable que
se escribe: la operacin total [LL SC] se ha realizado atmicamente,
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 (razn por la
cual se ha anulado el flag que se activ con la instruccin LL). No se
ha podido realizar atmicamente el par [LL SC] y por ello hay que
repetir todo el proceso. Como SC no ha escrito, no se produce ninguna
invalidacin, ni se genera, por tanto, trfico alguno.
En resumen, si SC termina bien, entonces el trozo de cdigo [LL SC]
se ha ejecutado atmicamente (lo cual no quiere decir que las instrucciones
entre LL y SC formen una seccin crtica).
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
BNZ R1,l1

; examinar cerrojo

...

SC CER,R2
BZ R2,lock
RET

; intentar cerrar cerrojo


; SC no ha escrito, repetir

unlock: ST CER,R0
RET

Como ocurre en el caso Test-and-T&S, no se genera trfico mientras


estamos en el bucle de espera (LL), ya que slo se hace una lectura. Las
mejoras vienen en la segunda parte. La instruccin T&S siempre escribe en
memoria, independientemente del valor del cerrojo; en cambio, la
instruccin SC slo escribe cuando el cerrojo est abierto (es decir, cuando
nadie ha escrito en dicha variable desde que se ejecut LL). As pues, slo se
genera trfico en el bus en dos casos: al entrar en la seccin crtica (cuando
SC cierra el cerrojo), y al salir de la misma (al abrir el cerrojo).

134

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Simulacin de la entrada a una seccin crtica


Sincronizacin: LL / SC
BRQ = peticin de bloque / x = invalidado /

transmisin de un bloque de datos

P0 C=0 INV

P1 LL x BRQ

LL(1) [SC . . . . . .SC INV]

P2 LL x BRQ. . . . .

LL(1) [SC . . . . . . . (0)x BRQ

P3 LL x BRQ. . . . . . . . . . . .

SECCIN CRTICA

SC] LL. . . . . . . . . . . .

LL(1) [SC . . . (0)x BRQ. . . . .

P4 LL x BRQ. . . . . . . . . . . . . .

SC] LL . . . . . . .

LL(1) [SC (0)x BRQ. . . . . . . .

SC] LL . . .

Trfico de datos (bloques)


Para que entre un procesador en la sec. cr. P + (P 1)
Al salir de la seccin crtica
0
En total 2P 1
P

Para que entren P

( 2P 1) =P

p =1

En la figura anterior se muestra una simulacin de esta estrategia. Como


puede observarse, slo una instruccin SC logra escribir en memoria, la
primera, ya que las dems encuentran desactivado el flag que activaron con
la instruccin LL. El trfico de datos, por tanto, es menor. En todo caso, el
trfico todava es alto, y adems no se aplica ningn tipo de gestin de las
peticiones de entrada. Hay, por tanto, oportunidades para la mejora.

4.2.2.2

Instruccin Compare&Swap

Antes de estudiar posibles mejoras del procedimiento anterior, veamos


otra alternativa en la lnea de la anterior. Se trata de la instruccin
Compare&Swap, que realiza la siguiente operacin en modo atmico:
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 slo cuando se cumple la comparacin. Lo que en la pareja
LL/SC se consigue con la ayuda del hardware (flag), en este caso se logra
mediante el flag estndar resultado de una comparacin.

4.2

135

EXCLUSIN MUTUA (mutual exclusion)

El cdigo para controlar un cerrojo usando la instruccin C&S es el


siguiente:
lock:

ADDI R2,R0,#1

; R2 := 1

l1:

C&S R0,R2,CER
BNZ R2,l1

; no escribe siempre
; R2 = 1 no se ha escrito

RET
unlock: ST CER,R0
RET

La instruccin C&S es ms "compleja" que las anteriores, puesto que


utiliza dos registros y una posicin de memoria (es decir, una operacin de
memoria con tres operandos). En muchas arquitecturas RISC no se utiliza
ese formato, por lo que suele ser ms 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


funcin. Por una parte, cada procesador tiene que disponer de un registro
especial y un flag asociado al mismo, y, por otra, se necesita la colaboracin
del controlador del bus. Cuando se ejecuta LL se guarda la direccin
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 direccin en algn otro procesador, en cuyo caso tiene
que borrar el flag. Tambin hay que borrar el flag cuando se reemplaza el
bloque que contiene la variable de sincronizacin o en los cambios de
contexto.
Al ir a ejecutar la instruccin SC se mira el flag. Si est activado, no hay
problemas: se escribe en memoria y se enva una seal de control para borrar
todos los flags asociados a dicha direccin en el resto de procesadores. Pero
si est desactivado, no se efecta la escritura y se devuelve el
correspondiente cdigo de error. Hay que implementar el protocolo con
cuidado para evitar problemas de deadlock, livelock, y similares. Por
ejemplo, tendramos 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).

136

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Para evitar estos problemas es conveniente no aceptar reemplazos del


bloque que contiene la variable de sincronizacin. Cmo? 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 estarn 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 sincronizacin intercalndose 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 atmico: las instrucciones Fetch&Op. Se trata de un
grupo de instrucciones RMW de uso ms general que las anteriores: antes de
volver a escribir la variable en memoria se efecta algn tipo de operacin
(op) con la misma. Segn la operacin 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 instruccin Fetch&Incr


incrementar el valor de la variable CONT de manera atmica:

podemos

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 posicin de memoria CONT. Es decir, si CONT
vala 6, tras ejecutar la instruccin tendremos que R1 = 6 y CONT = 7.

4.2

137

EXCLUSIN MUTUA (mutual exclusion)

Si el cdigo que hay que ejecutar en exclusin mutua es ms largo (algo


ms complejo que una simple operacin de incremento), entonces habr que
generar una seccin crtica; aunque utilizando este tipo de instrucciones
tambin se pueden implementar dichas funciones, lo ms habitual es utilizar
otro tipo de instrucciones atmicas para hacerlo.

4.2.4 Alternativas para reducir el trfico


Tal como hemos comentado, con las instrucciones LL/SC conseguimos
reducir el trfico, pero an caben ciertas optimizaciones. Analicemos las ms
importantes.

4.2.4.1

Tickets

Un mecanismo basado en tickets puede ser til para reducir el trfico en la


entrada a una seccin crtica. La idea es sencilla. Un proceso que quiere
entrar en la seccin crtica tiene que coger primero un ticket, que le indica el
nmero de turno de entrada que le corresponde. A continuacin, se quedar
esperando a que llegue su turno. En ese momento, solamente l tendr
permiso para entrar en la seccin crtica: es su turno. Al abandonar la
seccin crtica incrementar la variable que indica el turno, para dejar paso al
siguiente proceso.
Con el mtodo de los tickets no se produce contencin en la entrada de la
seccin crtica, ya que todas las entradas se han ordenado, y por tanto se
reduce algo el trfico. 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
exclusin mutua, para lo que podemos utilizar, si disponemos de ello, una
instruccin 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

LL
ADDI
SC
BZ

R1,TICKET
R2,R1,#1
TICKET,R2
R2,tick

; conseguir ticket
; incrementar nmero de ticket para el siguiente
; pero de manera atmica
; repetir la operacin hasta conseguir atomicidad

o bien:
tick:

138

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Utilizaremos una u otra solucin en funcin del tipo de instrucciones que


pueda usar el procesador. Finalmente, las rutinas de lock y unlock
quedarn as 22:
lock:

F&I

R1,TICKET

; obtener ticket

esp:

LD
SUB
BNZ

R2,TURNO
R3,R1,R2
R3,esp

; esperar turno

RET

unlock: LD
R1,TURNO
ADDI R1,R1,#1
ST
TURNO,R1
RET

; actualizar turno
; para dar paso al siguiente

En la siguiente figura (un poco ms adelante) se presenta una simulacin


de esta estrategia. Los procesos ya han conseguido su ticket y estn
esperando su turno. Slo se genera trfico una vez, cuando se incrementa el
turno al salir de la seccin crtica, aunque tendramos que contar tambin el
trfico generado al obtener el ticket, ya que hay que traer a la cache el bloque
que contiene dicha variable. Sumado todo el trfico, el nivel del mismo es
similar al que habamos conseguido antes, aunque ahora est ms distribuido
en el tiempo (la obtencin de los tickets no tiene por qu ser simultnea), lo
que tambin es importante.
A pesar de todo, todava hay momentos en los que se genera bastante
trfico. Al actualizar la variable TURNO (al salir de la seccin crtica) se
producirn fallos en la cache en todos los procesadores que estn esperando
entrar, que pedirn, ms o menos a la vez, una copia de dicho bloque: se
genera un pulso de trfico.
Desde el punto de vista de la latencia, cuando no se produce contencin
(simultaneidad) en la entrada, esta tcnica es de latencia ms alta, puesto que
primero hay que conseguir el ticket.
El reparto de peticiones es justo: se aplica una poltica tipo FIFO. Si se
quiere, en este caso se pueden aplicar tcnicas de espera tipo backoff, con un
tiempo de espera proporcional a la diferencia entre el ticket obtenido y el
turno actual.
22 Si el nmero de procesos es P, conviene incrementar las variables TICKET y TURNO mdulo P, para
evitar desbordamientos.

4.2

139

EXCLUSIN MUTUA (mutual exclusion)

4.2.4.2

Vectores de cerrojos

Como hemos comentado, el mtodo anterior genera momentos de trfico


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 direccin de un elemento de un vector de
cerrojos, un cerrojo particular donde esperar para entrar en la seccin
crtica. As, primero se reparten posiciones del vector de cerrojos valores
de la variable INDICE, tal como hemos hecho con los tickets en el
mtodo anterior; luego, cada proceso espera a que se abra su cerrojo
particular: VECT_CER(INDICE).
vector de cerrojos: VECT_CER

...

proceso en la seccin crtica

...

INDICE: siguiente posicin de espera

Las rutinas de lock y unlock seran las siguientes:


lock:

F&I R1,INDICE

esp:

LD R2,VECT_CER(R1)
BNZ R2,esp
ST MI_INDICE,R1
RET

unlock: ADDI R2,R0,#1


LD
ST

R1,MI_INDICE
VECT_CER(R1),R2

ADDI R1,R1,#1
ST VECT_CER(R1),R0
RET

; obtener posicin del vector de cerrojos


; ojo! funcin mdulo
; esperar turno

; guardar ndice para la salida de la S.C.

; R2 := 1
; recuperar ndice del vector de cerrojos
; cerrar cerrojo propio (1)
; ojo! funcin mdulo
; abrir siguiente cerrojo (0)

En la figura siguiente se muestra una simulacin del trfico generado. El


trfico es ahora constante, independiente del nmero de procesos, porque al
salir de la seccin crtica slo se actualiza (y se anula) el cerrojo de un
proceso. El resto de procesos no se entera, y contina a la espera de su turno.
Estamos suponiendo que no existe un problema de falsa comparticin en la
cache, es decir, que los elementos del vector de cerrojos estn en bloques
diferentes de memoria.

140

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

Simulacin de la entrada a una seccin crtica


Sincronizacin: Tickets / Vectores de cerrojos
BRQ = peticin de bloque / x = invalidado /

transmisin de un bloque de datos

Tickets

Vectores de cerrojos

P0 TURNO++ INV

P1 LD x BRQ

P2 LD x BRQ. . . . .

VC(i+1)= 0 INV

SEC. CRIT.

LD

LD . . . . . . . . . . . . .

P3 LD x BRQ. . . . . . . . . . . .

P4 LD x BRQ. . . . . . . . . . . . . .

LD

x BRQ

LD

SEC. CRIT.

LD . . . . . . . . .

LD . . . . . . . .

LD . . . . . . . . .

LD . . .

LD . . . . . . . . .

Trfico de datos (bloques)


Para conseguir el ticket / turno
Para que entre un procesador en la sec. cr.
Al salir de la seccin crtica
En total
Para que entren P

TICK.
1
P
0
P+1
P+3) / 2

V.C.
1
1
1
3
3P

El trfico de datos se reduce considerablemente, pero en cambio se


necesita ms memoria para implementar la sincronizacin (un vector de P
elementos).
Como hemos visto, existen diferentes alternativas para gestionar secciones
crticas (para generar funciones lock); por tanto el programador tendr que
analizar las caractersticas de su aplicacin y de la mquina paralela para
optar por la ms adecuada.
Como ejemplo final, y a modo de resumen, el trfico que se generar en el
bus, si tenemos P = 7 procesadores (en una mquina de 8) esperando a entrar
en una seccin crtica, ser el siguiente en funcin de la estrategia empleada:
T-T&S: P(3P1) / 2 70 bloques

LL/SC:

P2 49 bloques

Tick.: P(P+3) / 2 35 bloques

V.C.:

3P 21 bloques

4.3

4.3

SINCRONIZACIN "PUNTO A PUNTO" MEDIANTE EVENTOS

141

SINCRONIZACIN "PUNTO A PUNTO"


MEDIANTE EVENTOS

Decimos que la sincronizacin es "punto a punto" si slo toman parte en


la misma dos procesadores (o grupos): el primero avisa al segundo de que se
ha ejecutado determinada operacin. La sincronizacin se suele ejecutar
mediante un bucle de espera activa sobre una variable comn 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 sincronizacin puede ser as:
P1 (productor)

P2 (consumidor)

X = F1(Z);
aviso = 1;

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 sincronizacin) puede extenderse y ejecutarse


en hardware (y as se ha intentado en algunas mquinas de tipo experimental
y en situaciones de paralelismo de grano muy fino), aadiendo a cada
posicin 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
sincronizacin productor/consumidor se efectuara de la siguiente manera: el
productor escribe en la posicin 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 posicin de memoria si el bit de control est a 1, y en ese
caso lo pone a 0. No es una solucin que se haya aplicado comercialmente,
ya que es cara (1 bit por cada posicin de memoria), requiere instrucciones
especiales de memoria y presenta problemas en casos como, por ejemplo, un
productor y muchos consumidores.
La sincronizacin por eventos se efecta mediante una escritura y un
bucle de espera. En algunos contextos, esas dos operaciones se indican
mediante dos funciones especficas. Por ejemplo:

142

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

flag = 1
while (flag == 0) {}

post(flag)
wait(flag)

[ signal(flag) ]

Esas funciones pueden generalizarse utilizando vectores de flags (vectores


de eventos):

4.4

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

SINCRONIZACIN MEDIANTE BARRERAS

En la ejecucin 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 ejecucin del programa. Para ese tipo de
sincronizacin se utilizan barreras (barrier).
Para construir una barrera de sincronizacin 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 exclusin 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 cdigo siguiente representa una barrera de sincronizacin 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). Adems de ello, se utiliza la variable local mi_cont, que
indica cuntos procesos han llegado a la barrera.
struct tipo_barrera
{
int cer;
int cont;
int estado;
};
struct tipo_barrera B;

variable para el cerrojo


nm. proc. que han llegado a la barrera
estado de la barrera

declaracin de la barrera

4.4

143

SINCRONIZACIN MEDIANTE BARRERAS

BARRERA (B,P)

P = nmero de procesos

{
LOCK(B.cer);
if (B.cont == 0) B.estado = 0;
B.cont++;
mi_cont = B.cont;
UNLOCK(B.cer);

entro en la seccin crtica


soy el primero, cierro la barrera

if (mi_cont == P)
{
B.cont = 0;
B.estado = 1;
}
else while (B.estado == 0) {};

soy el ltimo

cuntos hemos llegado a la barrera


salgo de la seccin crtica

inicializo el contador
abro la barrera
espero hasta que la barrera se abra

Los procesos que ejecutan la barrera incrementan el valor de B.cont,


dentro de una seccin crtica. 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 seccin crtica, 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 cdigo, no se puede
utilizar, sin ms, el contador B.cont, ya que en ese momento puede haber
otro proceso en la seccin crtica incrementando dicho contador. Si se quiere
utilizar la variable B.cont, se debe mantener la seccin crtica hasta
despus de la comparacin de la instruccin if, y luego terminar la seccin
crtica (unlock) por las dos ramas del if (then y else).

4.4.2 Barreras reutilizables


Algn problema con la barrera anterior? S, si se utiliza de manera
repetida (por ejemplo, dentro de un bucle): clculo / barrera / clculo /
barrera..., lo cual es muy normal, ya que la barrera ser normalmente una
funcin de biblioteca que llamarn los procesos una y otra vez.
Supongamos que se est ejecutando una barrera de sincronizacin. El
ltimo proceso entra en la barrera y la abre, para que todos los procesos

144

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

salgan y continen ejecutando el programa. Si en el cdigo vuelve a aparecer


una llamada a la barrera, es posible que un proceso entre en esa segunda
llamada a la barrera cuando tal vez algn proceso no haya abandonado
todava la anterior, porque, por ejemplo, no se ha enterado todava de que la
barrera se ha abierto (no estaba en ejecucin).
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 podrn salir, y los que entren en la
segunda nunca llegarn a abrirla, porque no estn todos. Claramente, hemos
llegado a una situacin de deadlock.
Cmo evitar ese problema? Por ejemplo,
a. Utilizando un contador que cuente el nmero de procesos que
abandonan la barrera (de manera similar a como se hace al entrar).
Mientras no la abandonan todos, ningn proceso puede volver a
entrar.
Por una parte, la latencia de la barrera de sincronizacin 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). Cuntos valores
diferentes habra que utilizar? Es suficiente con dos, 0 y 1, puesto que
no es posible tener ms de dos instanciaciones simultneas 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 opcin, la barrera quedara as:

4.4

SINCRONIZACIN 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
{
inicializo el contador
B.cont = 0;
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 sincronizacin 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
trfico, debe escalar bien con el nmero de procesos, etc.
En lo que al trfico que se genera en una barrera de P procesos se refiere,
podemos hacer la siguiente estimacin. Supongamos que las variables de la
barrera (cer, cont y estado) se encuentran en bloques diferentes (para
evitar la falsa comparticin). En general, el proceso Pi tiene que conseguir
cuatro bloques de datos: el de la variable cer, para entrar en la seccin
crtica; 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 trfico generado ser del orden de 4P bloques (para ser ms
precisos, 4P 2, ya que el primer y el ltimo proceso necesitan un bloque
menos cada uno).
Analizado en el tiempo, el trfico se va a repartir, en general, de la
siguiente manera: 2 - 3 - 3... - 3 - P1; es decir, el trfico que se genera al
entrar en la barrera suele estar repartido en el tiempo (suponiendo que no hay

146

Captulo 4: SINCRONIZACIN DE PROCESOS EN LOS COMPUTADORES SMP

contencin en la entrada a la barrera; si no, la funcin lock generar ms


trfico, tal como hemos visto en los apartados anteriores), pero las ltimas
P1 peticiones se generan a la vez, ya que todos los procesos (salvo el
ltimo) estn esperando a salir de la barrera; en ese momento, por tanto, la
latencia de servicio de los bloques ser ms alta.
Como en los casos anteriores, tambin aqu son posibles algunas
optimizaciones. El objetivo es reducir el nmero 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 comunicacin 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 tambin en hardware, si se dispone de
un bus especfico de control. La implementacin es similar al caso de la
lnea de control sh (AND wired).

4.5

RESUMEN

Es habitual tener que sincronizar la ejecucin 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 crticas, para lo que se utilizan unas
instrucciones especficas del lenguaje mquina del procesador que se pueden
ejecutar de manera atmica. De esta manera, se puede leer, modificar y
escribir una variable sin que interfiera ningn otro proceso (atmicamente).
En otros casos, la sincronizacin hay que implementarla mediante eventos,
punto a punto, o hay que sincronizar un conjunto de procesos, mediante
barreras.
Las diferentes instrucciones atmicas que se utilizan para operaciones de
sincronizacin son similares, y en un procesador concreto slo 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 ms
adecuadas que otras, en funcin del trfico que generan (los bloques de datos
que se invalidan en las escrituras). Por ejemplo, una funcin lock
implementada simplemente mediante la instruccin T&S es adecuada si no

4.5

RESUMEN

147

hay contencin (competencia) para entrar en la seccin crtica, pero no es


nada eficiente si se espera una elevada contencin.
En los sistemas SMP el trfico es un problema importante, y, por tanto, se
han desarrollado diferentes estrategias para reducir el trfico que generan las
funciones de sincronizacin: backoff, test-and-test&set, tickets, vectores de
cerrojos... Por tanto, es responsabilidad del programador seleccionar entre
las diferentes alternativas la ms adecuada para su aplicacin, bien sea
utilizando funciones de una biblioteca del sistema, o bien sea escribiendo
funciones especficas.

Consistencia de la Memoria en
los Computadores Paralelos

5.1

INTRODUCCIN

5.1.1 Sistemas de un solo procesador


En qu orden se ejecutan las instrucciones en un procesador? La
pregunta tiene ms inters del que parece, y tal vez sin pensar mucho
podramos responder: en el orden en que estn en el programa. Sin embargo,
sabemos que eso no es verdad. Aunque el modelo de ejecucin 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 ejecucin 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, bferes de instrucciones en los
superescalares, etc.). Por parte del software, sabemos que el compilador

150

Captulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

puede desordenar las instrucciones para obtener ejecuciones ms eficientes


(list scheduling, trace scheduling, software pipelining...).
Lo ms preocupante de esas reordenaciones est en las instrucciones de
memoria. La memoria del computador debera mantener en todo momento el
"estado actual" de la aplicacin; es decir, debera reflejar en todo momento
hasta qu punto se ha llegado en la ejecucin 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
ejecucin; se utilizan bferes 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 ejecucin de un programa sigue su propio camino con el
objeto de lograr la mayor eficiencia posible. En todo caso, la ejecucin 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 estn 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
ejecucin 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, considerndolo en su totalidad? es el
resultado correcto en todos los casos?
Si la comunicacin 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?

5.1

151

INTRODUCCIN

Al problema que hace referencia al orden de ejecucin 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 captulo 3
tambin hace referencia a estas cuestiones, pero de manera ms limitada.
Recordemos que un protocolo de coherencia asegura que:
los cambios que se efectan en una variable en una determinada cache
aparecern en algn momento en todas las caches.
los cambios que se efectan 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 vern los cambios producidos en variables
diferentes.

5.1.3 Semntica de los programas y orden de


ejecucin de las instrucciones
Antes de nada, es necesario estar seguros de la semntica 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
ejecucin de las instrucciones es especialmente importante para entender el
comportamiento de un programa paralelo. Por ejemplo, cul ser el
resultado en P2 al ejecutar este programa paralelo (inicialmente, A = B = 0)?
Tiene un significado claro el programa?
P1
A = 1;
B = 2;

P2
(wr1)
(wr2)

print B;
print A;

(rd1)
(rd2)

Tenemos cuatro combinaciones de resultados posibles: BA = 00, 01, 21 y


20. Segn 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:

152

Captulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

tiempo

P1

P2

A = 1;

...

...

print B;

B = 2;

...

...

print A;

En cambio, la cuarta combinacin, BA = 20, parece imposible. Si B = 2,


entonces A debera ser siempre 1. Sin embargo, esa combinacin es posible
si el control de P2 decide desordenar la ejecucin de sus instrucciones (el
modelo de ejecucin 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 captulo anterior, la semntica de los
programas paralelos se asegura normalmente mediante operaciones de
sincronizacin; de esa manera, en el siguiente ejemplo deberamos obtener
siempre A = 1 en P2.
P1

P2

A = 1;
LISTO = 1;

(wr1)
(wr2)

while (LISTO == 0) {};


print A;

(rd1)
(rd2)

A pesar de ello, podemos seguir teniendo problemas con el orden de las


instrucciones. Las dependencias de datos del programa deberan 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 >> rd2 23),
entonces se respetar tambin la dependencia wr1 rd2, ya que tenemos
que: wr1 >> wr2 rd1 >> rd2 wr1 rd2. Pero si no (si el compilador o el hardware desordenan el cdigo), podra ser que obtuviramos A = 0.
23 Utilizamos el smbolo >> para indicar orden entre operaciones: A >> B indica que A debe ejecutarse
antes que B. El smbolo indica una dependencia de datos: A B indica que el dato producido por
A se utiliza en B.

5.1

153

INTRODUCCIN

El problema de la ordenacin de las instrucciones se observa tambin en


este otro ejemplo (inicialmente, F1 = F2 = 0):
P1

P2

F1 = 1;
if (F2 == 0) then
< cdigo >

F2 = 1;
if (F1 == 0) then
< cdigo >

...

...

Las dependencias entre la instrucciones son las siguientes:


wr1 (F1)

wr2 (F2)

rd1 (F2)

rd1 (F1)

De acuerdo a la lgica secuencial, no es posible que ambos procesadores


ejecuten el cdigo de la rama then, ya que hay que respetar
obligatoriamente la ordenacin wr1 >> rd1 y wr2 >> rd2; pero si se
desordena el cdigo 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 cdigo.
Los cambios de orden que hemos citado son muy habituales en los
sistemas de un solo procesador; ms an, son imprescindibles para lograr un
rendimiento adecuado del sistema.

5.1.4 Atomicidad de las instrucciones


Las operaciones de memoria son atmicas si mientras se efectan no se
realiza ninguna otra operacin en memoria. Adems, la finalizacin de la
operacin debe entenderse en su sentido ms amplio, incluyendo los efectos
de la operacin 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
A = 1;
LISTO = 1;

P2
(wr1)
(wr2)

while (LISTO == 0) {};


print A;

(rd1)
(rd2)

154

Captulo 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 llegarn a P2, pero nada nos dice sobre el orden en que
llegarn, ya que se trata de dos variables diferentes 24 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 atmicas, y se continua con
la ejecucin del programa (wr2) antes de que finalice por completo (las
consecuencias) la instruccin anterior. En definitiva: necesitamos saber
cundo finaliza una instruccin de memoria antes de poder empezar con otra.
El problema es incluso ms general. En este ejemplo tambin aparece la
necesidad de atomicidad (A = B = 0):
P1

P2

A = 1;

while (A == 0) {};
B = 1;

P3

while (B == 0) {};
C = A;

Cuando P1 escribe en A, el nuevo valor aparecer en algn momento en


P2 y P3. Al llegar a P2 se ejecutar B = 1, cuyo nuevo valor tambin llegar
a P3. Cul 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 / seales de control enviados de un procesador a otro pueden llegar al destino en
desorden, en funcin de la red y de los protocolos de comunicacin. Eso es muy claro en los sistemas
de memoria distribuida, pero tambin puede darse en los sistemas SMP (con bus) en funcin del tipo
de bus y del protocolo de comunicacin.

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 cdigo 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 prximos 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: slo 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 ms
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 direccin 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).

156

Captulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

tiempo

P1

P2

a
b

c
d

En conjunto

SC si
a
b
c
d

SC no
a
c
b
d

a
d
c
b

c
d
b
a

2. Tambin hay que mantener el orden global, por lo que no puede


ejecutarse ninguna instruccin de memoria hasta que finalice la
anterior de cualquier procesador (y todas sus consecuencias). Para
poder asegurar esta condicin es necesario que las operaciones de
memoria sean atmicas (write atomicity = todas las escrituras, en
cualquier posicin, deben aparecer en el mismo orden en cualquier
procesador). No se admite, por tanto, esta situacin:
instruccin a
instruccin b

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 raz, 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 comportarn tal como se espera. En la figura
aparece un esquema lgico de la estructura que impone este modelo.
P

orden

atomicidad

MEM

5.2.1 Orden y atomicidad de las instrucciones de


memoria
Imponer orden local en un procesador es relativamente sencillo, al menos
si sabemos cundo ha terminado la operacin anterior. La atomicidad en
cambio es ms complicada, dado que cada procesador utiliza una cache
local. Para cumplir con el modelo SC se debe hacer lo siguiente:

5.2

157

CONSISTENCIA SECUENCIAL (SC, sequential consistency)

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. Adems 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 ms complejo. Cuando se ejecuta un ST, el procesador
no puede ejecutar otra instruccin de memoria hasta que la escritura y
sus consecuencias (invalidacin o actualizacin 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,
aadiendo respuestas a dichas acciones: seales o mensajes de
confirmacin tipo ACK (acknowledgement). Tras actualizar sus
copias, cada procesador enva un mensaje de ese tipo; al recibirse
todos los mensajes, la operacin se da por finalizada (problema:
cmo saber cuntas copias hay?)
1. INV

1. INV
2. ACK

2. ACK

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 asignarn
valores diferentes a reg1 y reg2, con lo que la escritura de A no
habr sido atmica.
Cuando la red de comunicacin del multiprocesador es un bus, el
propio protocolo de coherencia (el snoopy) y una gestin
adecuada del uso del bus permiten asegurar el orden de las
escrituras. Como vamos a ver en un prximo captulo, cuando se

158

Captulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

utilizan otro tipo de redes ms 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 operacin de lectura hay
que esperar a que finalice totalmente la ltima operacin 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 invalidacin, esta operacin no
es complicada: nuestra copia est invalidada, y por tanto tenemos
que pedir una nueva copia, que recibiremos cuando haya
terminado la operacin. En cambio, si las copias se actualizan, el
proceso de coherencia se vuelve ms complejo: tras enviar la
seal de actualizacin de la variable (1), hay que esperar a recibir
todas las confirmaciones (2), tras lo cual hay que enviar una nueva
seal indicando que ya se puede utilizar dicha variable (3), con lo
que finaliza la operacin de escritura. La complejidad del
protocolo de actualizacin es la razn por la que no se suelen
utilizar protocolos de este tipo.
1. BC
2. ACK
3. seguir

1. BC
2. ACK
3. seguir

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
adems no se pueden aplicar las optimizaciones ms habituales en el caso de
un solo procesador. Recuerda que mientras se efecta una operacin de
memoria en un procesador, nadie puede efectuar otra operacin 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 bferes 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 cdigo tpicas, si con ello se modifica
el orden de las operaciones de memoria. Y tampoco puede optimizarse el uso
de la memoria mediante la utilizacin de registros, (para ahorrarnos
operaciones LD/ST). Por ejemplo, esta optimizacin no funciona en un
multiprocesador:

5.3

159

MODELOS RELAJADOS (relaxed)

P1

P2

P1

P2

A = 1;
B = A;

A = 0;

r1 = 1;
A = r1;
B = r1;

A = 0;

(2 ST)

(2 ST / 1 LD)

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 (bsicamente, un adelanto del LD). No es posible
esa optimizacin 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 ms cuidados; pero si el bloque se ha anulado o actualizado en
el camino, habr que echar marcha atrs (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. Adems, 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
finalizacin 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 ms flexibles, en los que se
eliminan algunas de las restricciones anteriores.
Analicemos las necesidades de orden de manera ms fina. El orden de las
instrucciones de memoria se reduce a estos cuatro casos:
rd >> rd 25

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 direccin, estamos ante un caso de dependencia
de datos.

160

Captulo 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 cul no entre las instrucciones de memoria.
si se cumple o no la atomicidad de las escrituras en memoria (ST), lo
que permitira 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
mquina del procesador (normalmente a travs de funciones de biblioteca
del sistema). Estas instrucciones se denominan barreras de ordenacin
(fence), y se utilizan como puntos de control. Una instruccin 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 funcin que la anterior, pero con las lecturas (se
utilizan normalmente para evitar el adelantamiento de los LD).
Memory-fence: misma funcin, pero para ambas operaciones, rd y
wr.
Por definicin, 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 optimizacin es esconder la latencia de las
escrituras en memoria, y para ello se admite que se ejecute una instruccin
LD aunque no haya finalizado un ST anterior; es decir, se permite el

5.3

161

MODELOS RELAJADOS (relaxed)

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
atmicas.
El esquema de memoria correspondiente sera, esquemticamente, el
siguiente:

LD

ST

ST

ST

ST
bferes ST
(FIFO)

MEM

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 instruccin LD bloquea el
acceso a memoria de las siguientes instrucciones.
Por ejemplo, el significado de la ejecucin de este programa es el
siguiente en funcin 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 podra ocurrir que ni Y_copia ni X_copia tuvieran el nuevo valor.

Por definicin, 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

162

Captulo 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

se pueden desordenar

SWAP ... LD
no se pueden desordenar

El modelo TSO es adecuado para aprovechar la latencia de las escrituras,


y bajo el mismo funciona bien la habitual sincronizacin mediante un flag:
write A; write FLAG // read FLAG; read A. Ha sido utilizado en
numerosas mquinas: 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 tambin la restriccin de orden entre escrituras; es decir, no se
aseguran las relaciones de orden wr >> rd, wr. La implementacin 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 tpica sincronizacin productor/consumidor
mediante un flag. Por tanto, hay que analizar con cuidado si merece la pena
su aplicacin, evaluando, como siempre, lo que esperamos ganar y lo que
podramos 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 tambin el orden wr >> wr), para lo que se utilizan las
instrucciones especiales de ordenacin (fence).
Este modelo de consistencia se ha utilizado, por ejemplo, en el Sun Sparc
PSO.

5.3

163

MODELOS RELAJADOS (relaxed)

5.3.3 Modelos ms relajados


El problema del orden (consistencia) de las instrucciones de memoria slo
aparece en los accesos a variables compartidas, y no en el resto. Ms an; en
los siguientes dos casos, por ejemplo, no sera 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, bastara con asegurar el orden en relacin a las operaciones


de sincronizacin; asegurado eso, da igual en qu orden se ejecuten el resto
de las operaciones de memoria. (p.e., slo habr un procesador en la seccin
crtica).
Decimos que se utiliza programacin sincronizada si el uso de las
variables compartidas se protege mediante operaciones de sincronizacin,
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
mayora de los programas paralelos utilizan alguna funcin de
sincronizacin para ordenar el acceso a las variables compartidas:
funciones lock y unlock, flags, etc.
De ser as, para mantener la consistencia (en su sentido ms intuitivo),
bastara con asegurar el orden de las operaciones de memoria con relacin
a las de sincronizacin, junto con el de las de sincronizacin entre s.
Nos interesa, por tanto, distinguir los accesos estndar a memoria (rd,
wr) y los accesos a variables de sincronizacin (s). As, junto con las
relaciones de orden entre operaciones rd y wr, tendremos que mantener
tambin estas otras:
rd, wr >> s

s >> rd, wr

s >> s

Para aplicar un tratamiento especfico a las operaciones de sincronizacin,


habr que identificarlas convenientemente (por hardware y/o por software).

164

Captulo 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 sincronizacin, 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 operacin de
sincronizacin hay que esperar a la finalizacin (global) de todas las
operaciones de memoria anteriores; de igual manera, las operaciones de
memoria posteriores debern esperar a que finalice por completo la
operacin de sincronizacin.
Estas son pues las relaciones de orden a mantener:
rd / wr >> s; s >> rd / wr; s >> s.
rd ...wr ...

sinc

rd ...wr ...

sinc

rd ...wr ...

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
sincronizacin las instrucciones LD o ST correspondientes.

5.3.3.2

Release Consistency (RC)

Se trata del modelo de consistencia ms flexible. Como en el caso


anterior, son las operaciones de sincronizacin las que van a marcar los
puntos de ordenacin del programa; entre ellas, los LD y ST pueden
ejecutarse en cualquier orden. Pero adems, las operaciones de
sincronizacin se dividen en dos tipos: adquisicin (acquire, sa) y
liberacin (release, sr). Las operaciones sa son lecturas (u operaciones
RMW), y las operaciones sr escrituras (u operaciones RMW). Por ejemplo,
una funcin de lock es una operacin de sincronizacin de tipo acquire,
mientras que unlock es de tipo release.
Junto con el orden entre operaciones de sincronizacin (s >> s), se deben
mantener estos otros:
las operaciones de memoria posteriores a operaciones de
sincronizacin tipo adquisicin (acquire) deben esperar a que
terminen stas; es decir, hay que mantener el orden sa >> rd / wr.

5.3

165

MODELOS RELAJADOS (relaxed)

antes de ejecutar una operacin de sincronizacin tipo liberacin


(release), el procesador debe esperar a que finalicen todas la
operaciones de memoria anteriores; es decir, hay que mantener el
orden rd / wr >> sr.

s_acq

rd ...wr ...

s_rel

rd ...wr ...

rd ...wr ...

Estos dos ltimos modelos de consistencia son adecuados en los casos de


planificacin dinmica de las instrucciones (desorden/desorden), puesto que
se acepta la finalizacin 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
ningn 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 caractersticas principales de los


modelos de consistencia.
Orden de las operaciones de memoria
Modelo

wr>>rd

SC

wr>>wr rd>>rd/wr

sinc.

Instrucc. para

wr atom. imponer orden

todas

TSO

todas

PC

todas

todas

STBAR, RMW

WO

todas

SYNC

RC

sa >> w/r
w/r >> sr
s >> s

PSO

MEMBAR, RMW

MEMBAR, RMW

REL, ACQ,
RMW

166

Captulo 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

rd

wr

sinc_a
wr

TSO/PC
wr >> rd

PSO
wr >> wr

= A

= A

WO
RC
rd >> wr, rd
= A
B

= A
B

sinc_acq

sinc_acq

sinc_acq

sinc_acq

sinc_acq

= D

=
= D

=
= D

rd

sinc_r

sinc_rel

sinc_rel

sinc_rel

sinc_rel

sinc_rel

wr

wr

5.4

= D

RESUMEN Y PERSPECTIVAS

Para que los programas paralelos tengan una semntica 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 relacin a las operaciones de sincronizacin (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.

5.4

RESUMEN Y PERSPECTIVAS

167

Si consideramos el rendimiento del sistema, los modelos relajados


debieran ser ms eficientes que el modelo estricto SC, ya que en este caso no
pueden aplicarse muchas de las optimaciones ms habituales, y, en
consecuencia, la eficiencia debiera ser menor. Pero como siempre, debemos
analizar los aspectos positivos y negativos de la aplicacin de modelos de
consistencia relajados, ya que para poder aplicar estos modelos se necesita la
colaboracin del hardware y del software (nuevas instrucciones, identificar
correctamente los puntos de ordenacin dentro del programa, etc.).
Uno de los investigadores principales de estas cuestiones es Mark Hill. En
su opinin, los multiprocesadores deberan utilizar SC como modelo bsico,
y, tal vez, ofrecer como alternativa un modelo relajado. Por qu?
En los procesadores actuales es habitual el uso de ejecucin especulativa
de las instrucciones. Las instrucciones se ejecutan sin estar seguro de que
hay que hacerlo. Cuando su ejecucin 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 haba 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 ejecucin del programa.
Siendo eso as, aunque el modelo de consistencia sea SC podran 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 diferenciaran entonces ambos tipos de consistencia?
Pues en que, en el caso de los modelos relajados, las instrucciones se
retiraran antes del procesador, ya que no habra que esperar a saber si la
reordenacin efectuada ha sido correcta o no.
Siempre es necesario medir las hipotticas ventajas de cualquier tipo de
estrategia utilizando programas reales o bancos de pruebas. Algunos
experimentos realizados muestran que los tiempos de ejecucin 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 diseadores de
middleware (software del sistema, aplicaciones en bajo nivel...) la
complejidad inherente al uso de modelos de consistencia relajados? Por
ejemplo, corresponde a los diseadores de compiladores introducir las
instrucciones fence (las estrictamente necesarias y no ms, para no perder
eficiencia); para facilitar la portabilidad del software hay que implementar
adecuadamente todos los modelos para poder trabajar en plataformas

168

Captulo 5: CONSISTENCIA DE LA MEMORIA EN LOS SISTEMAS PARALELOS

hardware diferentes; etc. Programar en paralelo es difcil en s mismo, y ms


an si hay que considerar modelos relajados de consistencia.
En resumen: el modelo SC es el estndar 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 ms habituales (adelantar los
LD) y sus efectos sobre el programador son bajos. Los modelos que eliminan
cualquier restriccin en el orden de las operaciones de memoria parecen ms
difciles de justificar.

Potrebbero piacerti anche