Examen Final Junio 2007

Anuncio
Arquitectura e Ingeniería de Computadores
Examen final de junio
Jueves, 21 de junio de 2007
¡Acuérdate de poner tu nombre en todas las hojas que utilices!
¡Justifica claramente todas tus contestaciones!
Utiliza grupos de folios separados para responder a las cuestiones de cada parte
SOLUCIONES
PARTE ARQUITECTURAS MONOPROCESADOR
1.
(2 puntos) Tenemos un procesador superescalar homogéneo de grado 2 (en todas las etapas
se pueden manejar hasta 2 instrucciones por ciclo), con búsqueda alineada y emisión alineada y en
orden. Para mantener la consistencia secuencial, así como para renombrar los registros y gestionar
las interrupciones se utiliza un ROB. Existe una estación de reserva para cada unidad de ejecución
y el predictor de saltos es estático y predice que no va a saltar en los saltos hacia adelante, y que si
va a saltar en los saltos hacia atrás. Se dispone de las siguientes unidades de ejecución: dos unidades
de carga/almacenamiento (segmentadas) de latencia 2 (calcula la dirección en el primer ciclo y
accede a la cache en el segundo ciclo), dos ALUs enteras de latencia 1, un sumador en coma flotante
(segmentado) de latencia 3, un divisor en coma flotante (segmentado) de latencia 4, y una unidad de
saltos que resuelve los saltos en la etapa de ejecución. Suponemos que en el cauce de enteros están
implementados los cortocircuitos y reenvíos habituales, pero no así en el cauce de coma flotante.
Supón para este problema un tamaño ilimitado del ROB y de cada una de las estaciones de reserva.
En dicho ordenador se va a ejecutar la siguiente secuencia de instrucciones que calcula la división
de dos vectores x e y componente a componente:
addi r2, r0, r0
lw r3, n
slli r4, r3, #2
inicio: lf f0, x(r2)
lf f1, y(r2)
divf f2, f0, f1
sf f2, z(r2)
addui r2, r2, #4
sub r5, r4, r2
bnez r5, inicio
trap #0
<sgte.>
;
;
;
;
;
;
;
;
;
;
;
;
;
el valor inicial de n es bastante grande
r2 = 0 (índice)
r3 = n
r4 = n * 4 (final del vector)
f0 = x(i)
f1 = y(i)
f2 = x(i) / y(i)
z(i) = x(i) / y(i)
r2 = r2 + 4 (incremento el desplazamiento)
compruebo si he llegado al final
saltar a inicio si r5 es distinto de 0
fin del programa
suponemos las instrucciones siguientes enteras
Nota: La instrucción trap no causa la terminación del programa hasta que se retira del ROB.
Considera que la primera vez que se accede a memoria (para obtener el valor de n y para obtener
el valor de los vectores a partir de r2 ) se produce un fallo de cache L1 que se resuelve en la cache
L2 con una latencia total de 3 ciclos. El resto de accesos a memoria son servidos por la cache L1
gracias a la técnica del prefetching.
1
a) (1 punto) ¿En qué ciclo se confirma la primera instrucción de salto? ¿En que ciclo se confirma
la segunda instrucción de salto? Dibuja el diagrama correspondiente para justificar tu respuesta.
En dicho diagrama indica, para cada instrucción y ciclo de reloj, qué fase de la instrucción se
está ejecutando.
b) (0,5 puntos) ¿Cuántas instrucciones tendrá el ROB al acabar el ciclo en que se confirma la primera
instrucción ejecutada (addi r2, r0, r0)? Para justificar la respuesta, muestra el contenido del
ROB en dicho ciclo.
c) (0,5 puntos) Considera ahora que el procesador tiene un reloj de 1 GHz. y que el régimen
estacionario de ejecución es igual al de la segunda iteración. ¿En cuanto tiempo (en segundos)
se ejecutaría el código anterior para un tamaño del vector de 1000 elementos? No hace falta que
tengas en cuenta el tiempo necesario para el llenado/vaciado del pipeline. ¿Cuantos MFLOPs
obtendríamos para el procesador descrito ejecutando el código anterior?
Solución
a) Para obtener las dos preguntas de este apartado tenemos que simular la ejecución del programa
durante las dos primeras iteraciones. Obtenemos el siguiente diagrama instrucciones–tiempo:
1
addi r2, r0, r0
lw r3, n
slli r4, r3, #2
lf f0, x(r2)
lf f1, y(r2)
divf f2, f0, f1
sf f2, z(r2)
addui r2, r2, #4
sub r5, r4, r2
bnez r5, inicio
lf f0, x(r2)
lf f1, y(r2)
divf f2, f0, f1
sf f2, z(r2)
addui r2, r2, #4
sub r5, r4, r2
bnez r5, inicio
trap #0
2
3
IF IS EX
IF IS EX
IF IS
IF IS
IF
IF
4
5
WB
L2
EX
IS
IS
IF
IF
C
L2
L2
EX
IS
IS
IF
IF
6
7
8
L2
L2
EX
EX
IS
IS
IF
IF
WB
EX
L2
WB
IS
IS
IF
IF
C
WB
WB
L1
EX
EX
EX
IS
IS
IF
IF
9
10 11 12 13 14 15 16 17 18 19 20 21
C
C
WB
WB
EX
L1
L1
EX
IS
IS
IF
IF
C
EX
WB
WB
EX
IS
XX
EX
EX
WB
EX
-
EX
EX
WB
EX
EX
EX
-
WB
EX
-
C
C(S1)
- C
- C
- - - WB - - - - -
C
C
-
C
C
-
C(S1)
C
- C
- C
En el diagrama denotamos con L1 un acceso a memoria resuelto en la cache L1 y con L2 un acceso
a memoria satisfecho por la cache L2. Como podemos apreciar, los dos primeros accesos a cache
fallan en la L1, siendo resueltos en tres ciclos por la cache L2. El tercer acceso a memoria, aunque
realiza la etapa de ejecución (EX), se queda bloqueado por la falta de unidades funcionales (solo
hay dos unidades segmentadas). Cuando desaparece el riesgo estructural, el acceso se resuelve en
L1 pues el dato buscado (y(r2)) está en el mismo bloque de cache que el anterior acceso (x(r2)).
A partir del diagrama anterior vemos que la primera instrucción de salto se confirma en el ciclo
17, mientras que la segunda instrucción de salto lo hace en el ciclo 20.
b) La primera instrucción ejecutada se confirma en el ciclo 5. Al final de dicho ciclo el contenido
del ROB es el siguiente:
2
Entrada
1
2
1
1
5
6
7
ocupado
Si
Si
Si
Si
Si
Si
Si
instr.
lw r3, n
slli r4, r3, #2
lf f0, x(r2)
lf f1, y(r2)
divf f2, f0, f1
sf f2, z(r2)
addui r2, r2, #4
estado
L2
IS
L2
EX
IS
IS
IS
dest.
r3
r4
f0
f1
f2
Mem[z(r2)]
r2
valor
No hemos rellenado el campo valor de ninguna entrada porque no hay ninguna instrucción que
haya hecho la etapa de escritura.
El número total de entradas del ROB ocupadas es de 7 al final del ciclo 5.
c) Del diagrama del apartado a) podemos ver que cada iteración del bucle en el régimen estacionario tarda en ejecutarse 4 ciclos de reloj (medido como la diferencia entre el comienzo de las
instrucciones de carga en el régimen normal). Por lo tanto, el tiempo que tardan en ejecutarse
1000 iteraciones, sin tener en cuenta los ciclos del llenado/vaciado del pipeline, es el siguiente:
T iempo = 4 ciclos ∗ 1000 = 4000 ciclos = 4 µseg
Para el cálculo de los MFLOPs, tenemos que en cada iteración sólo realizamos una operación en
coma flotante (la división), por lo que obtendríamos:
MFLOPs = 1 op / 4 ciclos @ 1 Ghz = 250 MFLOPs.
¤
2. (2 puntos) En este problema queremos ejecutar el mismo código del problema anterior pero
ahora en un procesador superescalar planificado estáticamente con grado de emisión de dos y con
las mismas características hardware del problema anterior (salvo que no tiene predictor de saltos y
el cálculo del salto se realiza en la etapa ID). Al igual que antes, considera que la primera vez que
se accede a memoria (para obtener el valor de n y para obtener el valor de los vectores a partir de
r2 ) se produce un fallo de cache L1 que se resuelve en la cache L2 con una latencia total de 3 ciclos.
El resto de accesos a memoria son servidos por la cache L1 gracias a la técnica del prefetching. Se
pide:
a) (0,75 puntos) ¿Cuantos ciclos tarda en ejecutarse las dos primeras iteraciones del bucle sin
planificar el código (teniendo en cuenta el código de inicialización)? Compáralo con el tiempo
obtenido en el caso del procesador superescalar planificado dinámicamente. Razona la respuesta.
b) (0,75 puntos) Considera ahora que el compilador desenrolla el bucle 2 veces y lo planifica para
que se ejecute lo más rápido posible. ¿Cuantos ciclos tarda ahora en ejecutarse las dos primeras
iteraciones del bucle (mas el código de inicialización)? Razona la respuesta.
c) (0,5 puntos) Considera ahora un procesador VLIW con el mismo hardware anterior (incluidos los
fallos de cache). La instrucción VLIW puede contener hasta 7 operaciones (una por cada una de
las UFs disponibles). ¿En cuantos ciclos se ejecutan las dos primeras iteraciones del bucle (mas
el código de inicialización)? Considera que el bucle se ha desenrollado 2 veces y se ha planificado
convenientemente. ¿Cual es la densidad de código obtenida así como los MFLOPs que podríamos
obtener para dicho código suponiendo un ciclo de reloj de 1 Ghz? Razona las respuestas.
Solución
3
a) En primer lugar, como el cálculo del salto se realiza en la etapa ID consideramos un hueco de
retardo para el salto de 1 ciclo. Tenemos en cuenta que los dos primeros accesos a memoria fallan
en la cache L1, siendo resueltos en tres ciclos por la cache L2. En segundo lugar, comentar que
aunque nos dice que el hardware es el mismo que en el problema anterior, en este caso vamos
a considerar que existe cortocircuito entre las UFs en coma flotante, pues es lo habitual en los
procesadores superescalares planificados estáticamente.
Obtenemos la siguiente temporización (nótese como en cada ciclo emitimos como mucho 2 instrucciones):
Ciclos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LD/ST
lw r3, n
LD/ST
ALU
addi r2, r0, r0
lf f0, x(r2)
slli r4, r3, #2
ALU
PF (DIV)
Saltos
lf f1, y(r2)
divf f2, f0, f1
sf f2, z(r2)
addui r2, r2, #4
sub r5, r4, r2
bnez r5, inicio
lf f1, y(r2)
lf f0, x(r2)
divf f2, f0, f1
sf f2, z(r2)
addui r2, r2, #4
sub r5, r4, r2
bnez r5, inicio
Podemos ver que la etapa de ejecución tarda 29 ciclos en realizarse (teniendo en cuenta el hueco
de retardo del salto). Añadiéndole las etapas de búsqueda, decodificación y escritura, obtenemos
un tiempo total de 32 ciclos en ejecutar este código.
Comparando con los 20 ciclos obtenidos en el caso del superescalar planificado dinámicamente,
vemos que es bastante peor. Esto se debe a que los riesgos de datos frenan la emisión de instrucciones (debido a que no hay estaciones de reserva), por lo que se aprovecha poco que las
unidades funcionales estén segmentadas. En la tabla anterior también podemos ver las detenciones producidas por el fallo de cache de las dos primeras operaciones de memoria.
b) En este caso vamos a procurar mejorar el rendimiento del procesador superescalar planificado
dinámicamente por medio de desenrollar el bucle dos veces y planificar adecuadamente el orden de
las instrucciones para que se ejecute lo más rápido posible. Obtenemos la siguiente temporización:
Ciclos
1
2
3
4
5
6
7
8
9
10
11
12
13
LD/ST
lw r3, n
LD/ST
ALU
addi r2, r0, r0
ALU
PF (DIV)
Saltos
lf f0, x(r2)
lf f1, y(r2)
lf f01, x(r2+4)
slli r4, r3, #2
lf f11, y(r2+4)
divf f2, f0, f1
addui r2, r2, #8
sub r5, r4, r2
divf f21, f01,f11
sf f2, z(r2-8)
bnez r5, inicio
sf f21, z(r2-4)
4
Obsérvese como la 3a carga lf f1, y(r2) empieza en el ciclo 4 y la 4a carga lf f01, x(r2+4)
en el ciclo 5, gracias a que las UFs de carga/almacenamiento están segmentadas.
Ahora la mejora es espectacular. El tiempo total de ejecución es de 13 ciclos más las etapas de
búsqueda, decodificación y escritura, lo que hace un total de 16 ciclos. Vemos como incluso mejora
al planificado dinámicamente, lo que nos da una idea de lo interesante que es la planificación de
código por parte del compilador y lo útil que es eliminar del código instrucciones de algún modo
redundantes (addui y sub).
c) En este caso obtenemos el siguiente diagrama:
Ciclos
1
2
3
4
5
6
7
8
9
10
11
LD/ST
lw r3, n
NOP
NOP
lf f1, y(r2)
lf f11, y(r2+4)
NOP
NOP
NOP
sf f2, z(r2-8)
NOP
sf f21, z(r2-4)
LD/ST
lf f0, x(r2)
NOP
NOP
NOP
lf f01, x(r2+4)
NOP
NOP
NOP
NOP
NOP
NOP
ALU
addi r2, r0, r0
NOP
NOP
NOP
slli r4, r3, #2
NOP
addui r2, r2, #8
sub r5, r4, r2
NOP
NOP
NOP
ALU
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
PF (DIV)
NOP
NOP
NOP
NOP
NOP
divf f2, f0, f1
divf f21, f01,f11
NOP
NOP
NOP
NOP
PF (ADD)
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
Saltos
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
bnez r5, inicio
NOP
Como podemos ver, gracias a que el procesador VLIW puede lanzar más instrucciones en paralelo
(en el ciclo 1 y en el ciclo 5 lanza 3 instrucciones), obtenemos un tiempo algo menor. El código
por tanto se ejecuta en 14 ciclos (los 11 mostrados más 3 para las etapas IF, ID y WB).
La densidad de código útil obtenida es:
Densidad = 14 / (11 * 7) = 18,18 %.
Para el cálculo de los MFLOPs, podemos ver que cada desenrollado del bucle (2 iteraciones y
por tanto 2 operaciones en coma flotante) se ejecutará en 14 ciclos. Tenemos por tanto:
MFLOPs = 2 ops / 14 ciclos @ 1 Ghz = 142,85 MFLOPs.
¤
PARTE ARQUITECTURAS MULTIPROCESADOR
3. (1,5 puntos) Explicar brevemente cada una de las cuestiones que a continuación se plantean
(se valorará la capacidad de concreción del alumno).
a) (0,25 puntos) Dada un multiprocesador formado por varios nodos, cada uno con la estructura
siguiente, explicar a qué categoría o categorías de las estudiadas en clase puede pertenecer la
máquina.
b) (0,25 puntos) ¿En qué consiste un directorio jerárquico? ¿En qué se diferencia de un directorio
plano basado en memoria?
5
c) (0,50 puntos) Para el siguiente fragmento de código explicar si es preferible (se producen menos
fallos de cache) un protocolo de coherencia basado en actualización (DRAGON) o en invalidación
(MESI). Suponer que inicialmente todas las variables están a cero, que flag y A están en líneas
de memoria distintas y que las caches de los procesadores sólo contienen las variables locales de
cada proceso.
P1
P2
int i;
int i;
for (i=0; i<1000; i++) {
while (flag) {}
A = g(i);
flag = 1;
}
for (i=0; i<1000; i++) {
while (!flag) {}
print A;
flag = 0;
}
d) (0,25 puntos) ¿Qué es el ancho de banda de la bisección de una red? ¿Por qué es importante
esta medida?
e) (0,25 puntos) Explicar el problema más importante que la implementación de los cerrojos basada
en array (array-based lock ) tiene en arquitecturas ncc-NUMA y cómo podría ser resuelto.
Solución
a) En primer lugar se trata de un multiprocesador de memoria compartida, puesto que la comunicación está integrada a nivel del sistema de memoria. Además, es un multiprocesador NUMA
puesto que cada nodo tiene una memoria local “cercana”. Así pues, las categorías a las que podría
pertenecer son cc-NUMA y ncc-NUMA.
b) En un directorio jerárquico la información de directorio se organiza en una estructura de datos
lógica de tipo árbol, en la que las hojas representan los nodos de procesamiento, cada uno con
su memoria local, y los nodos intermedios contienen la información de directorio. La diferencia
con un directorio plano basado en memoria es que para éste último la información de directorio
para cada bloque de memoria reside en un lugar fijo y conocido (el nodo home), mientras que
para un directorio jerárquico hay que encontrarla atravesando la jerarquía.
c) El protocolo basado en actualización se comporta mejor. Hay dos variables compartidas: flag
y A. La variable A es escrita por P1 y leída por P2, por lo que un protocolo de actualización
generaría una transacción de Update en cada iteración del bucle (fallo de escritura), mientras
que uno de invalidación generaría 2 (Fallo de escritura + fallo de lectura). Para la variable flag
ocurre algo similar, se escribe y se lee en cada iteración del bucle (por procesadores diferentes),
con lo que un protocolo basado en actualización genera menos fallos.
d) Se define como la suma de los anchos de banda del mínimo número de enlaces que separan la
red en dos mitades. Da una idea del ancho de banda máximo de una red de interconexión, ya
que bajo tráfico uniforme la mitad de los mensajes habrán de “cruzar” la bisección.
e) El problema es que la dirección de memoria sobre la que un proceso ha de realizar la espera
activa no es conocida a priori en esta implementación de los locks. De esta forma, esta dirección podría corresponder con una memoria remota, con lo que no podría ser almacenada en la
cache del proceso que realiza la espera activa. Esto ocasionaría una gran cantidad de tráfico
(una transacción de red por cada lectura hecha durante la espera activa). La solución pasa por
modificar la implementación de los cerrojos para asegurar que cada procesador realiza la espera
activa sobre una posición de memoria local. Esto se hace, por ejemplo, en la implementación de
los cerrojos con encolado software (Software Queuing Lock ).
6
¤
4. (3 puntos) Suponer un multiprocesador SMP con 10 procesadores y el protocolo de coherencia
de caches MSI estudiado en clase. Para implementar las barreras se ha optado por la versión software
centralizada con “cambio de sentido” (sense-reversal ) estudiada en clase.
a) (0,5 puntos) Explica las variables compartidas necesarias para llevar a cabo esta implementación,
así como el cometido de cada una de ellas. Para cada cada variable, indica también por cuáles
de los procesos es accedida y cómo (por ejemplo, leída)
b) (1 punto) Dado que el multiprocesador proporciona la versión de los cerrojos ticket-lock estudiada
en clase, calcula el número de fallos de cache que se producirían cuando 10 procesos (cada uno
ejecutándose en un procesador diferente) atravesaran una barrera. Para dicho cálculo suponer
que los procesos alcanzan la llamada BARRIER exactamente a la misma vez y que el árbitro
de bus otorga el control del mismo siguiendo un orden FIFO (para las peticiones que llegan al
mismo tiempo el árbitro da prioridad a las de los procesadores con un identificador más bajo),
que inicialmente la cache asociada a cada procesador no almacena ninguna variable compartida
(sí las variables locales al proceso), y que las variables compartidas identificadas en el apartado
a) residen en bloques de memoria diferentes.
c) (0,5 puntos) Cambiamos el protocolo de coherencia MSI por uno MESI optimizado para el patrón
migratorio (implementa la transición M → I para BusRead y el bloque es cargado en estado
E en la cache del procesador que tiene el fallo de lectura1 ). Bajo las mismas condiciones del
apartado anterior, ¿cuántos fallos de cache se producirían ahora?
d) (0,5 puntos) El ISA de los procesadores de la máquina dispone del par de instrucciones LL y SC,
que podrían ser empleadas directamente en el código de las barreras para ahorrar las llamadas
a los procedimientos de librería LOCK y UNLOCK. Muestra cómo quedaría el código de la
implementación de las barreras en este caso.
e) (0,5 puntos) Para el nuevo código de las barreras, ¿cuántos fallos de cache se producirían?
Solución
a) La implementación software de las barreras centralizadas con “sense-reversal” emplea tres variables compartidas:
counter : variable de tipo entero que se va incrementando con cada proceso que llega a la
barrera. Cuando su valor alcanza el número de procesos se procede a la liberación de la
barrera. Esta variable es leída y escrita por todos los procesos.
lock : variable de tipo cerrojo que es empleada para asegurar el acceso en exclusión mutua
a la variable anterior. Este cerrojo es adquirido y liberado por todos los procesos.
flag: variable de tipo entero usada para realizar la espera ocupada. Todos los procesos la
leen salvo el último en llegar a la barrera, que modifica su valor para notificar la liberación
de la misma al resto.
b) Para calcular el número de fallos de cache que se producen para que todos los procesos logren
pasar la barrera, vamos a mostrar lo que ocurriría para el primer proceso que entra en la misma:
1
Notar que esto obliga a notificar a memoria los reemplazos de bloques de datos en estado E
7
El número de fallos de cache que se producen hasta que el primer proceso logra adquirir el
cerrojo son: 10 como consecuencia de la instrucción fetch&increment (uno por proceso) +
10 como consecuencia de la lectura de now_serving (uno por proceso).
Después, dentro de la sección crítica el proceso leerá y posteriormente escribirá el contenido
del contador compartido, lo que ocasionará 2 fallos de cache con el protocolo MSI de clase.
Finalmente, el lock es liberado mediante una escritura de now_serving que ocasiona un
fallo de cache y el proceso que efectúa la liberación queda realizando la espera activa sobre
el flag compartido, lo que ocasiona un nuevo fallo de cache.
En total se producen 24 fallos de cache. El resto de procesos irán entrando en la sección crítica
conforme vayan viendo que el valor del contador now_serving coincide con el valor de su ticket.
De esa forma el número de fallos como consecuencia de la lectura de now_serving se irá reduciendo en 1 conforme un nuevo proceso entre en la P
sección crítica y el número de fallos de cache
para el resto de procesos salvo el último será de 7i=0 (13 − i). El último proceso en entrar a la
barrera procede de la misma forma, salvo que en lugar de realizar la espera activa sobre el flag,
escribe esta variable para notificar la liberación de la barrera, así como resetea el contador. Esto
ocasiona 1 fallo de cache para escribir la variable flag. Para resetar el contador, sin embargo, no
se produce fallo de cache, puesto que el proceso ya lo modificó con anterioridad y está en estado
M en su cache. Finalmente, los procesos que están en la espera ocupada sufren 1 fallo de lectura
cada uno para “ver” el nuevo valor de flag.
De esta forma, el número total de fallos de cache es de: 24 + 13 + 12 + 11 + 10 + 9 + 8 + 7 +
6 + 5 + 9 = 114.
c) La sustitución del protocolo MSI de clase por el MESI con la optimización del patrón migratorio
descrita en el enunciado supone las siguientes modificaciones en cuanto a comportamiento se
refiere:
La primera lectura a un bloque de datos hará que se cargue en cache en estado E, y por lo
tanto no ocasionará fallo en caso de que el procesador quiera escribirlo con posterioridad.
Cuando un procesador sufre un fallo de lectura para un bloque que otro procesador tiene en
estado M, el bloque de datos se trae en estado E (en lugar de S ), con lo que una posterior
escritura por parte del que tiene el fallo de lectura no genera fallo de cache.
De esta forma, nos ahorramos uno de los dos fallos que se producirían en la sección crítica como
consecuencia de la lectura y posterior modificación de la variable counter, dado que la lectura
traería el bloque de datos en estado E, con lo que la escritura posterior no ocasionaría fallo.
De esta forma, el número total de fallos de cache es de: 23 + 12 + 11 + 10 + 9 + 8 + 7 + 6 +
5 + 4 + 9 = 104.
d) El pseudocódigo de la implementación de las barreras con cambio de sentido utilizando el par
LL/SC quedaría como sigue:
struct bar_type {
int counter;
int flag = 0;}
bar_name;
BARRIER (bar_name, p) {
local_sense = !(local_sense);
incr: ll reg1, bar_name.counter
addi reg1, reg1, 1
sc bar_name.counter, reg1
beqz incr
8
if (reg1 == p-) {
bar_name.counter = 0;
bar_name.flag = local_sense;
} else
while (bar_name.flag != local_sense) {};
}
e) Asumiendo el protocolo MSI, el número de fallos de cache para esta nueva implementación se
ve reducido de manera significativa. Cuando el primer proceso atraviesa la barrera se producen
10 fallos para las instrucciones de LL, 1 fallo para el SC que tiene éxito. Después todos los
procesos salvo éste vuelven a intentar el LL. El proceso que ha logrado aumentar el valor de
counter observaría un nuevo fallo de cache cuando intentase leer la variable flag usada para la
implementación de la espera activa. En total 12 fallos de cache.
P
De esta forma, el número total de fallos sería: 9i=0 (12 − i) + 9 = 84
¤
5. (1,5 puntos) Para el siguiente segmento de código indica, justificando la respuesta, cuáles son
los resultados posibles que se imprimirían por pantalla en los siguientes casos (supón que todas las
variables están inicialmente a cero):
P1
A=1
flag1 = 1
P2
while (!flag1) {}
B=2
flag2 = 1
P3
while (!flag2) {}
if (!flag1) print (“Error”)
else print (A,B)
a) (0,5 puntos) Multiprocesador que implementa el modelo de consistencia secuencial.
b) (0,5 puntos) Multiprocesador que implementa un modelo de consistencia tipo PSO y preserva
la atomicidad de las escrituras.
c) (0,5 puntos) Multiprocesador que implementa el modelo de consistencia tipo PSO con escrituras
no atómicas.
Solución
a) Bajo el modelo de consistencia secuencial el único resultado que podría dar la ejecución del
programa es la impresión por pantalla del par (A,B) = (1,2). Puesto que la escritura de flag1 y
flag2 se produce después de actualizar los valores de A y B, y además, la escritura de flag2 se
produce después de la de flag1, éste es el único resultado posible.
b) El modelo de consistencia PSO permite que una operación de lectura se complete antes que
una escritura que la precede en el orden de programa, y que escritura a distintas posiciones de
memoria puedan finalizar en desorden. Por otro lado el que las escrituras sean atómicas significa
que son visibles a todos los procesos a la misma vez (una vez un proceso observa el valor de una
escritura ésta es vista también por el resto).
Para nuestro código, la atomicidad de las escrituras asegura que no se va a imprimir el mensaje
de error por pantalla, ya que el hecho de que P2 vea la escritura a flag1 por parte de P1 significa
que P3 también la va a ver tras observar el cambio del valor de flag2. Sin embargo, puesto que el
orden de las escrituras que cada proceso hace no está garantizado de ninguna forma, el programa
podría imprimir por pantalla uno de los siguiente 4 valores para el par (A,B) = {(0,0), (1,0),
(0,2), (1,2)}.
9
c) Finalmente, si eliminamos la restricción de que las escrituras sean atómicas, además de los
valores para (A,B) mostrados en el apartado anterior, la ejecución del código podría resultar en
la impresión del mensaje de error por pantalla, ya que P3 podría observar la modificación de
flag2 por parte de P2 pero no la de flag1 por parte de P1.
¤
10
Descargar