Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Capítulo 5
Capítulo 5
Procesamiento VLIW
Los procesadores VLIW (Very Long Instruction Word) son procesadores segmentados que
pretenden aprovechar el paralelismo entre instrucciones (ILP) alcanzando valores del CPI
(ciclos por instrucción) por debajo de la unidad. Sin embargo, mientras que en un procesador
superescalar se incluyen recursos hardware como los búferes de reordenamiento, los búferes
de renombrado, etc., para extraer dinámicamente el paralelismo, en un procesador VLIW, el
compilador es el responsable fundamental a la hora de aprovechar de forma óptima el
paralelismo del procesador. De esta forma, se pretende reducir la complejidad hardware de la
microarquitectura utilizando cauces más escalables, en los que el aumento en el número de
instrucciones que se emiten por ciclo no suponga un incremento demasiado elevado de la
complejidad del mismo, y con un consumo energético moderado por parte del procesador.
Captación Cola de
(IF) instrucciones
Decodificación
(ID)
Registros de
la arquitectura
El término VLIW hace referencia al hecho de que las instrucciones que procesa el cauce las
construye el compilador empaquetando operaciones que pueden ejecutarse en paralelo en el
procesador (debido a las unidades funcionales disponibles y a la independencia entre las
mismas). En un procesador superescalar, cada una de esas operaciones se codificaría
mediante una única instrucción y las dependencias entre ellas se deben comprobar en el
propio cauce. En un procesador VLIW, gracias al trabajo del compilador, las operaciones
empaquetadas en una instrucción VLIW son independientes y pasan a los slots de emisión del
procesador sin más comprobación. Esto simplifica la microarquitectura del procesador
VLIW con respecto al superescalar. Se dice que en un procesador superescalar, la
planificación de instrucciones es dinámica, mientras que en un VLIW es estática. La Figura 1
muestra un esquema de cauce superescalar. Frente a un procesador superescalar, un
procesador VLIW tiene la desventaja de que una menor portabilidad de los códigos de
procesadores de una misma familia, pero con ciertas diferencias en la microarquitectura, ya
que el compilador debe tener en cuenta las características concretas de la microarquitectura
del cauce para la que se esté generando código.
Los riesgos de control dificultan la planificación del código al compilador, ya que en tiempo
de compilación no se puede saber, a ciencia cierta, qué comportamiento tendrán los saltos
del programa, por lo que se complicará la tarea de encontrar operaciones independientes para
completar las instrucciones VLIW. Por lo tanto, cuantos menos saltos aparezcan en el
código, más sencilla será la planificación. Se denomina bloque básico al conjunto de
operaciones situadas entre dos instrucciones de salto. Así que la tarea principal del
compilador consistirá en la planificación más adecuada de las operaciones de cada uno de los
bloques básicos del programa. Existen algunas técnicas software que permiten aumentar el
número de instrucciones independientes de los bloques básicos, de forma que se facilite la
planificación local de instrucciones, como el desenrollado de bucles o la segmentación software
(software pipelining).
Una posible implementación del este fragmento de código podría ser la siguiente:
La Tabla 1 muestra una posible planificación del este código en un procesador VLIW. Como
se puede comprobar, debido a las latencias de las unidades de ejecución y a las dependencias
entre las instrucciones del cuerpo del bucle, aunque se puedan emitir hasta tres instrucciones
por ciclo, no se puede aprovechar todo este paralelismo. El código tardaría en ejecutarse
aproximadamente 6n + 2 ciclos para un bucle de n iteraciones y de las instrucciones
ejecutadas, sólo 6n + 3 de las operaciones emitidas serían útiles, mientras que sería necesario
introducir 12n + 3 operaciones nop (los huecos de la tabla) para poder construir las
instrucciones VLIW, es decir, que se estaría desaprovechando aproximadamente un 66% del
paralelismo disponible en el procesador.
El desenrollado de bucles consiste en repetir varias veces el código de una iteración del bucle
original en un nuevo bucle que itere menos veces. De esta forma, el bloque básico del cuerpo
del nuevo bucle estará compuesto por las instrucciones de varias iteraciones del bucle
original, lo que aumentará el número de instrucciones independientes. Por ejemplo, si
desenrollamos cinco veces el código anterior tendríamos el siguiente código:
ld f2, x(r3)
add f4, f2, f0 ld f2, x+8(r3)
sd f4, x(r3) add f4, f2, f0 ld f2, x+16(r3)
sd f4, x+8(r3) add f4, f2, f0
sd f4, x+16(r3)
Figura 2. Construcción del cuerpo del bucle aplicando de segmentación software.
Una alternativa para reducir las dependencias entre instrucciones dentro del cuerpo de un
bucle sin tener que recurrir al incremento de tamaño que implica el desenrollado de bucles es
la segmentación software. Esta técnica se basa en que las instrucciones de carga de datos, de
operación con los datos cargados, y de almacenamiento de los resultados obtenidos del bucle
original se distribuyen en iteraciones diferentes del nuevo bucle, en lugar de encontrarse en la
misma iteración. Con esto se consigue construir un cuerpo del bucle en el que todas las
instrucciones de manejo de datos son independientes. La Figura 2 muestra cómo se
construye el cuerpo del bucle aplicando esta técnica al programa que estamos usando como
ejemplo. Como muestra la figura, no todas las instrucciones de las tres iteraciones del bucle
original pasan a formar parte del nuevo bucle, por lo que será necesario insertarlas como
prólogo y epílogo del nuevo bucle, como se muestra a continuación:
La Tabla 3 muestra cómo se planificaría este código en el procesador VLIW que venimos
usando para este ejemplo. En este caso, el tiempo de ejecución es de unos 3n + 11 ciclos (se
ha reducido un 50% para valores de n grandes), y aún se podría reducir más si una vez
aplicada esta técnica, se desenrollara el bucle un par de veces, de forma que se pudieran
rellenar los tres slots vacíos que quedan en el cuerpo del bucle.
sd f4, x+16(r3)
Mediante las técnicas anteriores se puede ampliar el tamaño de los bloques básicos
introducidos por los saltos de los bucles, de forma que se mejore la eficiencia del código
generado para un procesador VLIW. Sin embargo, en los programa también pueden aparecer
instrucciones de salto condicional para implementar otro tipo de sentencias, como por
ejemplo las sentencias if o switch. Para planificar eficientemente este tipo de sentencias es
necesario aplicar otra técnica denominada ejecución vigilada o predicación de instrucciones. Esta
técnica cambia las dependencias de control por dependencias RAW, consiguiendo ampliar el
tamaño de los bloques básicos.
if (a && b)
j = j + 1;
else if (c)
k = k + 1;
else
k = k – 1;
i = i + 1;
Inicio
Si No
¿a ≠ 0?
p1
Si No
¿b ≠ 0?
p2
Si No
¿c ≠ 0?
p1 p4
p3
jj+1 kk–1
kk+1
ii+1
Fin
La Figura 3 muestra cómo se pueden aplicar predicados para generar el siguiente código sin
saltos condicionales, formando un único bloque básico, que se puede planificar como
muestra la Tabla 4 en un procesador VLIW con dos slots de emisión en el que las cargas de
memoria tienen una latencia de dos ciclos. Las dependencias de control pasan a ser
dependencias de datos y deben respetarse a la hora de ubicar las operaciones en las
instrucciones VLIW. Hay que tener en cuenta que, en una instrucción con predicado, el
predicado es un registro de entrada (se lee) y que en una instrucción de comparación que
determina valores de predicados, los predicados son registros de salida (se escribe sobre
ellos):
lw r1, a ; r1 = a
p1, p2 cmp.ne r1, r0 ; p1 = 1 si a != 0 (p2 = 1 en caso contrario)
(p1) lw r2, b ; r2 = b (si a != 0)
(p1) p1, p2 cmp.ne r2, r0 ; p1 = 1 si a != 0 y b != 0 (p2 = 1 en caso contrario)
(p1) lw r4, j ; r4 = j (si a != 0 y b != 0)
(p1) addi r4, r4, #1 ; r4 = j + 1 (si a != 0 y b != 0)
(p1) sw r4, j ; j = j + 1 (si a != 0 y b != 0)
(p2) lw r3, c ; r3 = c (si a == 0 ó b == 0)
(p2) lw r5, k ; r5 = k (si a == 0 ó b == 0)
(p2) p3 cmp.ne r0, r0 ; p3 = 0 (si a == 0 ó b == 0)
(p2) p4 cmp.ne r0, r0 ; p4 = 0 (si a == 0 ó b == 0)
(p2) p3, p4 cmp.ne r3, r0 ; p3 = 1 ((si a == 0 ó b == 0) y c !=0)
(p3) addi r5, r5, #1 ; r5 = k + 1 ((si a == 0 ó b == 0) y c !=0)
(p4) subi r5, r5, #1 ; r5 = k – 1 ((si a == 0 ó b == 0) y c ==0)
(p2) sw r5, k ; k = r5 (si a == 0 ó b == 0)
lw r6, i ; r6 = i
addi r6, r6, #1 ; r6 = i + 1
sw r6, i ;i=i+1
#. OP1 OP2
1 lw r1, a
2 lw r6, i
3 p1, p2 cmp.ne r1, r0
4 addi r6, r6, #1 (p1) lw r2, b
5 sw r6, i
6 (p1) p1, p2 cmp.ne r2, r0
7 (p2) p3 cmp.ne r0, r0 (p1) lw r4, j
8 (p2) p4 cmp.ne r0, r0 (p2) lw r3, c
9 (p1) addi r4, r4, #1 (p2) lw r5, k
10 (p2) p3, p4 cmp.ne r3, r0 (p1) sw r4, j
11 (p3) addi r5, r5, #1 (p4) subi r5, r5, #1
12
13 (p2) sw r5, k
#. OP1 OP2
1 lw r1, a
2 add r3, r4, r5
3 beqz r10, loop add r6, r3, r7
4 lw r8, 0(r10)
5
6 lw r9, 0(r8)
#. OP1 OP2
1 lw r1, a p1 cmp.ne r10, r0
2 (p1) lw r8, 0(r10) add r3, r4, r5
3 beqz r10, loop add r6, r3, r7
4 lw r9, 0(r8)
Tabla 6. Uso de predicados para implementar una carga especulativa en el código de la Tabla 5.
NOTA: Se sugiere desenrollar el bucle (hasta cuatro iteraciones) para optimizar el velocidad de
ejecución del programa.
UNIDAD
LATENCIA OP1 OP2 OP3 OP4 OPERACIONES REALIZADAS
FUNCIONAL
Comparaciones, sumas y restas
ALU 1 X X X X
con enteros y operaciones lógicas
Saltos condicionales e
Saltos 4 X
incondicionales
Solución
El programa en ensamblador sin desenrollar el bucle puede ser el siguiente:
Si se desenrolla el bucle cuatro veces, y suponiendo que n es múltiplo de cuatro, el código quedaría:
Reorganizando estas instrucciones y asignándolas a las operaciones posibles dentro del procesador
VLIW, se definen las instrucciones del mismo como indica la Tabla 8. Es esta tabla se puede
comprobar cómo se debe adelantar el salto para aprovechar los tres ciclos siguientes mientras se
resuelve. Como se puede ver, en este caso no se pueden ocupar 15 de los 36 huecos existentes para las
operaciones de estas 9 instrucciones VLIW. Es decir, se desperdicia un 41.7% del espacio. Se ha
supuesto que las dependencias WAR y WAW las resuelve el hardware de forma que se pueden incluir
en una misma instrucción VLIW operaciones con el mismo operando.
Solución
En el código que se proporciona en el enunciado existen bastantes latencias entre las instrucciones que
lo componen (por ejemplo, entre la multiplicación y la suma en coma flotante que aparecen). Se
pueden reducir los atascos causados por esas latencias desenrollado el bucle y reorganizando las
instrucciones, tras renombrar algunos registros. Así, si se desenrolla el bucle dos veces, y suponiendo
que n es par, se obtiene:
nop ; (16)
nop ; (17)
sd f0, z ; (18) Almacenamos el resultado
Teniendo en cuenta las latencias entre las distintas instrucciones, se observa que se perderían n + 2
ciclos (mostrados explícitamente mediante instrucciones nop). Los n primeros se perderían de uno en
uno, en cada una de las iteraciones del bucle, entre las instrucciones (10) y (14) dado que las
operaciones en coma flotante tienen una latencia igual a 4 ciclos si el resultado va a ser utilizado por
otra operación de coma flotante. Los dos ciclos perdidos al final se deben a la dependencia entre las
instrucciones (14) y (18), ya que tras una operación en coma flotante se deben esperar tres ciclos para
almacenar el resultado. Para mejorar las prestaciones se ha supuesto que el procesador utiliza un salto
retardado en el que se puede aprovechar la siguiente instrucción al salto. El tiempo estimado de
ejecución de este programa sería de unos:
n
Tseg n 3 12 3 6n 6
2
En el caso del procesador VLIW que puede emitir hasta dos operaciones por ciclo, se tendrá el código
mostrado en la Tabla 10. Debido a las altas latencias de las unidades de procesamiento de números en
coma flotante, el tiempo de ejecución no mejora demasiado. Como se ha podido solapar la ejecución
de tres instrucciones con respecto al código anterior, el tiempo de ejecución estimado sería de:
n
TVLIWn 2 10 3 5n 5
2
sd f0, z
Tseg n 6n 6
S n
TVLIWn 5n 5
6n 6
S max lim S n lim 1.2
n n 5n 5
NOTA: El procesador puede emitir, cada ciclo, una combinación de operación de referencia a
memoria y operación a ALU, o una única operación de salto condicional.
# OP1 OP2
Solución
La distribución de operaciones entre las distintas instrucciones VLIW presenta un hueco en el slot 1 de
la segunda instrucción. Además, si no se produce el salto existe una dependencia RAW entre las
instrucciones (4) y (5) que producirá atascos. Con una instrucción de carga condicional lwc que realice
la carga desde memoria en función del valor de r3 se podría mejorar la situación. Concretamente, se
podría utilizar la instrucción lwc r4, 0(r3), r3, que cargaría el valor de r4 cuando r3 sea distinto de cero,
tal y como indica la Tabla 12, lo que reduciría el código en una instrucción.
# OP1 OP2
Tabla 12. Optimización del código VLIW del problema 3 mediante instrucciones de carga condicional.
Para responder al segundo apartado, vamos a considerar sólo las instrucciones que se incluyen en el
campo de operación 1, ya que las del segundo slot son independientes y no afectarán a los cambios que
vamos a realizar en el código. Por lo tanto, nos centraremos en eliminar los saltos condicionales de la
siguiente secuencia de instrucciones:
En esta secuencia de instrucciones, la instrucción de salto (3) se introduce para que no se ejecute la
instrucción de carga (4) si r3 = 0. Por tanto, se puede suponer que la instrucción de salto tiene la
función de evitar una violación del acceso a memoria. Así, si se adelantara la instrucción (4) para que
estuviera delante de la instrucción de salto, la excepción que se produciría si r3 fuera igual a 0 haría que
el programa terminase. Para que este cambio pueda realizarse es necesario que r3 sea distinto de cero
siempre. En este caso, si existen registros disponibles, es posible utilizar instrucciones de movimiento
condicional para evitar que se produzca la carga en caso de que se vaya a producir la excepción. El
código sería:
donde r6 y r7 son registros auxiliares. En r6 se carga primero una dirección segura, y en r7 se introduce
el valor previo de r4 para poder recuperarlo si la carga especulativa no debía realizarse. Como se puede
comprobar, la especulación tiene un coste en instrucciones cuyo efecto final en el tiempo de ejecución
depende de la probabilidad de que la especulación sea correcta o no.
Es posible evitar la instrucción de salto si se utilizan cargas especulativas para las dos instrucciones de
carga protegidas por el salto en el código inicial. En este caso el código sería el siguiente:
Se supone que la dirección direc a la que se produce el salto viene a continuación de este trozo de
código. Si no fuese así, no se podría aprovechar tan eficientemente el procesamiento especulativo. Así,
en general, cuando existen saltos a distintas direcciones y no existe un punto de confluencia de esos
caminos no suele ser posible obtener mejores prestaciones mediante cambios especulativos que
eliminen la instrucción de salto.
4. En un procesador todas las instrucciones pueden predicarse. Para establecer los valores de
los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
En estas condiciones, utilice instrucciones con predicado para escribir sin ninguna
instrucción de salto el siguiente código:
if (a > b)
x = 1;
else
{
if (c < d)
x = 2;
else
x = 3;
}
Solución
A continuación se muestra el código que implementa el programa anterior usando predicados:
lw r1, a ; r1 = A
lw r2, b ; r2 = B
p1, p2 cmp.gt r1,r2 ; Si a > b p1 = 1 y p2 = 0 (si no, p1 = 0 y p2 = 1)
(p1) addi r5, r0, #1
p3 cmp.ne r0, r0 ; Inicializamos p3 a 0
p4 cmp.ne r0, r0 ; Inicializamos p4 a 0
(p2) lw r3, c ; r3 = c
(p2) lw r4, d ; r4 = d
(p2) p3, p4 cmp.lt r3, r4 ; Sólo si p2 = 1 p3 o p4 pueden ser 1
(p3) addi r5, r0, #2 ; Se ejecuta si p3 = 1 (y p2 = 1)
(p4) addi r5, r0, #3 ; Se ejecuta si p4 = 1 (y p2 = 1)
sw r5, x ; Almacenamos el resultado
5. Suponga un procesador en el que todas las instrucciones pueden predicarse. Para establecer
los valores de los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
x = 0;
if ((a b) && (b 0))
x = 1;
else if ((b < 0) && (a < b))
x = 2;
Solución
La Figura 4 muestra el organigrama que implementa la secuencia de código del enunciado, en el que se
han resaltado los predicados que se usarán para sustituir cada uno de los saltos. A partir de esta figura
es sencillo escribir el siguiente código:
Inicio
x←0
Si No
¿a ≥ b?
p1
Si No
¿b ≥ 0?
p2
p1
Si No
x←1 ¿b < 0?
p2
Si No
¿a < b?
p2
x←2
Fin
Una vez escrito el código, sólo nos queda optimizarlo para la arquitectura VLIW propuesta en el
enunciado, tal y como se muestra en la Tabla 13.
# SLOT 1 SLOT 2
1 lw r1, a lw r2, b
2 add r3, r0, r0
3
4 p1, p2 cmp.ge r1, r2
5 (p1) p1, p2 cmp.ge r2, r0
6 (p2) p2 cmp.lt r2, r0
7 (p2) p2 cmp.lt r1, r2
8 (p1) addi r3, r0, #1 (p2) addi r3, r0, #2
9 sw r3, x
6. En un procesador, todas las instrucciones pueden predicarse. Para establecer los valores de
los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
NOTA: Suponga que la ejecución de todas las instrucciones puede hacerse en un ciclo, y si hay
dependencia de tipo RAW entre instrucciones consecutivas hay que retrasar un ciclo la
segunda. En el caso de las dependencias WAR y WAW no se introducen retardos.
Solución
La Figura 5 muestra un posible organigrama para el código del enunciado.
Inicio
Si No
¿a > b?
p1
Si No
¿a > c?
p2
Si No
¿a < b?
p4
p3 Si No
¿a < c?
x2*x p5
x4*x
Fin
Teniendo en cuenta estas dependencias RAW, y que hay que incluir un ciclo entre cada dos
instrucciones con riesgos RAW, podríamos formar las instrucciones VLIW que muestra la Tabla 14.
El gran número de slots vacíos se debe a la cadena de dependencias existente entre las instrucciones
(1), (2), (8), (9), (10), (11), (13) y (14).
7. En un procesador, todas las instrucciones pueden predicarse. Para establecer los valores de
los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
NOTA: Suponga que la ejecución de todas las instrucciones puede hacerse en un ciclo.
Solución
El realizar un organigrama que refleje el comportamiento del código ayuda bastante a la resolución de
este tipo de problemas, sobre todo a la hora de decidir qué predicados se deben utilizar y dónde hay
que colocarlos. Un posible organigrama para este problema podría ser el que indica la Figura 6.
Inicio
Si No
¿a > b?
p2
Si No
¿a > c?
p1 p3
Si No
xx*2 ¿a < b?
p4
Si No
¿a > c?
p5
xx/2
Fin
Una vez diseñado el organigrama, hay que colocar un predicado en cada una de las instrucciones que
deban estar vigiladas, es decir que dependan de alguna condición, y tras esto, la traducción a código es
directa:
Teniendo en cuenta esto código, y que cualquier instrucción puede alojarse en cualquiera de los tres
slots de emisión, una posible colocación de las instrucciones podría ser la que indica la Tabla 15.
8. En un procesador VLIW cuyas instrucciones pueden codificar tres operaciones (tres campos
o slots en cada instrucción VLIW), todas las operaciones pueden predicarse. Para establecer
los valores de los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
Indique cómo sería el código VLIW para siguiente sentencia sin ninguna operación de salto,
teniendo en cuenta que las instrucciones de comparación sólo pueden aparecer en el primer
campo o slot de la instrucción VLIW (el resto de las instrucciones pueden aparecer en
cualquier campo). Considere que dispone del número de unidades funcionales necesarias en
cada momento.
if (x > 3)
{
y = x;
x = x / 2;
}
else if ((x > 0) && (x < 1))
{
y = – x;
x = 2 * x;
}
Solución
Como en el enunciado nos indican que la secuencia de instrucciones debe quedar sin ningún salto,
debemos predicar las instrucciones que contiene en su interior. La Figura 7 muestra el organigrama del
programa una vez que se han predicado las instrucciones. De este organigrama se puede derivar el
siguiente código ensamblador:
lw r5, x ; r5 = x
p1, p2 cmp.gt r5, r3 ; p1 = 1 si x > 3
p3 cmp.ne r0, r0 ; p3 = 0
p4 cmp.ne r0, r0 ; p4 = 0
(p1) sw r5, y ;y=x
(p1) sra r6, r5, r1 ; r6 = x / 2
(p1) sw r6, x ; x = r6
(p2) p3 cmp.gt r5, r0 ; p3 = 1 si x > 0
(p3) p4 cmp.lt r5, r1 ; p4 = 1 si x < 1
(p4) sub r7, r0, r5 ; r7 = –x
(p4) sw r7, y ; y = –x
(p4) sll r8, r5, r1 ; r8 = 2*x
(p4) sw r8, x ; x = r8
Inicio
Si No
¿x > 3?
p1
p2
yx Si No
¿x > 0?
xx/2
p3
No Si
¿x < 1?
p4
y–x
x 2*x
Fin
A partir de este código, sólo nos queda reorganizar el código respetando las dependencias de datos
para construir las instrucciones VLIW, tal y como muestra la Tabla 16.
9. En un procesador VLIW cuyas instrucciones pueden codificar tres operaciones (tres campos
o slots en cada instrucción VLIW), todas las operaciones pueden predicarse. Para establecer
los valores de los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
Indique cómo se escribiría la siguiente sentencia sin ninguna operación de salto y con el
mínimo número de instrucciones VLIW, teniendo en cuenta que las instrucciones de
comparación sólo pueden aparecer en el primer campo o slot de la instrucción VLIW (el resto
de las instrucciones pueden aparecer en cualquier campo). Considere que dispone del
número de unidades funcionales que necesite en cada momento.
for (i = 0 ; i < 2 ; i++)
{
if (x[i] > 2)
{
y[i] = x[i];
x[i] = 3 * x[i];
}
else if (x[i] > 0)
{
y[i] = – x[i];
x[i] = 5 * x[i];
}
}
Solución
Como en el enunciado nos indican que la secuencia de instrucciones debe quedar sin ningún salto,
debemos desenrollar el bucle y predicar las instrucciones que contiene en su interior. La Figura 8
muestra el organigrama del programa una vez que se ha desenrollado el bucle, destacando los
predicados que se usarán para evitar el uso de instrucciones de salto. A continuación se muestra el
código ensamblador derivado del organigrama:
Inicio
Si No
p1 ¿x[0] > 2?
p2
y[0] x [0] Si No
p3 ¿x [0] > 0?
x[0] 3 * x [0]
y[0] – x [0]
x [0] 5 * x [0]
Si No
p4 ¿x [1] > 2?
p5
y [1] x [1] Si
¿x [1] > 0?
No
p6
x [1] 3 * x [1]
y[1] – x [1]
x [1] 5* x [1]
Fin
lw r1, x ; r1 x[0]
subi r2, r1, #2 ; r2 x[0] – 2
p1, p2 cmp.gt r2, r0 ; p1 1 si x[0] > 2
p3 cmp.ne r0, r0 ; p3 0
(p1) sw r1, y ; y[0] x[0]
(p1) mult r3, r1, r13 ; r3 3*x[0]
(p1) sw r3, x ; x[0] 3*x[0]
(p2) p3 cmp.gt r1, r0 ; p3 1 si x[0] > 0
(p3) sub r4, r0, r1 ; r4 –x[0]
(p3) sw r4, y ; y[0] –x[0]
(p3) mult r5, r1, r15 ; r5 5*x[0]
(p3) sw r5, x ; x[0] 5*x[0]
Tras desenrollar e introducir las operaciones con predicados, sólo nos queda reorganizar el código
respetando las dependencias de datos para construir las instrucciones VLIW. El resultado se muestra
en la Tabla 17.
10. En un procesador VLIW cuyas instrucciones pueden codificar dos operaciones (dos campos
o slots en cada instrucción VLIW), todas las operaciones pueden predicarse. Para establecer
los valores de los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
Indique cómo se escribiría la siguiente sentencia sin ninguna operación de salto y con el
mínimo número de instrucciones VLIW, teniendo en cuenta que las instrucciones de
comparación sólo pueden aparecer en el primer campo o slot de la instrucción VLIW (el resto
de las instrucciones pueden aparecer en cualquier campo).
if ((x[0] % 2) == 0)
{
for (i =1 ; i < 3 ; i++)
{
if ((x[i] % 2) == 0)
x[i] = 2 * x[i];
else if (x[0] < 0)
x[i] = 0;
}
}
Solución
Como en el enunciado nos indican que la secuencia de instrucciones debe quedar sin ningún salto,
debemos desenrollar el bucle y predicar las instrucciones que contiene en su interior. El código en
ensamblador derivado del bucle, sería el siguiente:
p5 cmp.ne r0, r0 ; p5 = 0
p6 cmp.ne r0, r0 ; p6 = 0
p7 cmp.ne r0, r0 ; p7 = 0
Tras desenrollar e introducir las operaciones con predicados, solo nos queda reorganizar el código
respetando las dependencias de datos para construir las instrucciones VLIW. El resultado se muestra
en la Tabla 18.
# SLOT 1 SLOT 2
1 p2 cmp.ne r0, r0 lw r1, x
2 p3 cmp.ne r0, r0 andi r2, r1, #1
3 p1 cmp.eq r2, r0
4 p4 cmp.ne r0, r0 (p1) lw r3, x+4
5 p5 cmp.ne r0, r0 (p1) lw r5, x+8
6 p6 cmp.ne r0, r0 (p1) andi r4, r3, #1
7 (p1) p2, p3 cmp.eq r4, r0 (p1) andi r6, r5, #1
8 (p1) p5, p6 cmp.eq r6, r0 (p2) slli r3, r1, #1
9 p7 cmp.ne r0, r0 (p5) slli r5, r1, #1
10 (p3) p4 cmp.lt r1, r0 (p2) sw r3, x+4
11 (p6) p7 cmp.lt r1, r0 (p5) sw r5, x+8
12 (p4) sw r0, x+4 (p7) sw r0, x+8
Tabla 18. Optimización del código VLIW del problema 10.
11. Se dispone de un procesador VLIW con dos slots de emisión en el que todas las operaciones
pueden predicarse. Para establecer los valores de los predicados se utilizan instrucciones de
comparación (cmp) con el formato (p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se
comprueba entre los registros rx y ry (lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y
p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La instrucción sólo se ejecuta si el predicado p = 1
(habrá sido establecido por otra instrucción de comparación). También existen instrucciones
de comparación con el formato (p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera
y p1 = 0 si es falsa. Si una instrucción no tiene el predicado (p), se entiende que se ejecuta
siempre.
Indique cómo se escribiría la siguiente sentencia sin ninguna operación de salto y con el
mínimo número de instrucciones VLIW:
Solución
Como el código está compuesto por un bucle que itera dos veces y se nos dice en el enunciado que no
debe haber instrucciones de salto en la solución, será necesario desenrollar el bucle dos veces. Cada
una de las iteraciones del bucle seguirá el organigrama mostrado en la Figura 9, que nos ayudará a
escribir el código con predicados que se muestra a continuación:
Inicio
Si
¿x[i] > 0?
p1
Si
¿x[i] < a? No
No
p2
Si
¿x[i] > 4?
No
Si
¿x[i] < b?
p3 p4
x[i] = 2 * x[i] x[i] = 4
No
Fin
; Primera iteración
lw r4, x ; r4 = x[0]
p1, p2 cmp.gt r4, r0 ; p1 = 1 si x[0] > 0
p3 cmp.ne r0, r0 ; p3 = 0
p4 cmp.ne r0, r0 ; p4 = 0
(p1) p3, p2 cmp.lt r4, r1 ; p3 = 1 si x[0] < a
(p2) p4 cmp.gt r4, r3 ; p4 = 1 si x[0] > 4
(p2) p4 cmp.lt r4, r2 ; p4 = 1 si x[0] < b
(p3) add r4, r4, r4 ; r4 = 2 * x[0]
(p4) add r4, r0, r3 ; r4 = 4
sw r4, x ; x[0] = r4
; Segunda iteración
lw r5, x+4 ; r5 = x[1]
p5, p6 cmp.gt r5, r0 ; p5 = 1 si x[1] > 0
p7 cmp.ne r0, r0 ; p7 = 0
p8 cmp.ne r0, r0 ; p8 = 0
(p5) p7, p6 cmp.lt r5, r1 ; p7 = 1 si x[1] < a
(p6) p8 cmp.gt r5, r3 ; p8 = 1 si x[1] > 4
(p6) p8 cmp.lt r5, r2 ; p8 = 1 si x[1] < b
(p7) add r5, r5, r5 ; r5 = 2 * x[1]
(p8) add r5, r0, r3 ; r5 = 4
sw r5, x+4 ; x[1] = r5
Una vez que está escrito el código, sólo nos falta planificarlo en los dos slots del procesador
cumpliendo las restricciones que nos indican en el enunciado, tal y como muestra la Tabla 19.
# SLOT 1 SLOT 2
1 p3 cmp.ne r0, r0 lw r4, x
2 p1, p2 cmp.gt r4, r0 lw r5, x+4
3 p5 ,p6 cmp.gt r5, r0 lw r1, a
4 p7 cmp.ne r0, r0 lw r2, b
5 (p1) p3, p2 cmp.lt r4, r1 addi r3, r0, #4
6 (p5) p7, p6 cmp.lt r5, r1 (p3) add r4, r4, r4
7 p4 cmp.ne r0, r0 (p7) add r5, r5, r5
8 (p2) p4 cmp.gt r4, r3
9 (p2) p4 cmp.lt r4, r2
10 p8 cmp.ne r0, r0 (p4) add r4, r0, r3
11 (p6) p8 cmp.gt r5, r3 sw r4, x
12 (p6) p8 cmp.lt r5, r2
13 (p8) add r5, r0, r3
14 sw r5, x+4
12. En un procesador VLIW cuyas instrucciones pueden codificar dos operaciones (dos campos
o slots en cada instrucción VLIW), todas las operaciones pueden predicarse. Para establecer
los valores de los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
Indique cómo se escribiría la siguiente sentencia teniendo en cuenta que las instrucciones de
comparación sólo pueden aparecer en el primer campo o slot de la instrucción VLIW, y las
instrucciones de salto sólo pueden aparecer en el segundo (el resto de las instrucciones
pueden aparecer en cualquier campo):
El procesador no implementa lógica de bloqueo para asegurarse que los operandos están
disponibles en el momento de emitir las instrucciones, así que tras las instrucciones de carga
de memoria y multiplicación se deberá dejar 1 ciclo (como mínimo) para esperar a que el
resultado esté disponible. El procesador implementa salto retardado, por lo que la siguiente
instrucción VLIW tras un salto siempre se ejecutará independientemente de que se deba
saltar o no. Evite todos los saltos condicionales que no sean de terminación de bucle y
minimice, en la medida de lo posible, el número de slots vacíos en las instrucciones del
programa mediante reorganización de código y desenrollado de bucles.
Solución
La primera aproximación a la solución definitiva consistirá en escribir un programa sencillo que
resuelva el problema. El código en ensamblador derivado del bucle, sería el siguiente:
inicio: lw r1, n ; r1 = n
add r2, r0, r0 ; r2 = 0 (desplazamiento para recorrer x e y)
bucle: lw r3, x(r2) ; r3 = x[i]
p1, p2 cmp.lt r3, r0 ; p1 = 1 si x[i] < 0
(p1) add r4, r3, r0 ; y = 0 si x[i] < 0
(p2) mult r4, r3, r3 ; y = x[i] * x[i] si x[i] ≥ 0
sw r4, y(r2) ; almaceno y(i)
addui r2, r2, #4 ; incremento el desplazamiento
subi r1, r1, #1 ; queda un elemento menos
p3 cmp.gt r1, r0 ; p3 = 1 si quedan elementos
(p3) jmp bucle ; siguiente iteración (si i < n)
Tras escribir el programa en ensamblador, agruparemos las operaciones para crear instrucciones
VLIW para nuestro procesador, respetando que las comparaciones deben ir al slot 1, los saltos al slot 2,
que las multiplicaciones y las cargas de memoria tienen 2 ciclos de latencia, y que se implementa un
salto retardado en el que la siguiente instrucción se ejecuta siempre. El resultado se muestra en la
Tabla 20.
Como se puede comprobar, quedan bastantes slots vacíos. Para solucionar el problema, en un primer
paso reorganizaremos el código subiendo el incremento de r2, el decremento de r1 y la comparación
de r1 con r0. Al subir el incremento de r2, habrá que modificar también la operación de
almacenamiento para que almacene el valor de y en la posición correcta. El resultado se muestra en la
Tabla 21.
Con esta modificación hemos conseguido ahorrar cuatro instrucciones y sólo nos quedan tres slots
vacíos, aunque el overhead del bucle es de cuatro operaciones (el incremento de r2, el decremento de r1,
una comparación y un salto) de las doce del cuerpo del bucle, es decir, un 33%. Este overhead se puede
reducir si se desenrolla el bucle, como indica la Tabla 22 (suponemos que n es par). En esta nueva
versión del programa, se ha reducido el número de nops a 2 y el overhead del bucle al 25%.
if (a == 0)
a = b;
else
a = a + 2;
la variable a es igual a cero la mayoría de la veces. Ilustre cómo el compilador puede mejorar
el tiempo de ejecución utilizando procesamiento especulativo. Realice una estimación de la
ganancia que se obtiene con la especulación en las condiciones establecidas para el
procesador descrito en el problema 1.
Solución
El código ensamblador para esta sentencia, sin utilizar especulación podría ser:
lw r3, a ; r3 = a
bnez r3, else ; Saltamos a else si a != 0
if: lw r3, b ; r3 = b
j fin ; Saltamos a fin para almacenar el resultado
En el caso de que la variable a sea igual a cero, y teniendo en cuenta las latencias de las unidades de
ejecución, el código tardaría en ejecutarse 12 ciclos, tal y como muestra la Tabla 24. En cualquier otro
caso, la sentencia se ejecutaría en 9 ciclos, como muestra la Tabla 25.
Tabla 24. Ejecución de la rama if del código sin especulación para el problema 13.
Tabla 25. Ejecución de la rama else del código sin especulación para el problema 13.
Aprovechando que la variable a va a ser igual a cero casi siempre, se podría cargar b especulativamente
antes de la instrucción de salto. De esta forma se evitaría una instrucción de salto y se adelantaría la
instrucción de carga, que además es independiente de la de salto. El código especulativo quedaría:
lw r3, a ; r3 = a
lw r4, b ; r4 = b
beqz r3, fin ; Si a es igual a 0, saltamos a fin para almacenar el resultado
addi r4, r3, #2 ; r4 = a + 2
fin: sw r4, a ; Almacenamos el resultado en a
Como se puede comprobar, se necesita realizar un renombrado del registro r3, utilizando r4, para
conservar el valor cargado de a en r3 cuando se ejecuta la carga de b. En este caso, la distribución de
operaciones en instrucciones VLIW podría ser la que muestra la Tabla 26.
En el caso de que a sea igual a cero, el código especulativo tardaría en ejecutarse 8 ciclos, como indica
la Tabla 27. Para cualquier otro valor de a, el código especulativo tardaría 9 ciclos en ejecutarse, como
indica la Tabla 28.
Tabla 27. Ejecución de la rama if del código con especulación para el problema 13.
Tabla 28. Ejecución de la rama else del código con especulación para el problema 13.
En los análisis realizados no se ha tenido en cuenta la posibilidad de que exista predicción de salto.
Asumiendo que p es la probabilidad de que la variable a sea igual a cero, el tiempo de ejecución del
código no especulativo generado para la sentencia if sería:
Tno_espec p 3p 9
S p
Tespec p 9 p
Como se asume que la variable a será casi siempre igual a cero, la ganancia tenderá a 1.5. De todas
formas, en el peor de los casos, en el que la variable a nunca tome el valor cero, el tiempo de ejecución
no empeoraría, por lo que esta optimización es aconsejable en cualquier caso, ya que en el peor caso
no empeora y en el resto consigue un tiempo de ejecución menor.
14. Suponga un procesador VLIW que puede codificar tres operaciones por instrucción con las
restricciones que indica la Tabla 29. El procesador puede utilizar predicados con cualquier
instrucción. Aunque en el caso de utilizarlos, una instrucción con la ALU o de acceso a
memoria incrementa en un ciclo su latencia. Esos predicados se establecen a partir de los
resultados de las instrucciones de comparación (cmp) con el formato (p) p1, p2 cmp.cnd rx, ry
donde cnd es la condición que se comprueba entre los registros rx y ry (lt, ge, eq, ne,…). Si la
condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La instrucción sólo se
ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción de comparación).
También existen instrucciones de comparación con el formato (p) p1 cmp.cnd rx, ry donde
p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una instrucción no tiene el
predicado (p), se entiende que se ejecuta siempre.
UNIDAD
LATENCIA OP1 OP2 OP3 OPERACIONES REALIZADAS
FUNCIONAL
Comparaciones, sumas y restas
ALU 1 X X X
con enteros y operaciones lógicas
Operaciones aritméticas con
FP 4 X
números en coma flotante
Memoria 3 X X Cargas y almacenamientos
Saltos condicionales e
Saltos 3 X
incondicionales
Si sabemos de antemano que el 75% de los elementos del vector a son nulos, muestre, sin
desenrollar el bucle, las formas de ejecutar el código:
Solución
Sin utilizar procesamiento especulativo ni predicados, el código máquina puede ser el siguiente:
Teniendo en cuenta el formato de las instrucciones VLIW, el código sería el que muestra la Tabla 30
(39% del slots vacíos).
Tabla 30. Código VLIW sin especulación y sin predicados para el problema 14.
En el caso de que se usen instrucciones con predicado, el código anterior quedaría así:
La Tabla 31 muestra el código VLIW de esta aproximación (con un 33% de slots vacíos).
ETIQ. OP1 OP2 OP3
sum lw r1, n add r2, r0, r0
bucle lw r3, a(r2)
p1, p2 cmp.eq r3, r0 subi r1, r1, #1 bnez r1, bucle
(p1) lw r4, b(r2) (p2) addi r4, r3, #1
sw r4, c(r2) addi r2, r2, #4
Tabla 31. Código VLIW sin especulación y con predicados para el problema 14.
Aprovechando que la mayoría de los elementos del vector a son nulos, podemos hacer uso del
siguiente código especulativo:
En este código se ha supuesto que el procesador incorpora una instrucción de carga especulativa (lw.s)
que se implementa mediante el uso de bits de veneno (poison bits). Si la carga especulativa fallara, se
envenenaría el registro r4 y cualquier otro registro que operara con él. Si al final se intentara almacenar
un registro envenenado, se atendería la excepción. En este caso, si fallara la carga especulativa de b y la
variable a fuera igual a cero, se atendería la excepción al intentar almacenar el registro r4. Sin embargo,
si fallara la carga especulativa de b, y a fuera distinta e cero, se volvería a fijar un valor nuevo para r4
(no envenenado), por lo que no sería necesario atender la excepción. La Tabla 32 muestra el código
VLIW para esta versión especulativa (con un 33% de slots vacíos).
Tabla 32. Código VLIW con especulación y sin predicados para el problema 14.
Por último, se pueden ahorrar la mitad de los saltos del programa si, además de especulación, se
utilizan instrucciones con predicado. En este caso, se obtendría el siguiente código. La Tabla 33
muestra su versión VLIW (con un 33% de slots vacíos):
Tabla 33. Código VLIW con especulación y con predicados para el problema 14.
Una vez obtenidos los códigos VLIW para cada una de las situaciones propuestas en el problema,
pasamos a estimar el tiempo de ejecución de cada una de ellas. Para el primer caso, tenemos que
evaluar los tiempos de ejecución del programa para las dos partes del if. Si a[i] es cero, se tendría la
ejecución mostrada en la Tabla 34, mientras que en cualquier otro caso, se obtendrían la que muestra
la Tabla 35. Asumiendo que la probabilidad de que a[i] = 0 es p = 0.75, el tiempo medio de ejecución
será:
Tabla 34. Ejecución del código de la Tabla 30 suponiendo que a[i] es cero.
Tabla 35. Ejecución del código de la Tabla 30 suponiendo que a[i] no es cero.
En el caso en el que se usen instrucciones con predicado para evitar el salto condicional, la ejecución
sería como indica la Tabla 36, así que el tiempo medio de ejecución será, independientemente de p:
T2 n 7n 1 ciclos
Para el código de la tercera opción, en el que se permite usar accesos especulativos a memoria, hay que
volver a tener en cuenta el tiempo de ejecución de las dos ramas del if. La Tabla 37 muestra la
ejecución en el caso de que a[i] sea cero y la Tabla 38 en el caso contrario. A partir de estas dos tablas,
y asumiendo que la probabilidad de que a[i] sea nulo es del 75%, el tiempo medio de ejecución será:
Como el código especulativo se ejecuta en la mayoría de los casos, se ha conseguido reducir el tiempo
medio de ejecución en más de dos ciclos por iteración respecto al tiempo original, aunque no se llega a
superar el tiempo alcanzado al usar instrucciones con predicado debido a la alta latencia de los saltos.
Tabla 37. Ejecución del código de la Tabla 32 suponiendo que a[i] es cero.
Tabla 38. Ejecución del código de la Tabla 32 suponiendo que a[i] no es cero.
Por último, analizaremos el tempo de ejecución del caso en el que se aplica tanto especulación como
instrucciones con predicado. La Tabla 39 muestra cómo se ejecutaría el código, tardando una media
de:
T4 n 7n 1 ciclos
Como se puede ver, la situación más favorable corresponde al uso de predicados. En este caso hemos
obtenido que T4(n) = T2(n) porque la latencia de la carga de b se ha solapado con la del salto del bucle.
En otros casos más complejos deberían obtenerse mejores resultados usando ambas técnicas
simultáneamente. Si no se usan instrucciones con predicado, el uso de cargas especulativas favorece
también el tiempo de ejecución, siempre que se especule sobre las instrucciones cuya ejecución sea
más probable.
15. El siguiente fragmento de código forma parte de una rutina que se ejecuta muy a menudo en
cierto procesador VLIW:
En este procesador se pueden predicar todas las operaciones. Para establecer los valores de
los predicados se utilizan instrucciones de comparación (cmp) con el formato
(p) p1, p2 cmp.cnd rx, ry donde cnd es la condición que se comprueba entre los registros rx y ry
(lt, ge, eq, ne,…). Si la condición es verdadera p1 = 1 y p2 = 0, y si es falsa, p1 = 0 y p2 = 1. La
instrucción sólo se ejecuta si el predicado p = 1 (habrá sido establecido por otra instrucción
de comparación). También existen instrucciones de comparación con el formato
(p) p1 cmp.cnd rx, ry donde p1 = 1 si la condición es verdadera y p1 = 0 si es falsa. Si una
instrucción no tiene el predicado (p), se entiende que se ejecuta siempre.
La arquitectura VLIW tiene dos slots, pero las instrucciones de comparación sólo pueden
colocarse en el primero de ellos. Las latencias de las operaciones son de 1 ciclo para las
sumas, restas y comparaciones, de dos ciclos para las multiplicaciones y de cinco ciclos para
las cargas de memoria. Debido a esta alta latencia en el acceso a memoria, la arquitectura
incorpora una instrucción de carga especulativa de memoria, lw.s rx, desp(ry), repar, que
permite cargar un dato anticipadamente. Para dar soporte a esta instrucción, los registros del
procesador cuentan con un bit de veneno adicional para marcar aquellos resultados en los
que la especulación haya provocado una excepción. La aplicación de cualquier operación con
un operando envenenado provocará que se envenene el resultado, y la excepción sólo se atenderá
si se intenta almacenar un resultado envenenado. En dicho caso, tras atender la excepción se
ejecutará el código de reparación indicado con el puntero repar.
Solución
Lo primero que vamos a hacer es escribir el programa usando predicados:
#. OP1 OP2
1 mult r1, r2, r3
2
3 add r4, r5, r1
4 p1 cmp.ne r4, r0 sub r8, r1, r4
5 (p1) lw r6, dato
6
7
8
9
10 (p1) add r7, r4, r6
11 (p1) mult r2, r1, r7
12
13 mult r1, r8, r2
14
15 sw resul, r1
Sería interesante que la carga se realizara al principio del programa para tratar de ocultar esta latencia.
Como está vigilada por p1, se debe hacer especulativamente para ignorar las posibles excepciones que
puedan ocurrir en su procesamiento, ya que si al calcular r4 toma el valor 0, la carga no debería
haberse realizado y por tanto, no debería haber ocurrido ninguna excepción. El código para adelantar
la carga es el siguiente:
lw.s r6, dato, repar
mult r1, r2, r3
add r4, r5, r1
p1 cmp.ne r4, r0
(p1) add r7, r4, r6
(p1) mult r2, r1, r7
sub r8, r1, r4
mult r1, r8, r2
sw resul, r1
Y una vez colocado en forma de instrucciones VLIW, la Tabla 41 muestra que tarda 4 ciclos menos en
ejecutarse, siempre que no falle la carga especulativa, o que no se use el valor envenenado en caso de
que falle (si p1 toma el valor 0):
#. OP1 OP2
1 mult r1, r2, r3 lw.s r6, dato, repar
2
3 add r4, r5, r1
4 p1 cmp.ne r4, r0 sub r8, r1, r4
5
6 (p1) add r7, r4, r6
7 (p1) mult r2, r1, r7
8
9 mult r1, r8, r2
10
11 sw resul, r1
La Tabla 42 muestra el código VLIW para la rutina de reparación. Como se puede observar, la rutina
de reparación tarda en ejecutarse otros 11 ciclos debido a la latencia de la carga de memoria, por lo
que cuando haya un fallo de página el programa tardará en ejecutarse 22 ciclos, que es más tiempo del
que tardaba sin aplicar la optimización. Por tanto, esta optimización debería aplicarse sólo si la
probabilidad de que se produzca un fallo de página al cargar el dato es baja, o bien si la probabilidad
de que r4 tome el valor 0 es pequeña. Estas probabilidades se pueden estimar realizando algunas
ejecuciones del programa sin optimizar y analizando su comportamiento.
#. OP1 OP2
1 lw r6, dato
2
3
4
5
6 add r7, r4, r6
7 mult r2, r1, r7
8
9 mult r1, r8, r2
10
11 sw resul, r1
Tabla 42. Rutina de reparación para el código VLIW con especulación para el problema 15.