Está en la página 1de 108

TEMA 1 PROCESADORES SEGMENTADOS

1.1 PROCESADORES RISC FRENTE A PROCESADORES CISC


- CISC (Complex Instruction Set Computer): Los computadores no disponían de grandes cantidades de memoria.
Por esa razón, los diseñadores comenzaron a condensar la mayor cantidad posible de operaciones dentro del
microcódigo reduciendo el tamaño de los programas, que fueron dotados de conjuntos de instrucciones muy potentes
y realizaban gran cantidad de operaciones.
Estas instrucciones eran decodificadas en la unidad de control y ejecutadas mediante una serie de microinstrucciones
almacenadas en la memoria de control. Para este proceso se requerían varios ciclos de reloj, de al menos uno por
instrucción.
Con el afán de disminuir la cantidad de instrucciones que un programa debía almacenar, se introdujeron una gran
cantidad de modos de direccionamiento. Esto fue debido porque antiguamente el tiempo de acceso a memoria para
buscar operandos era la mejor alternativa.
Esta arquitectura dificultaba el paralelismo a nivel de instrucciones por la gran cantidad de dependencias entres la
microoperaciones pertenecientes a una misma instrucción.

- RISC (Reduced Instruction Set Computer): El avance de la tecnología de fabricación de los circuitos integrados
provoco que el aumento de la velocidad de los procesadores pasase a ser mucho mayor que el de las memorias, con
lo que el objetivo de diseño de esta arquitectura pasó a ser el de disminuir la cantidad de accesos a memoria tanto
como fuese posible mediante un aumento en la cantidad de registros internos en el procesador.
La decodificación de las instrucciones se implementa por hardware dentro de la CPU, disminuyendo la lógica de
control y obteniéndose una mayor velocidad de ejecución.
Esta arquitectura propone un conjunto de instrucciones sencillas con formatos fijos, permitiendo la posibilidad de
introducir la ejecución segmentada de instrucciones (pipeline) y el diseño de procesadores cableados con un
repertorio reducido de instrucciones sencillas, pero frecuentemente utilizadas. Las principales ventajas son:

- Reduce el tamaño de la CPU.


- Se facilita el diseño.
- Permite máquinas más compactas y con menos consumo.
- Posibilita la segmentación y el paralelismo en la ejecución de instrucciones.
- Reduce los accesos a memoria

El rendimiento de la implementación pipeline depende de los riesgos que se generan durante la ejecución de las
instrucciones, considerando que un riesgo aparece cuando dos o más instrucciones entran en conflicto.
Todas las operaciones complejas que aparecen en el código fuente se descomponen en multitud de operaciones
sencillas. Es decir, se hace un gran esfuerzo para mantener al hardware tan simple como sea posible, aun a costa de
hacer al compilador considerablemente más complejo.
Se obtiene una ganancia de velocidad por el hecho de que dominan las instrucciones más frecuentemente utilizadas,
mientras que las menos frecuentes deben descomponerse en operaciones simples

Hay que tener en cuenta la compatibilidad con el software existente en el momento de tomar la decisión de migrar a
RISC. Los atributos complejo y reducido describen diferencias entre los dos modelos de arquitectura para los
procesadores y expresan muy bien una característica importante siempre que no se tomen solo como referencia los
conjuntos de instrucciones, sino que se considere también la complejidad del hardware de los procesadores.
Un procesador RISD tiene una capacidad de procesamiento mayor que la del CISC con una estructura de hardware
mucho más simple.

1.2 CLASIFICACIÓN DE LAS ARQUITECTURAS PARALELAS


El aumento de las prestaciones viene dado por el paralelismo y la utilización de los principios de localidad espacial
y temporal. Gracias a ellas se reducen los accesos a niveles más lentos de la jerarquía de memoria y se evitan
colisiones en los elementos compartidos, pero a su vez ocasionan problemas de coherencia a los que hay que dedicar
recursos.
El paralelismo se implementa siguiendo dos líneas:

- Replicación de elementos: incluye unidades funcionales entre los que se distribuye el trabajo.

- Segmentación de cauce (pipelining): técnica a través de la cual un elemento se divide en una serie de etapas
que funcionan de forma independiente. Esta técnica se aplica al diseño de procesadores, permitiendo un mayor
aprovechamiento del paralelismo entre instrucciones en los procesadores superescalares y VLIW (Very Long
Instruction Word), y en el diseño de las unidades funcionales incluidas en los procesadores. Por ejemplo, en los
procesadores vectoriales donde lo que prima es aprovechar el paralelismo de datos al ejecutar una operación
sobre conjuntos de operandos diferentes.

La taxonomía de Flynn divide el universo de computadores en 4 clases, según el número de secuencias o flujos de
datos que pueden procesarse simultáneamente y está relacionada con el aprovechamiento del paralelismo a partir de
la réplica de elementos. Los 4 computadores que se distinguen son:

- SISD.
Un único flujo de instrucciones (Single Instruction) procesa
operandos y genera resultados, definiendo un único flujo de
datos (Single Data).
Existe una única unidad de control (UC) que recibe las
instrucciones de memoria, las decodifica y genera los códigos
que definen la operación correspondiente a cada instrucción que debe realizar la unidad de procesamiento
(UP).

- SIMD.
Un único flujo de instrucciones (SI) procesa operandos y
genera resultados, definiendo varios flujos de datos
(Multiple Data).
El código de operación actúa sobre varias unidades de
procesamiento (UPi). De esta forma puede realizar varias
operaciones similares simultaneas sobre operandos
distintos. Cada una de las secuencias y los resultados define
un flujo de datos diferente.

- MIMD.
El computador ejecuta varias secuencias o flujos distintos
de instrucciones (Multiple Instructions) y cada uno de ellos
procesa operandos y genera resultados de forma que
existen también varios flujos de datos (MD).
Existen varias unidades de control que decodifican las
instrucciones correspondientes a distintos programas. Cada
uno de esos programas procesa conjuntos de datos
diferentes, que definen distintos flujos de datos.
- MISD.
Se ejecutan varios flujos de instrucciones (MI) aunque
todos actúan sobre el mismo flujo de datos (SD).
Constituyen una clase de computadores cuyo
comportamiento se puede replicar con un MIMD en el
que sus procesadores se sincronizan para los datos
vayan pasando desde un procesador a otro.
Si esta forma fuese necesaria para algún problema, se
puede implementar en un computador MIMD.

Ejemplo: La mejora de prestaciones y el paralelismo que aprovechan las arquitecturas SIMD y MIMD se pueden
entender mejor estimando los tiempos de procesamiento que se tendrían en la ejecución de un fragmento de código.

- SISD: se necesitan 12 intervalos, 1 por cada operación.

- SIMD:

En este caso se necesitaría 3 intervalos de tiempo, 4 meces menos que en un SISD debido a que se están utilizando
4 unidades de procesamiento.

- MIMD:

Se consideran 3 procesadores, y cada uno ejecuta una operación distinta sobre 4 componentes de los vectores. En
este caso, el coste temporal seria de 4 intervalos, es decir, 3 veces menos que en un SISD.
La taxonomía de Flynn pone de manifiesto 2 tipos de paralelismo:

- Paralelismo de datos: Se explota cuando una misma función se ejecuta repetidas veces en paralelo sobre datos
diferentes. Se explota en las SIMD, aunque también se puede considerar que se aprovecha en las MIMD.

- Paralelismo funcional: Se aprovecha cuando las funciones que intervienen en la aplicación se ejecutan en
paralelo.
Existen diversos niveles en los que se puede presentar:

- Nivel de instrucciones u operaciones: Cuando se ejecutan en paralelo las instrucciones de un programa.

- Nivel de bucle: Cuando se ejecutan en paralelo distintas iteraciones de un bucle o secuencias de


instrucciones de un programa.

- Nivel de funciones: Cuando distintos procedimientos que constituyen un programa se ejecutan


simultáneamente.

- Nivel de programas: Cuando la plataforma ejecuta en paralelo programas diferentes que pueden
corresponder, o no, a una misma aplicación.

En la taxonomía de Flynn solo las arquitecturas MIMD y MISD pueden implementar paralelismo funcional pero
no a nivel de instrucciones.

- Procesadores segmentados:
Al disponer de una microarquitectura que
implementa las distintas fases o etapas de una
instrucción en elementos de circuito que pueden
trabajar de forma independiente, permiten que se
procesen simultáneamente varias instrucciones.

- Procesadores superescalares:
Implementan microarquitecturas basadas en
segmentaciones más complejas en cuyas etapas se puede
procesar más de una instrucción y reducir el número de
ciclos por instrucción.

- Procesadores supersegmentados:
Tratan de aprovechar la mayor velocidad de los circuitos,
diseñando segmentaciones con más etapas, donde cada
una de ellas necesita un tiempo menos y, por tanto, puede
funcionar a frecuencias mayores.
1.3 EVALUACIÓN Y MEJORA DEL RENDIMIENTO DE UN COMPUTADOR
Para la evaluación del rendimiento se mencionan las siguientes medidas:

- Tiempo de respuesta: Tiempo que tarda el computador en procesar una entrada.


- Productividad: Número de entradas procesadas por unidad de tiempo.
- Funcionalidad: Tipos de entradas diferentes que es capaz de procesar.
- Expansibilidad: Posibilidad de ampliar la capacidad de procesamiento añadiendo bloques a la arquitectura.
- Escalabilidad: Posibilidad de ampliar el sistema sin que suponga una devaluación de las prestaciones.
- Eficiencia: Coste entre el rendimiento obtenido y el coste que ha supuesto.

Para un procesador, en el que las entradas son las instrucciones, las medidas de rendimiento que se utilizan son:

- Ciclos que tarda en ejecutarse cada tipo de instrucción (CPIi).


- El valor medio del número de ciclos que tarda en ejecutarse una instrucción (CPI).
- La frecuencia de reloj o tiempo de ciclo (f / Tciclo).
- Instrucciones que procesa (NI).

𝐶𝑃𝐼
𝑇𝐶𝑃𝑈 = 𝑁𝐼 ⋅ 𝐶𝑃𝐼 ⋅ 𝑇𝐶𝐼𝐶𝐿𝑂 = 𝑁𝐼 ⋅ ( )
𝑓
Donde el valor de CPI se puede expresar como:

𝐶𝑖𝑐𝑙𝑜𝑠 𝑑𝑒 𝑟𝑒𝑙𝑜𝑗 𝑑𝑒𝑙 𝑝𝑟𝑜𝑔𝑟𝑎𝑚𝑎 ∑𝑛𝑖=1 𝑁𝐼𝑖 ⋅ 𝐶𝑃𝐼𝑖


𝐶𝑃𝐼 = =
𝑁𝐼 𝑁𝐼

La expresión para el cálculo de 𝑇𝐶𝑃𝑈 se puede utilizar para reflejar las características de los procesadores en
relación con el aprovechamiento del paralelismo entre instrucciones.

𝐶𝑃𝐸
𝑇𝐶𝑃𝑈 = 𝑁𝐼 ⋅ ( ) ⋅ 𝑇𝐶𝐼𝐶𝐿𝑂
𝐼𝑃𝐸

Donde CPE es el número medio de ciclos entre inicios de ejecución de instrucciones, e IPE el número medio de
instrucciones que se emiten.
La expresión del tiempo de CPU también se puede expresar como:

𝑁𝑜𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠
𝑇𝐶𝑃𝑈 = ( ) ⋅ 𝐶𝑃𝐼 ⋅ 𝑇𝐶𝐼𝐶𝐿𝑂
𝑂𝑝𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛

Donde 𝑁𝑜𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 es el número de operaciones que realiza el programa y 𝑂𝑝𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛 es el número medio
de operaciones que puede codificar una instrucción.

- Para un procesador no segmentado en el que todas las instrucciones duran 5 ciclos:

El número de ciclos entre etapas de emisión es CPE = 5 y dado que cada vez se emite una instrucción entonces
IPE = 1, CPI = 5 y 𝑂𝑝𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛 = 1.
- Procesador segmentado trabajando a pleno rendimiento:

CPE = 1, IPE = 1 y 𝑂𝑝𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛 = 1 por lo que CPI = 1.

- Procesador superescalar:

CPE = 1, IPE = 2 y 𝑂𝑝𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛 = 1 por lo que CPI = 0.5.

- Procesador VLIW:

CPE = 1, IPE = 1 y 𝑂𝑝𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛 = 2 por lo que CPI = 0.5.

Existen medidas que pretenden caracterizar el rendimiento de un computador y facilitar la comparación entre
distintos sistemas.

- MIPS (Millones de instrucciones por segundo):

𝑁𝐼 𝑓
𝑀𝐼𝑃𝑆 = 6
=
𝑇𝐶𝑃𝑈 · 10 𝐶𝑃𝐼 · 106

Esta medida puede variar según el programa, por lo que no sirve como medida característica de una
máquina. Puede ser inversamente proporcional al rendimiento. Si dos programas tardan igual, pero uno
utiliza más instrucciones que otro, el que utiliza más instrucciones tendrá más MIPS, indicando
erróneamente que proporciona mayor rendimiento.

- MFLOPS (Millones de operaciones en coma flotante por segundo):

𝑂𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑒𝑛 𝑐𝑜𝑚𝑎 𝑓𝑙𝑜𝑡𝑎𝑛𝑡𝑒


𝑀𝐹𝐿𝑂𝑃𝑆 =
𝑇𝐶𝑃𝑈

No es una medida adecuada para todos los programas ya que solo se tiene en cuanta las operaciones en
coma flotante.
Cuando en un computador se pretende incrementar su rendimiento mejorando alguno de sus recursos o elementos,
se puede utilizar la ganancia (speedup) para evaluar hasta que punto la mejora de rendimiento es un factor igual a p
en ese elemento se manifiesta en el rendimiento global del computador.

𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜(𝑝) 𝑇𝐶𝑃𝑈_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙
𝑆𝑝 = =
𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 𝑇𝐶𝑃𝑈_𝑚𝑒𝑗𝑜𝑟𝑎𝑑𝑎

Donde 𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜(𝑝) representa el rendimiento del programa prueba en la maquina mejorada, en la que uno de
sus recursos se ha mejorado en un factor p y 𝑟𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜_𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 es el rendimiento del programa prueba en la
maquina original.

Hasta que punto una mejor en el factor p en un recurso se pone de manifiesto en la mejora final obtenida. Para dar
respuesta se recurre a la ley de Amdahl.

𝑝
𝑆𝑝 ≤
1 + 𝑓(𝑝 − 1)

Donde 𝑓 es la fracción de tiempo de ejecución en la maquina original en la que no se puede aplicar la mejora
considerada. Asi solo si 𝑓 = 0 (la mejora se usa siempre), una mejora en un recurso en un factor p se observa en esa
misma medida en la máquina.
La conclusión de la ley de Amdahl es que para mejorar una maquina habría que aplicar el máximo esfuerzo a la
mejora de aquel recurso que se utilice con mayor frecuencia.

1.4 CARACTERÍSTICAS DE LOS PROCESADORES SEGMENTADOS


La segmentación (pipelining) es una técnica empleada en el diseño de procesadores que trata de explotar el
paralelismo intrínseco que existe entre las instrucciones de un flujo secuencial.
Lo característico de la segmentación es que diferentes subtareas pueden procesarse de forma simultánea, aunque sea
sobre diferentes datos, siendo el factor clave la posibilidad de comenzar una nueva tarea sin necesidad que la anterior
se haya terminado. Por esta razón, la medida de la eficacia de un procesador segmentado es el tiempo máximo que
pasa entre la finalización de dos tareas consecutivas.

Al número de etapas del procesador, n, se le denomina profundidad de la segmentación.


Para que el tiempo de latencia sea el menor posible, es necesario que el procesador este equilibrado, es decir, que
todas las subtareas en que se haya dividido la tarea total tarden en procesarse el mismo tiempo.
La relación de precedencia de un conjunto de subtareas 𝑇1 , … , 𝑇𝑛 que componen cierta tarea T, especifica para cada
subtarea Tj que no puede comenzarse hasta que hayan terminado ciertas subtareas Ti.
1.5 ARQUITECTURA SEGMENTADA GENÉRICA
Es del tipo de registros de propósito general donde los operandos se referencian explícitamente.
Las ventajas surgen del uso efectivo de los registros por parte del compilador al calcular expresiones aritmético-
lógicas y al almacenar datos. Además, cuando los datos están almacenados en registros se reduce el tráfico de
memoria, se acelera la ejecución del programa y se mejora la densidad de código.
Existen dos características importantes que permiten clasificar las arquitecturas de propósito general:

- El número de operandos que pueden tener las operaciones aritmético-lógicas.


- El número de operandos que se pueden direccionar en memoria en las instrucciones aritmético-lógicas.

Las instrucciones aritmético-lógicas utilizan en total tres operandos y ninguno de ellos se referencia en memoria por
lo que se les denomina maquinas registro-registro o máquinas de carga/almacenamiento.

Otro factor a tener en cuenta es el modo de direccionamiento. Como en la ASG el modo de direccionamiento es con
desplazamiento, la dirección efectiva (DE) se calcula sumando al contenido de un registro el operando OP declarado
en la instrucción, que se interpreta como un desplazamiento respecto al contenido del registro.

1.5.1 Repertorio de instrucciones de la ASG

La ASG tiene un total de 32 registros genéricos de 32 bits identificados como R0, R1, R2, …, R31. Y R0 siempre
contiene el valor 0.
Los registros de coma flotante de 64 bits se identifican como F0, F2, F4, …, F30. La longitud de todas las
instrucciones de la ASG es de 32 bits. Los cuatro tipos básico de instrucciones son:

- Aritméticas y lógicas.
- Transferencia de datos.
- Bifurcaciones o saltos incondicionales.
- Saltos condicionales.

Con el siguiente formato:


Donde CO representa el código de la operación que se realiza sobre OP1 y OP2; el resultado se almacena en
OPd.

El reportorio de instrucciones considerado es el siguiente:

- Bifurcación y salto
Instrucción ejemplo Nombre de la instrucción Significado
JUMP etiqueta Bifurcación PC ← etiqueta
JUMP R4 Bifurcación a registro PC ← R4
BEQZ R3, etiqueta Salto igual a 0 If (R3 = 0) PC ← PC + etiqueta
BNEZ R2, etiqueta Salto distinto de 0 If (R2 ≠ 0) PC ← PC + etiqueta
BGT R5, etiqueta Salto mayor que 0 If (R5 > 0) PC ← PC + etiqueta
BLT R7, etiqueta Salto menor que 0 If (R7 < 0) PC ← PC + etiqueta
- Aritméticas
Instrucción ejemplo Nombre de la instruccion Significado
ADD R3, R1, R2 Suma R3 ← R1 + R2
ADDI R2, R1, #4 Suma inmediata R2 ← R1 + 4
ADDD F2, F4, F6 Suma flotante F2 ← F4 + F6
ADDDI F8, F2, #4 Suma inmediata flotante F8 ← F2 + 4
SUB R5, R2, R3 Resta R5 ← R2 - R3
SUBI R4, R1, #8 Resta inmediata R4 ← R1 - 8
SUBD F4, F6, F8 Resta flotante F4 ← F6 - F8
SUBDI F2, F4, #8 Resta inmediata flotante F2 ← F4 – 8
MULT R3, R6, R5 Multiplicacion R3 ← R6 · R5
MULTI R6, R1, #5 Multiplicacion inmediata R6 ← R1 · 5
MULTD F4, F6, F8 Multiplicacion flotante F4 ← F6 · F8
MULTDI F6, F2, #5 Multiplicacion inmediata flotante F6 ← F2 · 5
DIV R7, R3, R5 Division R7 ← R3/R5
DIVI R8, R1, #3 Division inmediata R8 ← R1/3
DIVD F2, F6, F8 Division flotante F2 ← F6/F8
DIVDI F8, F4, #3 Division inmediata flotante F8 ← F4/3
If R6 > R5 R3 ← 1
SGT R3, R6, R5 Mayor que
Else R3 ← 0
If R1 > 5 R6 ← 1
SGTI R6, R1, #5 Mayor que inmediata
Else R6 ← 0
If R6 ≥ R8 R4 ← 1
SGE R4, R6, R8 Mayor o igual que
Else R4 ← 0
If R2 ≥ 5 R6 ← 1
SGEI R6, R2, #5 Mayor o igual que inmediata
Else R6 ← 0
If R6 < R5 R3 ← 1
SLT R3, R6, R5 Menor que
Else R3 ← 0
If R1 < 5 R6 ← 1
SLTI R6, R1, #5 Menor que inmediata
Else R3 ← 0
If R6 ≤ R8 R4 ← 1
SLE R4, R6, R8 Menor o igual que
Else R4 ← 0
If R2 ≤ 5 R6 ← 1
SLEI R6, R2, #5 Menor o igual que inmediata
Else R6 ← 0

- Carga y almacenamiento.
Instrucción ejemplo Nombre de la instrucción Significado
LD R1, 4(R2) Carga R1 ← M[R2 + 4]
SD 0(R3), R5 Almacenamiento M[R3 + 0] ← R5
LD F6, 5(R3) Carga flotante F6 ← M[R3 + 5]
SD 6(R1), F4 Almacenamiento flotante M[R1 + 6] ← F4

1.5.2 Implementación de la segmentación de instrucciones en la ASG

En el diseño de un procesador segmentado, el cálculo a segmentar es el trabajo que es necesario realizar en cada
ciclo de instrucción, que es el número de ciclos de reloj que consume su procesamiento.
En la ASG un ciclo de una instrucción se descompone en cinco etapas:
- IF (Instruction Fetch): Lectura de la instrucción de la cache de instrucciones.

- ID (Instruction Decoding): Decodificación de la instrucción y lectura de sus operandos del fichero de


registros.

- EX (Execution): Ejecución de las operaciones si se trata de una instrucción aritmético-lógica y del cálculo
de la condición y dirección de salto si se trata de un bifurcación o salto.

- MEM (Memory Access): Acceso a la caché de datos para lecturas o escrituras.

- WB (Write-Back results): Escritura del resultado en el fichero de registros.

En la segmentación de la ASG, el ciclo de instrucción lógico se ha asignado a cinco ciclos de maquina físicos
Resumiendo, la segmentación de la ASG da como patrón de ejecución el siguiente:

Aunque la segmentación incrementa la productividad de la CPU, no reduce el tiempo de ejecución de una


instrucción individual. Por el contrario, el tiempo total de ejecución de la instrucción segmentada es ligeramente
superior al de su equivalente no segmentada debido al tiempo que se consume en el control de la segmentación.
Este tiempo viene determinado por varios factores:

- Los cerrojos o buffers de contención que hay que colocar en el camino de datos del procesador con el
objeto de aislar la información entre etapas.

- La duración de todas las etapas de la segmentación es similar y viene determinada por la duración de la
etapa más lenta.

- Los riesgos que se producen en la segmentación y que introducen detenciones en el cauce.

1.6 RIESGOS EN LA SEGMENTACIÓN


Son consecuencia tanto de la organización de la segmentación como de las dependencias entre las instrucciones. La
dependencia entre ellas provoca que la instrucción que sucede a aquella con la cual posee dependencias no pueda
ejecutarse en el ciclo de reloj que le corresponde, ya que ha de esperar algún resultado para poder efectuar su
ejecución.
Las causas de los riesgos pueden ser varias:

- Riesgos estructurales: Surgen de conflictos por los recursos, es decir, por insuficiencia del hardware.
- Riesgos por dependencia de datos: Surgen cuando una instrucción necesita los datos de otra anterior.
- Riesgos de control: Se originan debido a que no se puede leer la instrucción siguiente hasta que no se conozca
su dirección.
1.6.1 Riesgos estructurales

Si alguna combinación de instrucciones no se puede realizar debido a conflictos por los recursos.
Se considera una maquina segmentada que comparte un único puerto de memoria para datos e instrucciones. En
este caso, cuando una instrucción contenga une referencia a la cache de datos, la segmentación debe detenerse
durante un ciclo de reloj. La máquina no puede buscar la siguiente instrucción debido a que la referencia al dato
está utilizando el puerto de memoria. Por tanto, el inicio de una nueva instrucción se tendrá que retrasar un ciclo
de reloj.

Es importante observar que este problema surge en los sistemas con una única memoria cache para instrucciones
y datos. En el caso de la ASG no se presenta este inconveniente porque se dispone de dos memorias cache, una
para instrucciones y otra para datos.

Otras situaciones en las que pueden aparecer riesgos estructurales son:

- No todas las etapas de la segmentación tienen la misma duración. En este caso alguna etapa de menor
duración debería esperar a que termine la que sea más larga.

- Hay instrucciones más complejas que otras. Hay instrucciones que pueden necesitar más tiempo de CPU
en la etapa EX. Por ejemplo, las instrucciones de carga y almacenamiento requieren menos trabajo de ALU
que una división en coma flotante.

Ejemplo: Se considera el siguiente fragmento de código:


Donde todas las instrucciones necesitan un único ciclo de reloj en la etapa EX salvo las instrucciones de
multiplicación y división que necesitan 2 ciclos. Además, se supone que hay un puerto de escritura para
almacenar los datos en memoria. Al existir solo una única unidad funcional aritmética, hasta que no se consumen
los dos ciclos que necesita la instrucción de multiplicar en su etapa EX no se puede iniciar la etapa EX de una
nueva instrucción.

En este caso se pueden plantear 2 alternativas:

- Segmentar o duplicar la unidad funcional aritmética de tal forma que se puedan solapar dos etapas EX de
dos instrucciones en el mismo ciclo de reloj.

- Separar, si se puede, las dos instrucciones de multiplicar (siendo ahora el orden de ejecución de las
instrucciones i1-i2-i4-i3)

1.6.2 Riesgos por dependencias de datos

Surgen cuando dos instrucciones comparten algún dato. La situación es la siguiente: Una instrucción ij actualiza
el valor de un registro, pero una instrucción posterior ik accede a ese registro antes de ij haya terminado la
operación.
Los riesgos por dependencias de datos se clasifican en:

- Riesgo RAW (Read after Write) o dependencia verdadera: Se produce cuando la instrucción j intenta leer
un dato antes de que la instrucción i lo escriba.
- Riesgo WAR (Write after Read) o antidependencia: Se produce cuando la instrucción j trata de escribir
en su destino antes de que este sea leído por la instrucción i.

- Riesgo WAW (Write after Write) o dependencia de salida: Se produce cuando la instrucción j intenta
escribir un operando antes de que este sea escrito por la instrucción i.

- Riesgo RAR (Read after Read): No es un riesgo. Esta situación se da cuando dos instrucciones necesitasen
leer un dato desde el mismo registro. En ASG, ambas instrucciones leerán el mismo registro sin problemas
en la etapa de decodificación de la instrucción.

En el caso de ASG únicamente se puede presentar el riesgo RAW y existen varias alternativas para evitar los
problemas que ocasionan estas dependencias:

- La reorganización de código.
- El interbloqueo entre etapas.
- El adelantamiento.

1.6.2.1 La reorganización de código.

Consiste en disponer las instrucciones en el programa de forma que entre instrucciones con dependencias
de tipo RAW existan instrucciones que permitan retrasar la segunda instrucción con respecto a la primera,
dando tiempo a que la primera haya escrito su resultado antes de que la lectura ocasionada por la segunda
tenga lugar.

Ejemplo:

Para evitar la dependencia habría que desplazar (retrasar) i4 3 ciclos.


Si el código se reordena, se pueden retrasar estos 3 ciclos sin necesidad de esperar.

Si se tiene en cuenta que al fichero de registros se accede dos veces en un ciclo, es posible realizar las
escrituras en el flanco de subida (en la primera mitad de WB) y las lecturas en el flanco de bajada (en la
segunda mitad de ID).

En el caso de que el compilador no pueda reorganizar el código para encontrar las k instrucciones que se
tienen que desviar sin modificar la lógica del programa, debe inserta instrucciones NOP entre las
instrucciones que tengan dependencias de datos.

Ejemplo:
La ventaja de la solución de la reorganización de código es que no se requiere un hardware adicional, pero
se necesita un compilador más complejo y una pérdida de tiempo si es necesario insertar instrucciones NOP
cuando no se puede reordenar el programa para insertar instrucciones útiles.

1.6.2.2 El interbloqueo entre etapas

Se introducen elementos hardware en el cauce para detectar la existencia de dependencias. En el caso de


que se detecte una, la instrucción que debe leer el resultado proporcionado por la primera se detiene el
número de ciclos necesario.

Ejemplo:

1.6.2.3 El adelantamiento (caminos de bypass o forwarding)

Mediante esta técnica se pueden aprovechar los elementos que en la técnica de interbloqueo permiten
detectar la existencia de dependencias entre instrucciones. Sin embargo, esta información ahora se
aprovecha para habilitar una serie de caminos (buses) que se añaden al cauce para permitir que los
resultados de una etapa pasen como entradas a la etapa donde son necesarios en caso de dependencias
RAW, al mismo tiempo que siguen su camino para almacenarse en el fichero de registros.
El cambio en el hardware permite adelantar el resultado obtenido al final de la etapa EX, teniendo el
resultado listo esta parte en lugar de en la etapa WB.

Ejemplo:
1.6.3 Riesgos de control

Cuando se ejecuta un salto condicional, el valor del contador de programa puede incrementarse automáticamente
o cambiar su valor en función de que el salto sea efectivo o no efectivo. En la ASG, si la instrucción i es un salto
efectivo entonces el PC no se actualiza hasta el final de la etapa MEM de la segmentación, una vez que se haya
verificado la condición y calculado la nueva dirección del PC en la etapa EX.

El esquema más fácil de implementar en la ASG es detener la segmentación hasta que se conoce el resultado
del salto, introduciendo instrucciones NOP después de cada instrucción.

Otra alternativa es dejar que las instrucciones que se han captado prosigan su ejecución en el cauce. En este
caso, el compilador debería introducir en esos huecos instrucciones que se tengan que ejecutar antes de la
instrucción destino de salto de forma que su efecto sea independiente de que el salto sea efectivo o no.

Un esquema mejor y solo ligeramente más complejo es predecir el salto como no efectivo, permitiendo que el
hardware continúe procesando la siguiente instrucción en la secuencia del programa como si el salto fuese
efectivo. En el caso de que el salto sea efectivo será necesario detener la segmentación y recomenzar la búsqueda
de la instrucción destino del salto.
1.7 PLANIFICACIÓN DINÁMICA: ALGORITMO DE TOMASULO
El hardware reorganiza la ejecución de la instrucción para reducir las detenciones mientras mantiene el flujo de datos
y la consistencia. Entre las ventajas está el aprovechamiento más optimo en tiempo real de una etapa EX con
múltiples unidades funcionales. Sin embargo, estas ventajas se consiguen c a costa de un importante aumento de la
complejidad del hardware. Las instrucciones se ejecutan tan pronto como sus operandos estén disponibles.

Para permitir la ejecución fuera de orden se desdobla la etapa ID en:

- Decodificación (ID): Decodificación de instrucciones y comprobación de riesgos estructurales.

- Emisión (II): La instrucción espera hasta que no haya riesgos de tipo RAW, cuando están listos los operandos
fuente, se leen y se emite la instrucción hacia la unidad funcional.

Ejemplo:

Asi sería la ejecución sin planificación, pero con desdoblamiento de la etapa ID seria asi:
Ejemplo: Ejecutar el siguiente código siguiendo el algoritmo de Tomasulo:

Con las siguientes hipótesis de salida:

- La FLOS distribuye hasta 2 instrucciones en cada ciclo según el orden del programa.

- Una instrucción puede comenzar su ejecución en el mismo ciclo en que se distribuye a una estación de reserva.

- La suma tiene una latencia de 2 ciclos y la de multiplicación de 3 ciclos.

- Se permite que una instrucción reenvíe su resultado a instrucciones dependientes durante su último ciclo de
ejecución. De esta forma, una instrucción a la espera de un resultado puede comenzar su ejecución en el siguiente
ciclo si detecta una coincidencia.

- Los valores de etiqueta 01, 02 y 03 se utilizan para identificar las 3 estaciones de reserva de la unidad funcional
de suma, mientras que 04 y 05 se utilizan para identificar las dos estaciones de la unidad de
multiplicación/división. Estos valores de etiqueta son los ID de las estaciones de reserva.

- Inicialmente el valor de los registros es: F0 = 6.0, F2 = 3.5, F4 = 10.0 y F6 = 7.8.


TEMA 2. PROCESADORES SUPERESCALARES
2.1 CARACTERÍSTICAS DE LOS PROCESADORES SUPERESCALARES
La diferencia más destacada entre una segmentación superescalar con respecto a una clásica es que varias
instrucciones avanzan simultáneamente por las etapas de la segmentación. Una característica fundamental es la
ejecución de instrucciones en un orden diferente al que se especifica el programa. Se caracterizan por 3 atributos:

- Paralelismo: Se dan simultáneamente dos tipos de paralelismo:

- Temporal: Es una consecuencia directa de ser procesadores segmentados.

- Espacial: Surge como consecuencia de la replicación el hardware que permite que varias instrucciones se
estén procesando en la misma etapa. El nivel del paralelismo espacial se especifica con el termino ancho o
grado de la segmentación, que es el número de instrucciones que se pueden procesar simultáneamente.

- Diversificación: Debido a que las diferentes instrucciones implican diferentes operaciones, disponer de un
único tipo de cauce es ineficiente. Hay instrucciones que no necesitan pasar por todas las etapas (por ejemplo,
las aritméticas en una máquina de carga/almacenamiento no necesitan acceder a memoria). Por esta razón, estos
procesadores incluyen en la etapa EX múltiples unidades funcionales diferentes e independientes.

- Dinamismo: Permite la ejecución de instrucciones fuera de orden. Las instrucciones se leen, decodifican,
distribuyen y terminan en el orden secuencial que aparecen en el programa, pero se pueden emitir y finalizar en
las unidades funcionales en un orden diferente establecido por el programa.

Una segmentación dinámica paralela utiliza buffers multientrada que permiten que las instrucciones entren
y salgan de los buffers fuera de orden.
Las ventajas que aporta la ejecución fuera de orden son:

- Unas instrucciones pueden adelantar a otras si no hay dependencias falsas, evitando ciclos de
detención innecesarios.

- Una reducción de ciclos de detención por dependencia verdadera de datos y memoria.

Otra característica de los procesadores superescalares es la capacidad para especular; realizar predicciones sobre las
instrucciones que se ejecutaran tras una instrucción de salto.
2.2 ARQUITECTURA DE UN PROCESADOR SUPERESCALAR GENÉRICO

El modelo de segmentación superescalar genérica consta de 6 etapas:

- Lectura de instrucciones (IF – Instruction Fetch): Es capaz de leer 4 instrucciones de 4 bytes:

- Decodificación (ID – Instruction Decoding): Se decodifican en paralelo.

- Distribución/Emisión (II – Instruction Issue): Según su tipo y disponibilidad de operandos quedan en la


estación de reserva centralizada a la espera de ser emitidas a las unidades funcionales.

- Ejecución (EX – Execution): Se realiza fuera de orden.


- Terminación (WR – Write-Back Results): Escritura de resultados en registros.

- Retirada (RI – Retirement Instructions): Las instrucciones de almacenamiento proceden a la escritura ordenada
en memoria.

En líneas generales se entiende que una instrucción ha sido:

- Distribuida (dispatched): Cuando ha sido enviada a una estación de reserva.

- Emitida (issued): Cuando sale de la estación de reserva a una unidad funcional.

- Finalizada (finished): Cuando abandona la unidad funcional y pasa al buffer de reordenamiento.

- Terminada (completed): Cuando ha realizado la escritura de los resultados y se actualiza el estado del
procesador.

- Retirada: Cuando ha realizado la escritura en memoria.

Las dependencias falsas se resuelven recurriendo a un almacenamiento temporal en el que se realiza la escritura que
entraña riesgo. Se emplea una técnica conocida como renombramiento dinámico de registros que consiste en utilizar
un conjunto de registros ocultos en los que se realizan los almacenamientos temporales. Consta de 2 pasos:

1. Resolución de riesgos WAW y WAR: Se renombran los registros que son objeto de escritura.
2. Mantenimiento de dependencias RAW: Se renombran los registros fuente que corresponden a una escritura
previa.

En lo que respecta a las dependencias de instrucciones de carga/almacenamiento:

- Una carga tras un almacenamiento produce una dependencia RAW:

SD 0(R1), R5
LD R5, 0(R1)

- Un almacenamiento tras una carga implica una dependencia WAR:

LD R3, 0(R1)
SD 0(R1), R5

- Dos almacenamientos seguidos implican una dependencia WAW:

SD 0(R1), R3
SD 0(R1), R5
2.3 LECTURA DE INSTRUCCIONES
El número de instrucciones que se extrae de la
cache en cada ciclo está determinado por el ancho
de la etapa de lectura. Esto implica que la cache de
instrucciones tiene que estar diseñada para que en
un solo acceso proporcione tantas instrucciones
como ancha sea la etapa de lectura.
Dado que los procesadores disponen de
mecanismos auxiliares de almacenamiento entre
las etapas de lectura y decodificación.
El buffer de instrucciones se comporta como un
amortiguador. Aunque la etapa de lectura no
suministre instrucciones durante uno o varios
ciclos, la etapa ID puede continuar extrayendo
instrucciones del buffer.

Una vez se introduce una dirección en el contador


de programa, la lógica de control accede al
conjunto y bloque correspondiente y suministra al
buffer de instrucciones 4 instrucciones en un único
ciclo. A ese conjunto de instrucciones se le
denomina grupo de lectura.

El objetivo que debe cubrir esta etapa es garantizar


un suministro constante de instrucciones al
procesador, aunque pueden surgir 2 problemas: la
falta de alineamiento y el cambio de flujo de
instrucciones debido a los saltos.

2.3.1 Falta de alineamiento

Un alineamiento incorrecto de un grupo de lectura implica


que las instrucciones que forman ese grupo superan la frontera
que delimita la unidad de una cache. Esto conlleva a la
necesidad de dos accesos a la cache y reduce el ancho de
banda como mínimo a la mitad al ser necesario 2 ciclos de
reloj para suministrar el grupo y además puede producirse un
fallo de lectura en la segunda parte del grupo si este no
pertenece a un bloque que no está en la cache.

Si no hay restricciones de alineación se recurre a hardware


adicional para realizar la extracción parcial de la unidad de
lectura con las instrucciones desordenadas y se procede a la
colocación correcta de las instrucciones mediante:
- Red de alineamiento: Consiste en reubicar las salidas de la cache mediante multiplexores que conducen
las instrucciones leídas a su posición correcta.

- Red de desplazamiento: Recurre a registros de desplazamiento para mover las instrucciones:

Para minimizar el impacto de los fallos de lectura en la cache se usa el prefetching o prelectura, que consiste en
disponer de una cola de instrucciones a la que se recurre cuando sucede un fallo de lectura. Si las instrucciones
se encuentran en esta cola, se introducen en la segmentación.

2.3.2 Rotura de la secuencialidad

Sucede cuando una de las instrucciones que forma parte del grupo de lectura es un salto incondicional o un salto
condicional efectivo. Esto provoca que las instrucciones posteriores del grupo sean invalidas, reduciéndose el
ancho de banda de lectura.

En una segmentación de ancho s, cada ciclo de parada equivale a no emitir s instrucciones y esto se denomina
coste mínimo de la oportunidad perdida y es el producto entre el ancho de la segmentación y el número de ciclos
de penalización.

- Salto incondicional: Se inserta en el PC la dirección de salto, dejando inservible el procesamiento de las


instrucciones posteriores al salto que ya se encontrasen en alguna etapa de procesamiento.

- Salto condicional: Hay que esperar a ver si el salto es efectivo o no y, en caso afirmativo, calcular la
dirección de salto, insertarla en el PC y anular las instrucciones posteriores que estuviesen en el cauce.

2.3.3 Tratamiento de los saltos

El principal problema del tratamiento de los saltos es descubrir los ciclos que hay que esperar para conocer el
resultado de la evaluación de la condición que determina si el salto es efectivo o no y la dirección de destino.

El tratamiento de los saltos se inicia en la etapa IF y una vez que las instrucciones han sido extraídas, y se
confirma en la etapa ID que una instrucción es un salto, se puede mantener el tratamiento iniciado o proceder a
su anulación.
La técnica que se emplea es la especulación. Al mismo tiempo que se están leyendo instrucciones de la cache,
se comienza a predecir su efectividad y su dirección de destino, sin saber realmente si se trata de un salto (que
se confirma en la etapa ID).
Si a la salida de la unidad funcional de salto el resultado no coincide con la especulación, el hardware procederá
a vaciar la segmentación de las instrucciones especuladas y a redirigir la segmentación a la dirección correcta.
Es el compilador es el encargado de realizar la predicción estática cuando puede conocer de antemano el
resultado del salto condicional.

2.3.4 Estrategias de predicción dinámica

Se basan en el hecho de que un salto es una instrucción cuya ejecución se repite con cierta frecuencia, o que
permite derivar un patrón de comportamiento futuro basado en el análisis del comportamiento pasado. Las tasas
de predicción dinámica obtienen tasas de acierto que oscilan entre el 80% y el 95%, pero hay un incremento en
el coste económico del procesador debido al aumento en la complejidad del hardware aumenta.

Para realizar la especulación completa es necesario predecir los dos componentes que produce su procesamiento:
la dirección de destino y el resultado (efectivo o no efectivo).

2.3.4.1 Predicción de la dirección de destino de salto mediante BTAC (Branch Target Address
Cache)

Una BTAC es un cache asociativa en la que se almacenan las direcciones de las instrucciones de saltos
efectivos ya ejecutados (BIA – Branch Instruction Cache) y las direcciones de destino de esos saltos (BTA
– Branch Target Address)

El acceso se realiza en paralelo con el acceso a la I-cache. Si ninguna de las direcciones de las instrucciones
coincide con alguna de las de las BIAs es debido a que no hay ninguna instrucción de salto efectivo o nunca
antes había sido ejecutado. El funcionamiento de la BTAC es el siguiente:
El problema que surge en el de los falsos positivos (saltos fantasmas o saltos en falso) en los que la BTAC
devuelve una dirección de destino, pero en el grupo de lectura no hay ningún salto. El tratamiento correcto
es desestimar la predicción de la BTAC incurriendo en un pequeño coste en penalización (Ciclos_ID).

Estos falsos son debidos a que el campo BIA almacena una parte de una instrucción y provoca que a
direcciones distintas se les asocie la misma BIA. Si la tasa de falsos positivos es suficientemente baja, es
admisible pagar ese coste, pero si la tasa es alta, la solución es que el campo BIA se incremente ya que, si
llega a igualarse a la longitud de las direcciones, el problema de los falsos positivos deja de existir.
Otra técnica para predecir la dirección del salto es utilizar una BTIB (Branch Target Instruction Buffer).

En lugar de almacenar la dirección de destino de salto BTA, se almacena la instrucción de destino y algunas
posteriores. Asi, la BTIB entrega una secuencia de instrucciones al buffer como si se hubieran extraído de
la cache y en el PC se coloca la dirección que corresponde a la siguiente instrucción que hay después de la
última BTI.
2.3.4.2 Predicción de la dirección de destino de salto mediante BTB con historial de salto (Branch
Target Buffer)

Junto con la dirección de destino predicha BTA se almacena un conjunto de bits que representan el historial
de la efectividad del salto BH (Branch History). Si hay un acierto, las 4 situaciones que se pueden dar son:

- Se predice como efectivo y no hay error en la predicción: No sucede nada, no hay penalización y se
actualiza BH.

- Se predice como efectivo, pero hay error en la predicción: Se vacía el cauce de las instrucciones
especuladas, se actualiza BH y la entrada de BTB con la dirección de destino y se procesa la
instrucción indicada por el salto.

- Se predice como no efectivo y no hay error en la predicción: No sucede nada, no hay penalización y
se actualiza BH.

- Se predice como no efectivo, pero el salto si lo es: Se vacía el cauce de las instrucciones especuladas,
se salta a la dirección de destino, se actualiza BH y la entrada de la BTB con la dirección de destino
del salto.

Si ninguna de las instrucciones proporciona coincidencia en la BTB, se predice como no efectivo y se


plantean 2 situaciones:

- El salto no es efectivo: No ocurre nada.

- El salto es efectivo: Se vacía el cauce, se salta a la dirección destino y se incluye una entrada en la
BTB con los datos BIA, BTA y BH.
Otro aspecto que hay que resolver es la eliminación de entradas en la tabla debido a la falta de capacidad.
Como se trata de una especia de cache, la solución depende de la organización de la tabla.

- Correspondencia directa: Se sustituye la entrada directamente.

- Asociatividad: Se eliminan las entradas mediante el algoritmo LRU; se elimina la entrada que lleva
sin utilizarse más tiempo o la que tiene mayor probabilidad de ser no efectiva según su historial.

2.3.4.3 Predictor de Smith o predictor bimodal

- De un bit:

- De 2 bits:

Es el algoritmo de predicción más sencillo, que se basa en asociar un contador de saturación de k bits a
cada salto de forma que el bit más significativo del contador indica la predicción para el salto: 0 si el salto
se predice como no efectivo (NT – Not Taken) o 1 si el salto se predice como no efectivo (T – Taken).
Los k bits que forman el contador constituyen el historial de salto que se almacena en el campo BH de la
BTB.

Un contador de saturación es un contador clásico, pero cuando está en su valor máximo y se incrementa,
se queda igual y al revés. Cada vez que un salto es invocad, se extrae el historial de la BTB, se mira la
predicción y se aplica.

Las transiciones en las máquinas de estados representan el resultado real del salto mientras que los estados
representan el historial del salto.
2.3.4.4 Predictor de dos niveles basado en el historial global

Mantienen en un primer nivel de información un historial de los últimos saltos ejecutados. En un segundo
nivel, la información del primer nivel en combinación con la dirección de la instrucción de salto se utiliza
para acceder a una tabla que almacena contadores de saturación que son los que determinan la predicción.

El historial de los últimos h saltos se almacena en un registro de desplazamiento de h bits denominado


registro del historial de saltos (BHR – Branch History Register). Si el salto bue efectivo se introduce un 1
por la derecha, y en caso contrario, un 0.

Los h bits del BHR se concatenan con un subconjunto de m bits obtenido mediante la aplicación de una
función hash a la dirección de la instrucción de salto. Estos h + m bits se utilizan para acceder a una tabla
del historial de patrones (PHT – Pattern History Table), que almacena en cada una de sus 2h + m entradas un
contador de saturación de 2 bits (Como en el predictor de Smith).

Una vez que se ha evaluado el salto y se conoce su resultado se incrementa/decrementa el contador de la


PHT y se actualiza el historial del BHR.
2.3.4.5 Predictor de dos niveles basado en el historial local

En lugar de un único registro con el historial de los últimos saltos se utiliza una tabla en la que se almacena
el historial particular de cada salto. Se cambia el registro BHR por una table de historial de saltos (BHT –
Branch History Table) compuesta por varios BHR.

El acceso a la BHT se realiza mediante un hashing de la dirección de la instrucción de salto que los reduce
a k bits. Una vez se disponen los h bits del historial, se concatenan con los m bits obtenidos mediante otro
hashing de la dirección. Con la secuencia h + m se accede a la PHT para recuperar el estado del contador
de saturación.
Una vez que el salto se ha evaluado y se conoce su resultado, la BHT y la PHT se actualizan; primero se
accede a la PHT para actualizar el contador (se suma 1 si fue efectivo, se resta 1 si no lo fue) y después se
accede a la BHT para introducir el resultado del salto (1 si fue efectivo, 0 si no lo fue).
2.3.4.6 Predictor de dos niveles de índice compartido gshare

Es una variante del predictor de dos niveles de historial global. En lugar de concatenar los m bits obtenidos
de la función hash con los h recuperados del historial global, se realiza una XOR entre los h bits superiores
de los m. los h bits obtenidos de la XOR se concatenan con los m – h bits restantes para poder acceder a la
PHT.

2.3.4.7 Predictores híbridos

Los procesadores superescalares actuales recurren a 2 predictores que generan un resultado y un selector
se ocupa de decidir cuál de las dos decisiones hay que utilizar.

2.3.5 Pila de dirección de retorno

Validar una predicción de una instrucción implica comprobar que el resultado del salto coincide con la
predicción y si la dirección de destino predicha coincide con la dirección de destino real.

La instrucción de retorno de subrutina no puede predecirse adecuadamente ya que cada vez que se la invoca
puede saltar a una instrucción completamente diferente. Por ello la BTB generaría predicciones de destino con
una elevada tasa de fallos.
Para manejar estas situaciones, los procesadores actuales disponen de una pila de direcciones de retorno (RAS
– Return Address Stack) o buffer de pila de retorno (RSB – Return Stack Buffer). Cuando se invoca a una
subrutina mediante una instrucción de salto se efectúan 3 acciones:

- Se accede a la BTB para obtener la predicción de la dirección de destino.


- Se especula el resultado del salto.
- Se almacena en la RAS la dirección de la instrucción siguiente al salto.

El principal problema que presenta la RAS es su desbordamiento cando se maneja subrutinas con muchos
anidamientos y la pila no ha sido dimensionada adecuadamente.
2.3.6 Tratamiento de los errores en la predicción de los saltos

Cuando el resultado de la predicción del salto no coincide con el resultado verdadero es necesario deshacer el
procesamiento de todas las instrucciones incorrectamente ejecutadas debido al error de predicción. Esto se
conoce como recuperación de la secuencia correcta. Una vez que se conoce el resultado real del salto, los bloques
de instrucciones incorrectamente especulados deben ser expulsados de la segmentación.

Ejemplo:

La forma habitual para conocer y validar o anular las secuencias de instrucciones que se están ejecutando de
forma especulativa es recurrir a etiquetar las instrucciones durante todo el tiempo que permanecen en la
segmentación.
Desde que entran en la fase de distribución hasta que son terminadas, todas las instrucciones llevan asociadas
dos etiquetas con la información de su estado:

- Etiqueta de validez: Permite saber si la instrucción debe o no terminarse.

- Etiqueta especulativa: Si es de 1 bit, el valor 1 identifica a la instrucción como especulada. Si es de más


de 1 bit, el procesador permite que haya varias rutas especulativas ejecutándose en paralelo, esto se
denomina nivel de especulación y la etiqueta dispone de más bits para identificar cada uno de los bloques
de instrucciones especuladas.

Si la predicción coincide con el resultado, las etiquetas especulativas se cambian de forma que las instrucciones
especuladas asociadas a ese salto son correctas y se pueden terminar. Si la predicción es incorrecta, hay que
realizar 2 acciones.

1. Invalidar las instrucciones especuladas: El bit de validez pasa a indicar invalidez y no son terminadas.
Sus resultados se pierden y todas las instrucciones asociadas a esa ruta especulada se eliminan.
2. Recuperar la ruta correcta: Se inicia la lectura de las instrucciones desde la dirección de salto correcta.
Si la predicción incorrecta fue no realizar el salto, se utiliza el resultado del salto como nuevo valor del PC.
Si la predicción incorrecta fue realizar el salto, se accede a la tabla en la que se almaceno la dirección de la
instrucción de salto y se utiliza para obtener el nuevo valor del PC.

2.4 DECODIFICACIÓN
En un procesador RISC superescalar se decodifican varias instrucciones en paralelo y se identifican las dependencias
de datos con todas las instrucciones que componen el grupo de lectura y con las que están ejecutándose en unidades
funcionales. Además, serian varias las instrucciones que necesitarían leer todos sus operandos. Se debe confirmar si
hay saltos en falso en el grupo de lectura con el fin de poder anular la ruta especulada y minimizar el impacto que
tiene su procesamiento erróneo.
Por todas estas razones, los procesadores superescalares reparten las tareas que realiza una etapa de decodificación
clásica en dos etapas:

- Decodificación: Detecta los saltos en falso y realiza la adaptación del formato de las instrucciones.

- Distribución: Se ocupa del renombramiento de los registros, de la lectura de los operandos y del análisis de las
dependencias verdaderas.

Una complicación adicional que surge en los procesadores superescalares CISC es que las instrucciones pueden tener
longitudes diferentes, por ello es necesario analizar el código de operación de cada instrucción para conocer su
formato y longitud y poder determinar dónde termina y empieza la siguiente instrucción y cuáles son sus operandos.

Los factores que determinan la complejidad de la etapa de decodificación son el tipo arquitectónico del procesador
y el número de instrucciones a decodificar en paralelo. Las soluciones son tres:

- Descomponer la etapa de decodificación en varias subetapas.


- Realizar parte de la decodificación antes incluso que la extracción de las instrucciones (predecodificación).
- Traducción de instrucciones: Consiste en descomponer una instrucción en instrucciones más básicas.

Tras completar la decodificación, las instrucciones ya pasan al buffer de distribución y a las estaciones de reserva
para iniciar su verdadero procesamiento.

2.4.1 Predecodificación

Se sitúa antes de la I-caché, de forma que las instrucciones que ésta recibe como consecuencia de un fallo de
lectura tienen que pasar forzosamente por esta etapa. Está constituida por hardware situado antes de la I-caché
que realiza una decodificación parcial de las instrucciones. Este proceso analiza cada instrucción y le concatena
un pequeño conjunto de bits con información sobre ella.

Tiene 2 inconvenientes:

- La necesidad de un mayor ancho de banda, además del coste del hardware adicional que hay que añadir.
- El incremento de tamaño de la I-caché.
Ejemplo: Para simplificar las operaciones que se realizan en la etapa de extracción y en la decodificación, el
PowerPC970 cuenta con una etapa de predecodificación situada delate de la I-caché y del buffer de prefetch.
Esta etapa se ocupa de añadir 5 bits a cada instrucción, que se utilizan posteriormente en la etapa de Fetch para
determinar el tipo de instrucciones de salto y proceder o no a su especulación.

2.4.2 Traducción de instrucciones

Es el proceso por el que una instrucción RISC o CISC se convierte en un grupo equivalente de instrucciones
básicas RISC. Estas operaciones se conocen como:

- Microoperaciones (micro-ops) en terminología Intel.


- Operaciones internas (IOPs - Internal Operations) en terminología PowerPC
- ROPs (RIPS operations) en terminología AMD.

Esta técnica se utiliza para reducir la complejidad de determinadas instrucciones. La idea que subyace es
simplificar el repertorio original de instrucciones para que su procesamiento hardware se pueda realizar
directamente en el procesador.

Ejemplo: La arquitectura CISC que emplea la traducción de instrucciones es la Intel Core Microarchitecture.
La etapa de decodificación consta de 4 decodificadores (uno generalizado y lento y tres restringidos pero
rápidos) y un generador de microcódigo que permiten decodificar 4 instrucciones de longitud variable en
paralelo.

El primero de los 4 decodificadores, D0, puede manipular cualquier instrucción. Por su parte, los 3 restantes
están limitados a instrucciones sencillas que generan una única micro-op. La secuencia de micro-ops obtenida
se envía en grupos ordenados de 7 a un buffer de micro-ops desde donde pasan en grupos de 4 al RAT (Register
Alias Table) para el renombramiento de registros y posteriormente al buffer de reordenamiento (ROB, ReOrder
Buffer).
La arquitectura Intel se caracteriza por complementar la traducción de instrucciones con dos técnicas
adicionales:

- Macro-fusión: Consiste en fusionar o unir ciertos tipos de instrucciones en la fase de predecodificación y


enviarlas a uno de los decodificadores para producir una única micro-op, denominada macro-fused micro-
op. Esto permite realizar más trabajo con menos recursos hardware puesto que son necesarias menos
entradas al buffer de reordenamiento y en las estaciones de reserva. El efecto es un incremento del ancho
de banda y un incremento virtual de la anchura de segmentación en lo que respecta al núcleo de ejecución.
Esto es debido a que una unidad funcional puede estar ejecutando una macro-fused micro-op que
corresponde a dos instrucciones.

- Micro-fusión: Consiste en decodificar una instrucción que genera dos micro-ops y fundirlas en una única
micro-op que solo ocupa una entrada en el buffer de reordenamiento ROB, pero al emitirse se desdobla en
sus dos componentes originales. La aplicación más habitual es con las instrucciones de almacenamiento ya
que se desdobla en dos micro-ops: una para realizar el cálculo de la dirección de destino y otra para la
escritura del dato. Esto produce un aumento de la capacidad de decodificación y de la eficiencia energética
de la arquitectura al emitir más micro-ops recurriendo a menos hardware.

Ejemplo: El PowerPC970 de tipo RISC utiliza esta traducción de instrucciones, pero convirtiendo las
instrucciones CISC en Operaciones Internas (IOPs).
Cada ciclo de reloj extrae 5
instrucciones del buffer de
instrucciones y se comienzan a
procesar en la etapa de
decodificación compuesta por 3
etapas D1, D2 y D3 conocida como
in-line Decoding. Cuando una
instrucción tiene que ser traducida
en más de dos IOPs, el procesador
recurre a una extensión de la etapa
de decodificación compuesta por un
cauce de 4 subetapas y que realiza
una decodificación basada en
plantillas. Esta etapa se llama
template-based Decoding y es capaz
de generar 4 IOPs por ciclo.

Se denominan instrucciones rotas a


aquellas que se descomponen en 2
IOPs e instrucciones
microcodificadas a las que es
necesario descomponer en 3 o más
IOPs.

El objetivo final de la etapa de


decodificación es producir grupos
de distribución formados por 5
IOPs.

2.5 DISTRIBUCIÓN
Establece el punto de partida para la ejecución de instrucciones en paralelo y fuera de orden. Se ocupa de repartir las
instrucciones según su tipo entre las distintas unidades funcionales para que se pueda proceder a su ejecución en
paralelo.
Tras la decodificación, las instrucciones se depositan temporalmente en dos buffers conocidos como:

- Buffer de distribución o ventana de instrucciones.


- Buffer de terminación o de reordenamiento.

Si los operandos fuente no están disponibles y hay que esperar por algún resultado de las instrucciones o todo está
disponible pero no hay suficientes buses, hay riesgos estructurales. Por ello se tiene que detener la instrucción en la
etapa de decodificación hasta que todo esté listo para poder emitirla.
La solución por la que se opta es desacoplar la etapa de decodificación de la ejecución utilizando la ventana de
instrucciones. El desacoplo consiste en no detener la instrucción sino decodificar lo que se pueda y hacer que avance
hacia la ventana de instrucciones. La instrucción permanece a la espera para poder emitirse a la unidad funcional
correspondiente una vez que se cumplan las condiciones necesarias para ello.
2.5.1 Organización de la ventana de instrucciones

- Estación de reserva centralizada.

Una única estación distribuye y emite las instrucciones a las unidades funcionales. Intel core utiliza esta
organización.

- Estaciones de reserva distribuidas o individuales.

Cada unidad funcional dispone de una estación de reserva propia. Los procesadores PowerPC750 utilizan
estas organizaciones.

- Estaciones de reserva en clústeres o compartidas.

Las estaciones de reserva reciben las instrucciones del buffer de distribución, pero una estación de reserva
puede servir a varias unidades funcionales del mismo tipo. Un ejemplo de esta configuración es el
PowerPC970.
2.5.2 Operativa de una estación de reserva individual

Una estación de reserva es un


buffer de almacenamiento con
múltiples entradas en donde se
almacenan las instrucciones ya
decodificadas. Consta de los
siguientes campos:

- O (Ocupado): Indica que la


estrada está ocupada por una
instrucción valida pendiente de
emisión.

- CO (Codigo de operación):
Contiene el código de operación
de la instrucción.

- Op1 (Operando 1): Si el registro que corresponde al primer operando está disponible, almacena el valor
del registro o el identificador (depende del modo de lectura). Si no está disponible, almacena el
identificador del registro.

- V1 (Valido 1): Indica si el operando 1 está disponible o no.

- Op2 (Operando 2): Similar a Op1.

- V2 (Valido 2): Similar a V1.

- D (Destino): El identificador del registro destino.

- L (Listo): Indica que todos los operandos están disponibles y la instrucción puede emitirse.

Cuando una instrucción se distribuye y se almacena en una estación de reserva, el bit O se coloca a 1 para indicar
que la instrucción está pendiente de ser ejecutada. Si los dos operandos fuente estuviesen disponibles, los
campos V1 y V2 contendrían el valor 1. Si los operandos no están disponibles entonces se almacena el
identificador del registro fuente. Una vez que estén listos, entonces el bit L se coloca a 1 y la instrucción ya se
encuentra preparada para ser emitida en cuanto la unidad funcional asignada y el bus de enrutamiento estén
libres y O se pone a 0.

Ejemplo: Por simplificar, los bits de validez ya vienen establecidos desde la etapa de decodificación, por lo que
el buffer de distribución cuenta con ellos. Se considera que el buffer puede recibir y suministrar 5
instrucciones/ciclo, la unidad de suma/resta consume 1 ciclo y la de multiplicación/división 2 ciclos. Se
considera también que la lectura de los operandos fuente del fichero de registros no consume tiempo y se efectúa
al emitir la instrucción a la unidad funcional.
- ciclo i: Las 5 instrucciones son recibidas por el buffer de distribución desde la etapa de decodificación.

- ciclo i + 1: De las 5 instrucciones se distribuyen las 4 primeras instrucciones a las estaciones de reserva
individuales dado que su capacidad es de 2 instrucciones. i5 permanece en el buffer de distribución a la espera
de una entrada libre en la estación de reserva.

- ciclo i + 2: i1 e i4 tienen todos sus operandos fuente disponibles por lo que son emitidas a sus respectivas
unidades funcionales. En este mismo ciclo, el buffer de distribución envía la instrucción i5 a la estación de
reserva al quedar una entrada libre tras la emisión de i4.
- ciclo i + 3: La unidad de suma/resta ha concluido el procesamiento de i1 al final del ciclo anterior y ha
comunicado a las estaciones que el resultado que va al registro R1 está disponible. Además, en este ciclo se
emiten i2 e i3 al tener todos los operandos disponibles.

- ciclo i + 4: La instrucción i4 ha terminado en el ciclo anterior ya que consume 2 ciclos y ha concluido i3 al


consumir un ciclo, por ellos se produce la emisión de i5 debido a que los operandos que requiere ya se han
generado por parte de i3 y de i4.

- ciclo i + 5: Concluye el procesamiento de i2.

- ciclo i + 6: Concluye el procesamiento de i5.

De forma implícita, el mecanismo de las estaciones de reserva resuelve el problema de las dependencias RAW.
Las fases por las que pasa una instrucción desde que abandona el buffer hasta que se emite desde la estación de
reserva individual son 3: distribución, supervisión y emisión.

2.5.2.1 Fase de distribución

Consiste en el envío de una instrucción desde el buffer de


distribución a la estación de reserva individual que le
corresponda según su tipo. La introducción de las
instrucciones en la estación de reserva se realiza por parte del
hardware conocido como lógica de asignación, que se ocupa
de:

- Ubicar correctamente la instrucción recibida.


- Establecer inicialmente los bits de validez.
2.5.2.2 Fase de supervisión

Concluye en el momento en el que una instrucción tiene los dos operandos fuente disponibles y está en
condiciones de ser emitida. Durante esta fase, las instrucciones que tienen algún operando fuente marcado
como no disponible se encuentran en una espera activa, supervisando continuamente los buses de reenvío
(CDB, Common Dara Bus). El hardware que realiza la supervisión de los buses se denomina lógica de
activación.
Durante esta espera activa, la lógica de activación está comprobando continuamente los identificadores de
los operandos no disponibles con los identificadores que se publican en los buses de reenvío. Cuando se
produce una coincidencia de identificadores, se cambia el bit de validez del operando fuente
correspondiente, se lee el valor del bus de reenvío y, si todos los operandos ya están listos se activa el bit
que señala a la instrucción como preparada para ser emitida.

La complejidad de la lógica de activación es elevada y aumenta con el tamaño y el número de las estaciones
de reserva.

2.5.2.3 Fase de emisión

Comienza una vez que la instrucción tiene todos sus operandos disponibles. También se caracteriza por la
espera activa hasta que la lógica de selección determina la instrucción que se puede emitir de entre todas
las disponibles.
La lectura de los operandos se realiza en el momento en que la instrucción se emite a la unidad funcional.
Cuando se emite una instrucción se libera la entrada asociada mediante la asignación de un 0 en el bit de
ocupado para que se pueda distribuir una nueva instrucción.

La lógica de selección no es más que un algoritmo de planificación. El más habitual consiste en seleccionar
la instrucción más antigua de entre todas las disponibles.

2.5.3 Lectura de los operandos

Hasta este punto se ha considerado que la lectura de los operandos fuente se realizaba siempre en el momento
en que se emitían las instrucciones desde las estaciones de reserva individuales a las unidades funcionales, lo
que se denomina planificación sin lectura de los operandos.
Una de las ventajas de este modo de organización es
que el ancho de las estaciones de reserva y de los
buses de reenvío se reduce considerablemente.

Otra forma de efectuar la lectura es cuando la instrucción se distribuye desde el buffer a las estaciones de reserva
y se denomina planificación con lectura de operandos.
El código de operación y el identificador del registro destino
se copian directamente en la estación de reserva desde el
buffer de distribución mientras que los identificadores de los
operandos fuente se envían al fichero de registros. Si el
operando está disponible en el fichero de registros, la
estación de reserva recibe el valor y coloca el bit de validez
de su entrada a 1. Por el contrario, si el registro no está
disponible se reenvía el identificador a la estación y se coloca
el bit de validez a 0.

Ejemplo: Se considera que la estación de reserva centralizada distribuye 4 instrucciones/ciclo a las dos
estaciones de reserva individuales que alimentan una unidad funcional de suma/resta de 1 ciclo y una unidad
segmentada de multiplicación/división de 2 ciclos. Las estaciones de reserva individuales emiten un máximo de
1 instrucción/ciclo a la unidad funcional que tienen asignada. Inicialmente todos los registros son válidos.
- ciclo i: Se reciben las 5 instrucciones desde la etapa de decodificación.

- ciclo i + 1: Se distribuyen i1, i2, i3 e i4 quedando en espera la i5 para su distribución en el siguiente ciclo. Se
marcan los bits de validez de R1, R4 y R5 a 0 en el fichero de registros dado que son destinatarios. En las
estaciones de reserva se almacenan los valores de los operandos y se establecen sus bits de validez a 1. i1 e i4
se marcan como listas para ser emitidas al tener disponibles todos sus operandos.

- ciclo i + 2 (inicio): Se emiten i1 e i4, se recibe i5 de la estación de reserva centralizada y se coloca el bit de
validez de R6 a 0.
- ciclo i + 2 (final): Concluye la ejecución de i1 y se publica el resultado y el identificador de su registro destino
R1 en el buffer de reenvío. Se actualiza el bit de validez de R1 (pasa a 1) y su contenido en el fichero de registros.
Se actualizan las entradas de las estaciones de reserva que mantienen R1 como no disponible. i2 e i3 se marcan
como listas para ser emitidas.

- ciclo i + 3 (inicio): Se emiten i2 e i3. En la segmentación de la unidad funcional de multiplicación/división


coinciden dos instrucciones: i2 e i4.

- ciclo i + 3 (final) Concluye la ejecución de i3 e i4 y se actualizan los bits de validez y los valores de R4 y R5
en el fichero de registros. También se actualiza la entrada de la estación de reserva que corresponde a i5 que
queda lista para ser emitida.
- ciclo i + 4 (inicio): Se emite i5.

- ciclo i + 4 (final): Concluye la ejecución de i2 y se actualiza el valor y el bit de validez de R5 en el fichero de


registros.

- ciclo i + 5 (inicio): i5 comienza su procesamiento en la segunda etapa de la segmentación de la unidad de


multiplicación/división.
- ciclo i + 5 (final): i5 concluye su procesamiento en la unidad funcional y se publica el resultado en el buffer
de reenvío. Se actualiza el bit de validez y el valor de R5.

2.5.4 Renombramiento de registros

La ejecución fura de orden introduce nuevos problemas: la gestión de dependencias falsas WAR
(antidependencias) y WAW (dependencias de salida). Son consecuencia de la necesidad de reutilizar los
registros accesibles por el repertorio de instrucciones para efectuar el almacenamiento temporal de resultados.
Los registros accesibles por el programados son conocidos como fichero de registros arquitectónicos o de
registros creados. La mayor parte de estos registros son accesibles, pero otros no, porque contienen estados
internos del procesador.

El tiempo durante el que un dato almacenado en un registro es válido se denomina rango de vida del registro.
Es el tiempo desde que se almacena el valor en el registro hasta que se hace uso de ese valor por última vez
antes de reemplazarlo mediante una nueva escritura. Cada rango de vida conforma por si mismo una
dependencia verdadera RAW.

Ejemplo: Se considera que hay una unidad funcional de suma/resta de 1 ciclo y una unidad de
multiplicación/división de 2 ciclos y que en el mismo ciclo en que se obtiene un resultado se actualizan los bits
de validez
Al depender i3 de un operando de i2, se produce un retraso en su emisión hasta que i2 concluya y publique el
valor de R5. i4 depende de i3 por lo que por lo que también queda retenida hasta que R1 se marque como
disponible. Esta situación permite que i5 se emita y se ejecute, lo que posibilita la ejecución de i4 e i6 al validarse
R1. i4 se ejecuta antes que i6 ya que el planificador detecta que es más antigua puesto que esta antes en la
secuencia.

La ejecución de i4 dentro del rango de vida definido por i5 – i6 introduce un riesgo WAR ya que se produce un
solapamiento de los rangos. Además, la ejecución tardía introduce un riesgo WAW al permanecer el valor
descrito por i3 en R1, cuando semánticamente correcto sería que permaneciese el valor de R1 dejado por i5.

Hay 2 soluciones para arreglar esto:

- Ejecución secuencial de las instrucciones y la escritura ordenada de los registros que hacen de operando
destino, pero no es viable porque va en contra de una de las señales de identidad de los procesadores
superescalares.

- Detener aquellas instrucciones dependientes hasta que la instrucción inicial haya terminado de acceder al
registro dependiente. Esto es obligado para respetar las dependencias verdaderas.

La solución adoptada para resolver las dependencias falsas de datos es el renombramiento dinámico de registros
de la arquitectura mediante hardware. Este proceso consta de 2 pasos:

1. Resolución de los riesgos WAW y WAR. Se renombran de forma única los operandos destino de las
instrucciones. Se resuelven asi las dependencias WAW y WAR.

2. Mantenimiento de las dependencias RAW. Se renombran todos los registros fuente que son objeto de
una escritura previa utilizando el mismo nombre que se empleó en el paso 1.

El renombramiento dinámico se realiza incluyendo en el procesador un nuevo fichero de registros, denominado


fichero de registros de renombramiento (RRF, Rename Register File). Al fichero de registros de la arquitectura
se le denomina ARF (Architected Register File). Existen 3 formas de organizar el RRF: como único fichero
formado por la suma del RRF y del ARF, como estructura independiente pero accesible desde el ARF o como
parte del buffer de reordenamiento y accesible desde el ARF.
2.5.4.1 Organización independiente del RRF con acceso indexado

Las entradas del ARF se componen de 3 campos:

- Datos: Contiene el valor del registro.


- Ocupado: Indica si el registro ha sido renombrado.
- Índice: Apunta a la entrada del RRF que corresponde al último renombramiento realizado.

Las entradas del RRF constan de 3 campos:

- Datos: Resultado de la instrucción que ha causado el renombramiento.


- Ocupado: Se emplea para saber si el registro está siendo utilizado
- Valido: Indica que todavía no se ha realizado la escritura en el RRF.

La lectura de un registro puede encontrarse frente a 3 situaciones:

- El registro de ARF no está pendiente de ninguna escritura. Su bit de Ocupado permanece a 0


indicando que no hay renombramiento. Se procede a la lectura del valor almacenado en el campo
Datos.

- El registro del ARF es destinatario de una escritura por lo que ha sido renombrado, su bit de Ocupado
está a 1 y el campo Índice contiene un puntero a una entrada del RRF. Una vez se accede al RRF, se
pueden plantear 2 situaciones:

- Campo Valido = 0: El operando no está disponible y se procede a enviar el identificador del


registro de renombramiento a la estación de reserva.

- Campo Valido = 1: El operando está disponible por lo que se puede extraer su valor del RRF si
una instrucción lo necesita como operando fuente.

Por último, hay 3 situaciones que se pueden plantear al realizar el renombramiento en función del estado
de la instrucción de escritura que forzó el renombramiento:

- Instrucción pendiente de escritura: El registro R0 del ARF está marcado como ocupado y por ello el
contenido de Datos es información no actualizada. El valor de su índice apunta al Rr2 del RRF. En el
RRF el campo Valido se encuentra a 0 indicando que el resultado de la operación todavía no se ha
obtenido y el contenido de su campo Datos tampoco es válido. Ocupado está a 1 indicando que la
entrada esta en uso

- Instrucción finalizada: R2 está marcado como ocupado lo que indica que una instrucción de escritura
lo renombro, utilizando para ello Rr0 del RRF. Rr0 tiene su campo Valido a 1 para señalar que la
actualización del registro se ha realizado, lo que es indicativo de la finalización de la ejecución de la
instrucción de escritura. Ocupado permanece a 1 ya que la actualización de R2 todavía no se ha
realizado como consecuencia de que la instrucción no ha sido terminada. El valor almacenado en el
campo Daros del R2 no tiene validez porque está a falta de actualización de la escritura del contenido
del campo Datos del Rr0 en el campo Datos del R2.

- Instrucción terminada: R1 está marcado como no ocupado lo que indica que la instrucción de
escritura que lo utilizó ya terminó y actualizó el contenido de su campo Datos.

2.5.4.2 Organización independiente del RRF con acceso asociativo

Se accede de forma asociativa mediante una búsqueda en el RRF del identificador del registro ARF que
provoca el renombramiento. El ARF no necesita disponer de un campo Índice ya que para acceder al
registro de renombramiento se utiliza el identificador del registro destino ARF.
En la imagen, las 3 primeras entradas del ARF y del RRF replican las situaciones que se daban en la imagen
anterior. La cuarta entrada del ARF, indica que se encuentra renombrado por lo que hay que acceder a RRF.
Rr3 y Rr4 son dos renombramientos de R3. De los registros renombrados, el Rr4 es el que tiene el campo
Ultimo a 1 lo que indica que su renombramiento es más actual que Rr3.

2.5.4.3 Organización del RRF como parte del buffer de reordenamiento

Otra forma de acomodar el RRF en el núcleo del procesador es como parte de un buffer de reordenamiento
o terminación. Para incluirlo hay que añadir dos campos a sus entradas. Un campo Datos y un campo
Valido. No hay que añadir Ocupado ya que ya existe en el buffer de terminación puesto que se utiliza para
saber si una entrada esta libre o no cuando hay que almacenar una nueva instrucción.
Si al realizar la distribución de una instrucción se accede al ARF para efectuar la lectura de un operando,
las situaciones que se pueden producir son:

- Si el registro no está renombrado, se lee su valor del campo Datos del ARF y se utiliza como operando
fuente en la estación de reserva.

- Si está renombrado, se sigue el puntero del campo Índice y se accede a una entrada del buffer de
reordenamiento. Pueden pasar 2 cosas:

- Valido = 0: Se está a la espera de la finalización de la instrucción que tiene que actualizar el


contenido del registro de renombramiento. Como operando fuente se utiliza el identificador de la
entrada del buffer.

- Valido = 1: Se está pendiente de la terminación de la instrucción. Como operando fuente se


utiliza el valor almacenado en el campo Datos del buffer de terminación. En el instante en que la
instrucción se termine, el bit Ocupado se coloca a 0 tanto en el ARF como en el buffer de
reordenamiento.

2.5.5 Ejemplo de procesamiento de instrucciones con renombramiento

Se va a usar planificación dinámica con lectura de operandos y renombramiento de registros mediante un RRF
independiente con acceso indexado. La estación de reserva centralizada puede distribuir un máximo de 4
instrucciones/ciclo a las dos estaciones de reserva individuales que alimentan una unidad funcional de
suma/resta de 1 ciclo y una unidad segmentada de multiplicación/división de 2 ciclos. Las estaciones de reserva
individuales emiten 1 instrucción/ciclo. Los accesos al ARF y RRF se realizan en el mismo ciclo en que se
distribuye la instrucción.
El fragmento de código que se va a procesar es el siguiente:

- Ciclo 1: Recepción de instrucciones del decodificador.

- Ciclo 2: Distribución de i1, i2, i3 e i4 y renombramiento de R1, R2 y R3.


- Ciclo 3 (inicio): Se emiten i1 e i3. Se distribuye i5 y se renombra R1 por tercera vez.

- Ciclo 3 (final): Concluye i3 y se publica su resultado (-35) y su destino (Rr2).

- Ciclo 4 (inicio): Se emite i4. i1 se encuentra en el 2º ciclo. Se libera Rr2 por terminación de i3.

- Ciclo 4 (final): Finaliza i1. Se publica su resultado (150) y su destino (Rr0).


- Ciclo 5 (inicio): Se emiten i2 e i5. i4 se encuentra en el 2º ciclo. Se libera Rr0 por terminación de i1.

- Ciclo 5 (final): Finaliza i4 y se publica su resultado (-175) y su destino (Rr3). Finaliza i2 y se publica su
resultado (155) y su destino (Rr1).

- Ciclo 6 (inicio): Termina i2, se actualiza R2 y se libera Rr1. Termina i4, se actualiza R3 y se libera Rr3. i5 se
encuentra en el 2º ciclo.

- Ciclo 6 (final): Finaliza i5, se publica su resultado (200) y su destino (Rr4).


- Ciclo 7 (inicio): Termina i5, se actualiza R1 y se libera Rr4.

2.6 TERMINACIÓN
Una vez que las instrucciones han completado su etapa de ejecución, quedan almacenadas en el buffer de
reordenamiento a la espera de su terminación arquitectónica. Una instrucción se considera terminada cuando
actualiza el estado del procesador manteniendo la consistencia, que es cuando las instrucciones concluyen su
procesamiento en el mismo orden secuencial en el que se encuentran en el programa. Esto es fundamental por dos
razones:

- Para garantizar el resultado final del programa.


- Para permitir un tratamiento correcto de las interrupciones.

Una instrucción ha finalizado cuando abandona la unidad funcional y queda a la espera en el buffer de terminación.

Una instrucción ha terminado cuando ha actualizado el estado de la máquina.

Una instrucción se ha retirado cuando ha escrito su resultado en memoria.

El buffer de reordenamiento es una estructura que mantiene entradas con el estado de todas y cada una de las
instrucciones que hay en vuelo; las de las estaciones de reserva individuales, las que están en ejecución en las
unidades funcionales y las finalizadas a la espera de su terminación arquitectónica. El significado de estos campos
es el siguiente:

- O (Ocupada): Indica que la instrucción se ha distribuido. Permanece a 1 hasta que es terminada.


- E (Emitida): Pasa a valer 1 cuando la instrucción inicia su ejecución.
- F (Finalizada): Indica que la instrucción ha salido de la unidad funcional y espera ser terminada.
- Dir (Dirección): Contiene la dirección de memoria de la instrucción.
- Rd (Registro de destino): Id del registro destino. Se libera cuando no hay renombramientos pendientes.
- Rr (Registro de renombramiento): Id del registro de renombramiento.
- Es (Especulativa): Identifica a la instrucción como parte de una ruta especulativa.
- V (Validez): Se usa para saber si la instrucción puede terminarse o hay que ignorarla.

Si el RRF fuese parte del buffer de terminación habría que añadir a cada entrada los campo Datos (D) y validez de
los datos (Vdatos) y eliminar el campo Rr ya que la propia entrada haría de registro de renombramiento.
Si se unen los campos Ocupada, Emitida y Finalizada en un único campo de 3 bits, denominado Estado, una
instrucción pasara sucesivamente por las siguientes situaciones:

- Estado = 100: Instrucción en espera de emisión.


- Estado = 110: Instrucción en ejecución.
- Estado = 111: Instrucción pendiente de terminación.

El funcionamiento del buffer de terminación es similar al de una estructura de datos circular tipo anillo con:
- Puntero de cola: Apunta a la entrada de la siguiente instrucción a terminar.
- Puntero de cabecera: Apunta a la siguiente entrada libre en el buffer.

Cada vez que una instrucción se distribuye, el puntero de cabecera se incrementa y se inicializan los campos de la
entrada. El puntero de cola se incrementa cuando una instrucción se termina.

Cuando una instrucción es terminada arquitectónicamente sucede lo siguiente:

- Su registro de renombramiento asociado a Rr se libera.


- El registro destino Rd se actualiza con el valor de su registro de renombramiento Rr asociado.
- El campo Ocupado se fija a 0.
- El puntero de cola se incrementa para apuntar a la siguiente instrucción a terminar.

Ejemplo: El mismo ejemplo anterior solo que ahora se dispone de un buffer de terminación con un ancho de banda
de 2 instrucciones/ciclo.

- Ciclo 2: Se distribuye i1, i2, i3 e i4. - Ciclo 3 (inicio): Se emiten i1 e i3. Se distribuye i5.

- Ciclo 3 (final): Finaliza i3. - Ciclo 4 (inicio): Se emite i4.

- Ciclo 4 (final): Finaliza i1. - Ciclo 5 (inicio): Termina i1, se actualiza R1 y se libera Rr0.
No se libera R1 porque tiene 2 renombramientos pendientes
(Rr2 y Rr4). Se libera la entrada i1 del buffer. Se emiten i2 e
i5.
- Ciclo 5 (final): Finalizan i2 e i4. - Ciclo 6 (inicio): Termina i2, se actualiza R2 y se libera Rr1.
Termina i3, se actualiza R1 y se libera Rr2. No se libera R1
porque tiene un renombramiento pendiente (Rr4). Se liberan
las entradas 1 y 2 del buffer.

- Ciclo 6 (final): Finaliza i5. - Ciclo 7 (inicio): Termina i4, se actualiza R3 y se libera Rr3.
Termina i5, se actualiza R1 y se libera Rr4. Se liberan las
entradas 3 y 4 del buffer.

𝑉𝑝𝑖𝑐𝑜 = 𝑀𝑎𝑥. 𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 𝑡𝑒𝑟𝑚𝑖𝑛𝑎𝑑𝑎𝑠 𝑝𝑜𝑟 𝑐𝑖𝑐𝑙𝑜 · 𝐹𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎 𝑝𝑟𝑜𝑐𝑒𝑠𝑎𝑑𝑜𝑟

2.7 RETIRADA
Es exclusiva de las instrucciones de almacenamiento. El objetivo de esta etapa para con los almacenamientos es el
mismo que el de la etapa de terminación para con las instrucciones que escriben en registros: garantizar la
consistencia de memoria evitando los riesgos de memoria WAW y WAR. El mecanismo que se utiliza en esta etapa
es el buffer de almacenamiento o buffer de retirada que consta de dos partes:

- Finalización.
- Terminación.

La sintaxis genérica de las instrucciones de carga y almacenamiento que emplean direccionamiento basado en el
desplazamiento es:

- LD registro_destino, desplazamiento (registro_base)


- SD desplazamiento (registro_base), registro_fuente

El camino que siguen estas instrucciones consta de 3 pasos:

- Generación de la dirección: Se accede al registro base y se suma su valor al desplazamiento. Las instrucciones
de almacenamiento además deben extraer del registro fuente el valor a almacenar. Las instrucciones de
carga/almacenamiento también sufren el renombrado de sus registros y las esperas en las estaciones de reserva
hasta que sus operandos están disponibles. Si el procesador utiliza el modo de direccionamiento real, no es
necesario realizar este paso.

- Traducción: Se realiza consultando la TLB. Si al realizar el acceso a ella, la dirección virtual se encuentra
mapeada en la memoria física, la TLB devuelve una dirección física y el acceso se produce con normalidad. Si
la dirección virtual no se encuentra en memoria principal se produce un fallo de página y obliga al sistema
operativo a suspender la ejecución del proceso que ha provocado la excepción.

- Lectura/escritura en memoria: Las instrucciones de carga realizan los 3 pasos en la etapa de ejecución mientras
que las de almacenamiento dejan el acceso a memoria para la etapa de retirada. Si el dato está en la D-Cache, la
lectura por parte de la instrucción de carga consume un ciclo de reloj. Si el dato no está, se produce un fallo de
lectura. Una vez que la instrucción ha recuperado el dato, lo coloca en el bus de reenvío para que se actualicen
el RRF y las entradas de las estaciones de reserva individuales que detecten coincidencia de operandos. Una vez
que una instrucción de almacenamiento es finalizada, el dato a escribir y la dirección de memoria se guardan en
la entrada que a la instrucción se le asigna en el buffer de almacenamiento. Cuando la instrucción es terminada
por mandato del buffer de reordenamiento, la instrucción de almacenamiento se marca en el buffer de
almacenamiento como terminada, quedando lista para escribir en memoria.

La escritura diferida por parte de una instrucción de almacenamiento tras el abandono del buffer de reordenamiento
evita una actualización errónea y precipitada de memoria.
Los riesgos de memoria WAW se evitan mediante el buffer de almacenamiento que asegura que las escrituras en la
D-Cache se realizan respetando el orden en que aparecen en el programa. Los riesgos WAR se evitan gracias a que
una instrucción de almacenamiento posterior a una carga nunca podrá escribir antes que la carga ya que lo evita la
escritura diferida.

2.8 MEJORAS EN EL PROCESAMIENTO DE LAS INSTRUCCIONES DE


CARGA/ALMACENAMIENTO
- Una mejora se obtiene permitiendo que las instrucciones de carga (que escriben en un registro que constituye el
punto de arranque crítico para un conjunto de instrucciones aritmético/lógicas) terminen antes que otras instrucciones
sin que se violen dependencias RAW tanto de datos como de memoria.

- Otra mejora es el reenvío de datos desde una instrucción de almacenamiento hacia una de carga que tengan
operandos destino y fuente comunes.

2.8.1 Reenvío de datos entre instrucciones de almacenamiento y de carga

Se puede realizar cuando existe una dependencia de memoria RAW entre una instrucción de almacenamiento y
una de carga. Es necesario comprobar que las direcciones de memoria de una instrucción de carga y de los
almacenamientos pendientes sean coincidentes.
Los riesgos de datos RAW se resuelven en las estaciones de reserva, pero los riesgos de memoria RAW todavía
no han sido tratados y son los que hay que analizar para reenviar datos y adelantar cargas.

Ejemplo:

En principio, las direcciones de memoria de i1 e i2 son diferentes y no sería correcto reenviar el contenido
de R1 a R3, pero si R2 = 50 y R4 = 100, las dos direcciones de memoria serian iguales y surgiría una
dependencia de memoria tipo RAW.

Esta situación se denomina dependencia ambigua y es propia de estas instrucciones.

En el proceso de búsqueda de direcciones coincidentes pueden plantearse dos situaciones:

- Hay coincidencia (hay dependencia de memoria RAW): Existe una instrucción que va a escribir un dato
en la misma posición que la carga tiene que leer. El valor del campo Datos de la entrada que presenta
coincidencia se coloca en el bus de reenvío junto con el Id del registro destino RRF de la carga. La carga
no necesita acceder a la D-Cache.

- No hay coincidencia (no hay dependencias de memoria RAW): Se realiza el acceso a la cache de datos
de forma normal.
Un problema que puede surgir es la aparición de una carga que presenta varias coincidencias en el buffer de
almacenamiento. En ese caso, el procesador debe contar con el hardware adecuado para saber cuál es el
almacenamiento más reciente, que es el que hay que utilizar como dato.

2.8.2 Terminación adelantada de las instrucciones de carga

- Las instrucciones de carga/almacenamiento son emitidas en orden: La carga podría comprobar la coincidencia
de su dirección en el campo Dirección de las entradas del buffer de almacenamiento. Hay 2 posibles situaciones:

- Hay coincidencia de direcciones: No se puede adelantar la carga. El dato de la D-Cache se descarta y se


utiliza el dato del almacenamiento coincidente (se produce un reenvío de datos).

- No hay coincidencia de direcciones: El dato recuperado de la D-Cache es válido y se puede almacenar en


su registro destino (se produce un adelantamiento).

La emisión ordenada reduce la complejidad del hardware que es necesario para resolver las dependencias
ambiguas. Pero esta aproximación ofrece un menor rendimiento que si se permitiese una ejecución desordenada.

- Las instrucciones de carga/almacenamiento son emitidas fuera de orden: Una técnica utilizada se basa en el
uso de un buffer de cargas finalizadas. La idea de esta técnica se basa en permitir que las cargas se ejecuten de
forma especulativa sin comprobar la existencia de dependencias antiguas. Las cargas comprueban las existencias
de coincidencias de dirección con los almacenamientos que se encuentren en el buffer de almacenamiento. Se
plantean dos situaciones:

- Hay coincidencia: Hay una dependencia RAW con un almacenamiento. Se anua la carga y todas las
instrucciones posteriores para su emisión posterior.

- No hay coincidencia: Se permite que la carga continúe normalmente ya que no hay ningún tipo de
dependencia de memoria RAW.

Tras finalizar, las cargas quedan almacenadas en el buffer de cargas finalizadas. Cuando una instrucción de
almacenamiento termina tiene que comprobar la existencia de una coincidencia con las cargas que están en el
buffer de cargas finalizadas. Pueden darse 2 situaciones:

- Hay coincidencia: Se ha hecho una carga de forma especulativa y hay que invalidar esa carga y todas las
instrucciones posteriores para su posterior emisión.

- No hay coincidencia: La carga y todas las instrucciones posteriores pueden terminarse.

2.9 TRATAMIENTO DE INTERRUPCIONES


Cuando se produce una interrupción, el estado del proceso interrumpido es guardado por el hardware, el software, o
una combinación de ambos. El estado del procesador está definido por el contador está definido por el contador de
programa, los registros arquitectónicos y el espacio de memoria asignado.
Si un procesador es capaz de tratar las interrupciones de forma que se respeten las condiciones previas, se dice que
el procesador tiene precisión de excepción o que maneja interrupciones precisas. Las instrucciones se dividen en 2
grupos:

- Interrupciones externas: Producidas por eventos externos a la ejecución del programa. Una estrategia de
actuación consiste en:

- Detener la lectura de nuevas instrucciones.


- Vaciar el cauce terminando arquitectónicamente las instrucciones ya existentes.
- Guardar el estado en que quedo el procesador tras terminar la instrucción previa la que fue interrumpida.
- Excepciones, interrupciones de programa o traps: Causadas en la etapa IF o por las instrucciones del programa
en ejecución. El tratamiento de las excepciones es similar al de las interrupciones externas, aunque en algunos
tipos de excepción no se considera la reanudación del programa.

Las técnicas para el tratamiento de interrupciones en un procesador que permite la ejecución fuera de orden se pueden
clasificar en 4 categorías:

- Ignorar el problema y no garantizar la precisión de la excepción.

- Permitir que las instrucciones sean algo imprecisas, pero guardo información para que la rutina de tratamiento
software pueda recrear una secuencia precisa para recuperarse de la interrupción.

- Permitir que una instrucción sea emitida solo cuando se está seguro de que las instrucciones que la preceden
terminaran sin causar ninguna interrupción.

- Mantener los resultados de las operaciones en un buffer de almacenamiento temporal hasta que todas las
operaciones previas hayan terminado correctamente y se puedan actualizar los registros arquitectónicos sin
ningún tipo de riesgo.

Para lograr la precisión de excepción se han diseñado 2 técnicas:

- Buffer de historia.
Es muy similar en su estructura y funcionamiento al
buffer de reordenamiento. La diferencia radica en que
en sus entradas se almacena el calor de los registros
destino al comienzo de la ejecución de las
instrucciones (Vp).

Cuando una instrucción es interrumpida, parte del


contenido del ARF se recupera. Asi si una instrucción
hubiese finalizado antes que la interrumpida pese a ser posterior, el estado de su registro destino se sacaría
del campo Vp de su entrada en el buffer de historia y se copiaría en el ARF.

- Fichero de registros de futuro.


Los operandos fuente se leen del fichero de futuro y al
finalizar las instrucciones los resultados se escriben en el
fichero de futuro y en el buffer de terminación. Si una
instrucción es interrumpida, el fichero de futuro recupera
los valores originales de los registros destino de la
interrupción interrumpida y de las posteriores desde el
ARF.

Estas técnicas proporcionan precisión de excepción retrasando el almacenamiento definitivo de los resultados
mientras que las instrucciones no sean terminadas arquitectónicamente y dadas por válidas.

2.9.1 Excepciones precisas con buffer de reordenamiento

Es el elemento clave que permite mantener la consistencia del procesador, ya que posibilita que las instrucciones
no interrumpidas terminen en el mismo orden, almacenando sus resultados definitivos de forma ordenada. Para
conocer si una instrucción ha sufrido una interrupción, las entradas del buffer cuentan con un campo Int, que
pasa a valer 1 para indicar que una instrucción ha sido interrumpida.
2.10 LIMITACIONES DE LOS PROCESADORES SUPERESCALARES
El aumento de prestaciones que se puede obtener en un procesador superescalar explotando únicamente el
paralelismo a nivel de instrucción, está restringido por dos factores:

- La complejidad y el coste de la fase de distribución de las instrucciones y el análisis de las dependencias


existentes entre ellas.

- El grado de paralelismo intrínseco existente en el flujo de instrucciones de un programa.

TEMA 3: PROCESADORES VLIW Y PROCESADORES


VECTORIALES
3.1 EL CONCEPTO ARQUITECTÓNICO VLIW
Un procesador VLIW es similar a un procesador superescalar en cuanto a que puede emitir y terminar varias
operaciones en paralelo. La diferencia es que el hardware no tiene que intervenir para descubrir el paralelismo entre
instrucciones, ya que es responsabilidad del compilador. El hardware se limita a emitir una instrucción por ciclo, lo
que implica que el paralelismo que puede proporcionar el hardware debe ser conocido por el creador del compilador.

La diferencia entre el enfoque superescalar y el VLIW es como realiza la planificación de las instrucciones:

- Superescalar: La planificación se realiza vía hardware y se adjetiva como dinámica.


- VLIW: Se realiza vía software y se denomina estática, ya que es el compilador el que establece la secuencia
paralela de instrucciones.

En un procesador VLIW se emite una instrucción por ciclo y una detención de una unidad funcional implica detener
todas las unidades funcionales para mantener la sincronía de la emisión

La complejidad del hardware se reduce y se desplaza hacia el software. Ya no son necesarios todos los recursos
asociados a las etapas de decodificación, distribución y terminación de un procesador superescalar. Esto se traduce
en:

- Reducción de la cantidad de transistores necesarios.


- Menor energía consumida.
- Menor calor generado.
- Menor inversión económica.

Otra ventaja es que la complejidad que implica el desarrollo del compilador se paga una sola vez y no cada vez que
se fabrica un chip. Además, nuevas mejoras en el compilador pueden introducirse una vez que los procesadores están
en fase de producción.

La explicación de que los procesadores VLIW hayan fracasado es:

- La incapacidad para desarrolla compiladores que aprovechen al máximo las características del enfoque VLIW.
Uno de los principales problemas es que el tamaño del código objeto para un procesador VLIW es mayor que
para un procesador superescalar.

- Los problemas de compatibilidad entre generaciones de procesadores VLIW. Procesadores VLIW con el
mismo repertorio de instrucciones, pero con una arquitectura diferente no son compatibles a efectos de código
abierto.
3.2 ARQUITECTURA DE UN PROCESADOR VLIW GENÉRICO

La principal diferencia con respecto a un procesador superescalar es la ausencia de los elementos necesarios para la
distribución, emisión y reordenación de instrucciones, aunque todos aquellos mecanismos que utilizan los
procesadores superescalares para garantizar el flujo continuo de instrucciones al procesador desde memoria son
perfectamente válidos para un procesador VLIW.

En esta arquitectura genérica, la unidad UF1 es la que se ocupa de las instrucciones de acceso a memoria, de ahí su
conexión con la cache de datos, mientras que las demás son unidades para la realización de operaciones
aritmético/lógicas, considerando a los saltos y bifurcaciones como operaciones enteras. Las unidades funcionales
están segmentadas y presentan diferentes latencias.

Los repertorios de instrucciones de las arquitecturas VLIW siguen una filosofía RISC con la excepción de que el
tamaño de instrucción es mucho mayor ya que contienen múltiples operaciones o mini-instrucciones.

El número y tipo de operaciones que contiene una instrucción VLIW se corresponde con el número y el tipo de
unidades funcionales existentes en el procesador.
Otro aspecto que debe tener en cuenta el compilador es si las operaciones que forman una instrucción pueden situarse
en cualquier posición dentro de la instrucción:

- Si las instrucciones pueden situarse en cualquier posición, la red de interconexión se complica ya que debe
contemplar cualquier posibilidad.

- Si cada operación tiene una posición fija dentro de la instrucción, la red de interconexión se simplifica ya que
basta con una conexión directa entre el campo asignado a una operación y la ventana de emisión asociada a la
unidad funcional.
Los procesadores VLIWE no detienen las unidades funcionales en espera de resultados. No existen interbloqueos
por dependencias de datos ni hardware para detectarlas ya que el compilador se encarga de generar el código objeto
para evitar estas situaciones. Para ello recurre a la inserción de operaciones NOP.

Ejemplo: Se considera un procesador VLIW con un formato de instrucción que admite una operación entera de 1
ciclo de latencia y una operación en coma flotante de 2 ciclos. Se pretende ejecutar el siguiente código:

En un procesador VLIW, el compilador conoce las unidades funcionales con que cuenta el procesador y sus
respectivas latencias. De acuerdo con esto, las instrucciones VLIW que produciría el compilador serían las siguientes:

Las dos primeras operaciones se pueden colocar en la misma instrucción I1 ya que se pueden ejecutar en paralelo al
no tener ningún tipo de dependencia entre ellas. Ya que la latencia de la unidad entera es de 1 ciclo, la instrucción I2
puede contener la operación entera i3. Sin embargo, el slot correspondiente a la operación en coma flotante debe
quedar vacío ya que hay que esperar 1 ciclo por el resultado de i2. I3 está formada por la operación en coma flotante
i4 junto con la siguiente operación entera, la correspondiente a i5.

Uno de los grandes problemas del enfoque VLIW es la necesidad de que el compilador encuentre instrucciones fuente
independientes para poder rellenar todas las operaciones de que consta una instrucción VLIW. El no poder rellenar
una instrucción tiene 2 consecuencias:

- El código objeto de un procesador VLIW es de mayor tamaño que el equivalente para un procesador
superescalar. El espacio que ocupa la instrucción en memoria no se reduce. Se coloca como código de operación
NOP.

- No se aprovechan al máximo los recursos del procesador ya que se tienen unidades funcionales ociosas.

A medida que el ancho de la instrucción VLIW es mayor y aumenta el número de referencias a memoria, la detención
de todas las unidades funcionales llega a ser inaceptable, limitando gravemente el rendimiento del procesador.

3.3 PLANIFICACIÓN ESTÁTICA O BASADA EN EL COMPILADOR


Cuando un procesador VLIW recibe como entrada el código fuente de una aplicación, realiza una serie de tareas
encaminadas a optimizar el código:

- Código intermedio: Está formado por sencillas instrucciones tipo RISC y las únicas dependencias de datos que
permanecen son las RAW. Presenta dos problemas:

- Encontrar un número suficiente de operaciones en el código intermedio para formar instrucciones VLIW
de forma que se maximice el rendimiento del procesador.

- La necesidad de extraer el máximo paralelismo posible en tiempo de compilación.

- Grafo de flujo de control: Es necesario conocer los bloques básicos de que consta el código intermedio, que se
componen de un grupo de instrucciones que forman una línea de ejecución secuencial por lo que en su interior
no existen instrucciones de salto con la salvedad de la última. Para obtenerlos, se analiza el código intermedio
teniendo en cuenta que:
- Una instrucción etiquetada o la siguiente instrucción a una instrucción de salto establecen el comienzo de
un bloque básico.

- El bloque básico se compone por todas las instrucciones que hay desde la instrucción inicial hasta la
siguiente instrucción de salto que se detecte.

- Los bloques se enumeran de forma secuencial. La interconexión de las entradas y las salidas de los
diferentes bloques básicos conforma el diagrama de flujo de control.

- Grafo de flujo de datos: Es un grafo dirigido en el que los nodos son las instrucciones de un bloque básico y
los arcos se inician en una instrucción de escritura en un registro y tienen como destino una instrucción que lee
el valor de ese registro. Los números colocados junto a cada arco representan los ciclos de reloj que consume la
unidad funcional en que se ejecuta cada operación y que el compilador utilizara para realizar la planificación de
la secuencia de instrucciones VLIW.

De cada bloque, el compilador intentara extraer el máximo paralelismo existente entre sus instrucciones teniendo en
cuenta el diagrama de flujo de datos de cada bloque, las unidades funcionales del procesador y las latencias que
presentan.
Para completar un ejemplo de planificación local recurriendo a la secuencia de instrucciones del bloque básico de la
anterior imagen, se considera que se dispone de un procesador VLIW con un formato de instrucción que admite dos
operaciones de suma/resta de 1 ciclo, una operación de multiplicación/división de 3 ciclos, una operación de carga
de 2 ciclos y otra de almacenamiento de 2 ciclos. Las instrucciones de salto son consideradas de suma/resta que
escriben en el registro contador de programa, por lo que consumen 1 ciclo.

Si se considera que las instrucciones VLIW tienen una longitud de 20 bytes y que cada operación básica ocupa 4
bytes, el desaprovechamiento del código es del 64% ya que el programa consume 100 bytes en memoria, pero solo
un 36% está ocupado por operaciones útiles.

3.4 DESENROLLAMIENTO DE BUCLES


Es una técnica de planificación local que permite aprovechar el paralelismo existente entre las instrucciones que
componen el cuerpo de un bucle. La técnica consiste en replicar múltiples veces el cuerpo del bucle utilizando
diferentes registros en cada replica y ajustar el código de terminación en función de las veces que se replique el
cuerpo. Esto tiene 3 ventajas:

- Se reduce el número de iteraciones del bucle, lo que implica una reducción de las dependencias de control al
ejecutarse menos instrucciones de salto.

- El total de instrucciones ejecutadas es menor ya que se reduce el número de instrucciones de


incremento/decremento de los índices que se utilicen en el bucle.

- Se proporciona al compilador un mayor número de oportunidades para planificar las instrucciones ya que al
desenrollar el bucle queda mucho más calculo al descubierto al incrementarse el tamaño de los fragmentos de
código lineal.

Ejemplo:

El código representa un bucle en el que se realiza la suma de una constante, almacenada en el registro F2, a todos los
elementos de un vector almacenado en memoria cuyos elementos son de doble precisión (8 bytes). El índice que
permite acceder a los elementos se almacena en R1, que inicialmente contiene la posición de memoria que ocupa el
último elemento del vector. Si se desenrolla el cuerpo del bucle 4 veces se obtiene la siguiente secuencia de
instrucciones:
El índice se decrementa de 4 en 4 elementos, que son los elementos del vector que se procesan en cada iteración del
nuevo bucle desenrollado. El desenrollamiento ha permitido que el número de iteraciones necesarias para recorrer
todo el bucle se haya dividido por 4 y el total de instrucciones ejecutadas sea inferior ya que se han eliminado 2
instrucciones por cada nuevo desenrollamiento, lo que representa un ahorro de 6 instrucciones en cada iteración del
bucle desenrollado. La secuencia desenrollada se puede reorganizar agrupando las instrucciones por tipo.

Se considera que se dispone de un procesador VLIW dotado de 3 unidades funcionales: una para operaciones enteras
de 1 ciclo, una para operaciones de coma flotante de 3 ciclos y otra para operaciones de carga/almacenamiento de 2
ciclos. Las operaciones de salto se ejecutan en la unidad entera.
En cada ciclo de reloj, la lógica de emisión del procesador emite una instrucción hacia las unidades funcionales, no
deteniéndose en ningún momento el proceso de emisión como consecuencia de una dependencia de datos entre
instrucciones.
La dependencia WAR existente entre la lectura del registro R1 por las instrucciones de almacenamiento y su escritura
por la instrucción SUBI se ha resuelto teniendo en cuenta el efecto que produce el decremento adelantado del índice.
Por ese motivo, las cuatro instrucciones de almacenamiento se modifican para recoger el decremento adelantado del
registro R1: como se decrementa por adelantado en 32, se suma un valor de 32 a los desplazamientos de los
almacenamientos 0, -8, -16 y -24, dando como resultado que el adelanto en la escritura de R1 provoque que los
nuevos desplazamientos tengan que pasar a ser 32, 24, 16 y 8.

Si la instrucción y cada operación necesitan un tamaño de 12 y 4 bytes respectivamente, el espacio de


almacenamiento desaprovechado es aproximadamente del 50%. Las instrucciones VLIW ocupan 108 bytes (9
instrucciones · 12 bytes) mientras que las operaciones originales consumen 56 bytes (14 instrucciones · 4 bytes) en
el caso del bucle desenrollado y 20 bytes (5 instrucciones · 4 bytes) en el bucle original.

Donde si se aprecia una mejora es en el rendimiento. Si el vector constase de 1000 elementos, el cuerpo del bucle
desenrollado 4 veces y planificado consumiría, en el mejor de los casos 3500 ciclos (250 iteraciones · 14
instrucciones) mientras que el VLIW únicamente necesitaría 2250 ciclos (250 iteraciones · 9 instrucciones). El
enfoque VLIW es un 55% más rápido que la aproximación escalar desenrollada y planificada.

Sin desenrollamiento ni planificación, el VLIW sería el siguiente:

Costa de 6 instrucciones y tiene un tamaño de 72 bytes, de los cuales 20 bytes están ocupados con operaciones. En
lo que representa a la velocidad de ejecución, si el vector consta de 1000 elementos se tardarían 6000 ciclos (1000
iteraciones · 6 instrucciones).

Por tanto, la utilización de la técnica de desenrollamiento en este ejemplo como paso previo a la generación del
código VLIW permite acelerar la ejecución un 166%.

3.5 SEGMENTACIÓN SOFTWARE


Es otra técnica que se utiliza para intentar aprovechar al máximo el paralelismo a nivel de instrucción existente en el
cuerpo de algunos bucles. Consiste en producir un nuevo cuerpo del bucle compuesto por la intercalación de
instrucciones correspondientes a diferentes iteraciones del bucle original. Al reorganizar un bucle mediante
segmentación software, siempre es necesario añadir:

- Prólogo: Instrucciones de arranque antes del cuerpo del bucle.


- Epílogo: Instrucciones de terminación tras la finalización del cuerpo del bucle.

Una vez se dispone del nuevo bucle ya reorganizado, y dado que las operaciones que lo forman pertenecen a
diferentes iteraciones, es posible planificarlas y emitirlas en paralelo bajo la forma de instrucciones VLIW.
Un bucle escalar reordenado mediante esta técnica no consta de más instrucciones que el bucle original salvo en lo
que se refiere a las instrucciones que forman el prólogo y el epilogo.
Este es un esquema de aplicación a un
bucle compuesto por 4 instrucciones
genéricas A, B, C y D donde cada una de
ellas consume un ciclo y presenta una
dependencia RAW con la instrucción
que la precede.

Tras 6 iteraciones del bucle original


aparece un patrón de ejecución
compuesto por instrucciones
pertenecientes a 4 iteraciones diferentes
y que ya no presentan dependencias
RAW. Este patrón constituye el cuerpo
del nuevo bucle reorganizado.

Las instrucciones que hay antes de la aparición del primer patrón es el prólogo y las que hay después del último
patrón es el epilogo.

Dado que mediante esta técnica se ejecutan al mismo tiempo instrucciones provenientes de múltiples iteraciones se
la conoce también como planificación policíclica.
Una vez que se aplica la técnica de segmentación a un bucle, las instrucciones que componen el prólogo, el patrón y
el epilogo se utilizan para generar la versión VLIW del bucle original.

Ejemplo: Se dispone de un procesador VLIW con 3 unidades funcionales: una para las operaciones de acceso a
memoria de 2 ciclos, una para enteros de 1 ciclo y otra para operaciones en coma flotante de 3 ciclos.

El patrón que se obtiene al iterar el bucle 6 veces es:

No se han incluido las instrucciones que decrementan el valor de R1, aunque si se ha reflejado el necesario
decremento en los desplazamientos de las instrucciones de carga y almacenamiento.
Una vez que se dispone del patrón, el compilador debe proceder a generar el código VLIW. Una secuencia de código
genérico correspondiente al bucle segmentado es:
if(R1 <> 40){R1 := R1 – 8; go to
inicio}

Se puede apreciar que las instrucciones VLIW se obtienen prácticamente de forma directa del patrón.

Si se considera que las instrucciones VLIW son de 16 byte s, el tamaño total del código es de 176 bytes. En lo
referente al tiempo para procesar un vector de 1000 elementos, la aproximación VLIW emplearía 1005 ciclos (5 del
prólogo, 5 del epilogo y 995 a las iteraciones del bucle).

La segmentación software puede llegar a ser extremadamente complicada de aplicar en un procesador VLIW cuando:

- Hay instrucciones condicionales en el cuerpo del bucle que impiden la aparición de un patrón regular.
- El número de iteraciones no se conoce a priori.
- El número y tipo de operaciones que forman el patrón de ejecución no se ajusta al formato de la instrucción
VLIW.

3.6 PLANIFICACIÓN DE TRAZAS


Es una técnica de planificación global que permite tratar una secuencia de bloques básicos como si fuese uno único
y, a partir de ese nuevo bloque extendido dotado de un mayor número de operaciones, producir código VLIW más
optimo mediante una adecuada planificación de las operaciones.

La planificación de trazas es una combinación de dos pasos que se efectúan de forma consecutiva:

- Selección de la traza.

Consiste en encontrar un conjunto de bloques básicos que formen una secuencia de código sin bucles
(traza). La selección de los bloques dentro del grafo de flujo de control se realiza especulando sobre cuáles
son las rutas de ejecución consideradas más probables. Para conocerlas, el compilar recurre a un grafo de
flujo de control con pesos que es similar al grafo de flujo de control, pero asignando a cada bloque una
etiqueta que indica la probabilidad o frecuencia de ejecución. Su obtención se puede realizar de varias
formas:

- Perfiles de ejecución del programa.


- Estimaciones software.
- Planificación estática de saltos.

Ejemplo: Se dispone de un procesador VLIW con 3 unidades funcionales: una para las operaciones de
carga/almacenamiento de 2 ciclos, una para enteros de 1 ciclo y otra para operaciones en coma flotante de
3 ciclos. Las operaciones de salto se ejecutan en la unidad entera.
El siguiente código corresponde a un bucle en el que se recorres un vector de números entero A y dos
vectores de valores en coma flotante X e Y. En cada iteración del bucle, y en función del contenido de A[i],
se incrementa X[i] o se decrementa Y[i] con una constante almacenada en F2:
La representación en forma de código intermedio del cuerpo del bucle es la siguiente:

Lo que da lugar al diagrama de flujo de control siguiente:

El compilador, en base a una técnica cualquiera ha considerado que la secuencia de ejecución más probable
es la que corresponde a A[i] == 0 lo que implica ejecutar la rama que produce el incremento de los
elementos de X.

El programa VLIW que se obtendría de codificar el código intermedio es el siguiente:


- Compactación.

Se usa para generar código en el que se minimice el número de operaciones vacías en las instrucciones
VLIW y se reduzcan los ciclos de ejecución en beneficio de las rutas de ejecución más probables. El
compilador efectúa desplazamientos de las operaciones siendo los saltos los que introducen los mayores
impedimentos ya que constituyen puntos de entrada y de salida de la traza.

Por muy probable que sea la ruta decidida por el compilador, siempre se puede presentar la otra alternativa,
por lo que será necesario tenerlo en cuenta al reorganizar el código.

Ejemplo: Siguiendo con el ejemplo anterior.

Se aprecia que la ejecución de la rama supuestamente más probable del if (A[i] == 0) conlleva a la ejecución
de las instrucciones 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 17 mientras que la menos probable ( A[i] <> 0) provocaría
la ejecución de las instrucciones 1, 2, 3, 4, 10, 11, 12, 13, 14, 15, 16, 17. En resumen, la ejecución de una
iteración del bucle consumiría siempre 12 ciclos con independencia de cual fuese la rama del if que tuviese
mayor probabilidad de ser ejecutada.

Algunos de los posibles desplazamientos de operaciones que se pueden realizar dentro de una traza son los
siguientes:
Las consideraciones que debe realizar el compilador para desplazar operaciones dentro de una traza son las
siguientes:

- Conocer cuál es la secuencia de ejecución más probable.


- Conocer las dependencias de datos existentes para garantizar su mantenimiento.
- La cantidad de código de compensación que es necesario añadir.
- Saber si compensa el desplazamiento de operaciones dentro de la traza, midiéndose el coste tanto en
ciclos de ejecución como en espacio de almacenamiento.

Ejemplo: Siguiendo con el ejemplo anterior.

Se puede apreciar que es posible aplicar el desplazamiento de operaciones correspondiente al bloque B1


de la primera de las opciones. El desplazamiento correspondería a las operaciones relacionadas con la
expresión X[i] := X[i] + a por considerarse la ruta de ejecución más probable. Por lo tanto, el código de
compensación necesario, bloque -B1, sería la expresión contraria, X[i] := X[i] – a. Aplicando esta
transformación al código original, el nuevo código intermedio es el siguiente:

El nuevo código VLIW generado a partir del código intermedio ya planificado es el siguiente:
Al igual que en el código VLIW original se han aprovechado los huecos de retardo que presentan las
instrucciones de salto para ubicar las instrucciones de incremento de los índices.
La secuencia de ejecución de las instrucciones del código planificado considerando la ruta de ejecución
más probable ha pasado a ser 1, 2, 3, 4, 5, 6, 7, 8, 15, 16, lo que implica un consumo de 10 ciclos. Si se
recorre la ruta menos probable, las instrucciones pasan a ser 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16,
lo que equivale a un consumo de 15 ciclos.
El tiempo medio de ejecución del código planificado será mejor que el código original siempre que se
cumpla:

[[10 𝑐𝑖𝑐𝑙𝑜𝑠 · 𝑝 + 15 𝑐𝑖𝑐𝑙𝑜𝑠 · (1 − 𝑝)] < [12 𝑐𝑖𝑐𝑙𝑜𝑠 · 𝑝 + 12 𝑐𝑖𝑐𝑙𝑜𝑠 (1 − 𝑝)]]

donde p es la probabilidad de que la rama (A[i] == 0) sea la ejecutada.

Uno de los problemas que presenta la planificación de trazas es que, a mayor cantidad de operaciones desplazadas,
mayor es la penalización en que se incurre cuando se realiza una predicción errónea.

Muchas de las optimizaciones que se pueden aplicar de forma manual no se realizan de forma automática por el
compilador ya que requieren técnicas muy complejas de implementar y que pueden producir grandes penalizaciones
si la traza seleccionada no constituye la ruta más frecuente.

3.7 OPERACIONES CON PREDICADO, CON GUARDA O CONDICIONADAS


Tienen el objetivo de poder tratar las instrucciones condicionales como cualquier otra instrucción. La utilización de
las operaciones con predicado permite al compilador reducir el número de saltos condicionales que hay en el código
de forma que un diagrama de flujo de control compuesto por diferentes ramas con pequeños bloques básicos pueda
transformarse en un único bloque básico extendido y aplicarle técnicas de planificación local. Al proceso de eliminar
los saltos condicionales de un programa y reemplazarlos por instrucciones con predicados se conoce con el termino
if-conversión.

Una operación con predicado es una instrucción en la que su resultado se almacena o se descarta dependiendo del
valor de un operando que tiene asociado (predicado), que se implementa como un registro de 1 bit que se añade como
un nuevo operando de lectura.
Según el valor de este registro cada una de las operaciones que forman la instrucción VLIW almacenara su resultado
o lo abandonara. Una representación habitual para indicar que una instrucción de código intermedio tiene asociado
un predicado es:

𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛(𝑝)

donde si p vale 1 indica que el resultado de la operación que realice la instrucción se almacenara y lo contrario en
caso de que valga 0. Un formato para las instrucciones de manipulación de predicados podría ser el siguiente:
Predicado Acción
% p1 := false
PRED_CLEAR p1, p2
% p2 := false
% p1 := (reg == valor)
PRED_EQ p1, p2, reg, valor
% p2 := NOT(p1)
% p1 := (reg <> valor)
PRED_NE p1, p2, reg, valor
% p2 := NOT(p1)
% p1 := (reg < valor)
PRED_LT p1, p2, reg, valor
% p2 := NOT(p1)
% p1 := (reg > valor)
PRED_GT p1, p2, reg, valor
% p2 := NOT(p1)

donde la utilización de p2 es opcional. Solo están permitidas las combinaciones true/false, false/true y false/false.
Esta última es útil en estructuras condicionales tipo if-then-else anidadas en donde las instrucciones de ambas ramas
puede que no tengan que ser ejecutadas. Es posible incluso asignar un predicado a las operaciones de manipulación
de predicados.

Ejemplo: Se dispone de un procesador VLIW con 3 unidades funcionales: una para las operaciones de
carga/almacenamiento de 2 ciclos, una para enteros de 1 ciclo y otra para operaciones en coma flotante de 3 ciclos.
Las operaciones de salto y las operaciones para la manipulación de predicados se ejecutan en la unidad entera.

Las instrucciones VLIW del código intermedio sin considerar predicados son:
Las instrucciones VLIW del código intermedio considerando predicados son:

La aplicación de la técnica if-conversión a un conjunto de bloques básicos que contienen varias rutas de ejecución
condicionales tiene por objetivo generar un único bloque con instrucciones condicionadas.

Las operaciones con predicado son adecuadas para eliminar saltos incondicionales difíciles de predecir,
especialmente aquellos que no forman parte de un bucle como las estructuras if-then. Las operaciones condicionadas
siempre se ejecutan, pero si su predicado sigue sin cumplirse al concluir su procesamiento, el resultado se desecha.

Un uso excesivo de este tipo de operaciones puede producir una degradación en el rendimiento del procesador ya
que se consumen recursos, pero puede que no cambien el estado de la máquina. Si todas las rutas de ejecución tienen
el mismo tamaño, la eliminación de las estructuras if-then mediante instrucciones condicionadas es una técnica muy
efectiva, pero si las rutas presentan grandes diferencias, el rendimiento se reduce.

3.8 TRATAMIENTO DE EXCEPCIONES


El tratamiento de excepciones se usa para garantizar la consistencia del procesador y de la memoria frente a la
ejecución especulativa de operaciones.
Una de las estrategias aplicadas es la utilización de centinelas, que es una técnica análoga al buffer de reordenamiento
de los procesadores superescalares y consiste en que el compilador marca las operaciones especulativas con una
etiqueta y en el lugar del programa en el que estaba el codigo especulado que ha sido desplazado sitúa un centinela
vinculado a esa etiqueta.
Cuando una operación marcada se ejecuta, su resultado se almacena o marca como temporal y al estar el codigo del
centinela en la posición del codigo especulado, la ejecución del centinela indica que las operaciones ya no son
especulativas.

Esta estrategia se implementa mediante una especie de buffer de terminación en el que las instrucciones se retiran
cuando les corresponde salvo las marcadas como especulativas, que se retirarán cuando lo señale la ejecución del
centinela. Si una instrucción posterior lanza una excepción no habrá que deshacer el resultado de ninguna operación
especulada sino eliminarla del buffer.

3.9 EL ENFOQUE EPIC


El aumento del nivel de paralelismo a nivel de instrucción mediante un enfoque VLIW puro presenta una serie de
problemas que constituyen un impedimento para que procesadores de propósito general basados en esta concepción
arquitectónica representen una opción alternativa a los actuales procesadores superescalares. Estos problemas son:

- Los repertorios de instrucciones VLIW no son compatibles entre diferentes implementaciones.

- Las instrucciones de carga representan comportamientos no determinísticos por la propia naturaleza del
sistema de memoria.

- La importancia de disponer de un compilador que garantice una planificación optima del codigo de forma que
se maximice el rendimiento del computador y se minimice el tamaño del codigo.

Para superar tales inconvenientes surgió el estilo de arquitectura EPIC (Explicit Parallel Instruction Computing), que
constituye una evolución del enfoque VLIW en el que se han absorbido varios conceptos del ámbito de procesadores
superescalares.

Las características arquitectónicas más destacadas del estilo EPIC son:

- Planificación estática con paralelismo explícito.


- Operaciones con predicado.
- Descomposición o factorización de las instrucciones de salto condicional.
- Especulación de control.
- Especulación de datos.
- Control de la jerarquía de memoria.

Las diferencias principales entre las arquitecturas superescalar, VLIW y EPIC son las siguientes:
Agrupamiento de operaciones Asignación de UF Secuencia de emisión a las UF
Superescalar Hardware Hardware Hardware
EPIC Compilador Hardware Hardware
VLIW Compilador Compilador Compilador

3.10 PROCESADORES VECTORIALES


Son maquinas diseñadas en base a una arquitectura que permite la manipulación de vectores. Para poder
manipularlos, estos procesadores cuentan con un repertorio de instrucciones en el que los operadores fuente y destino
son vectores almacenados en unos registros vectoriales. Las operaciones vectoriales pueden tener como fuente dos
operandos vectoriales o uno vectorial y uno escalar, pero el resultado siempre es un operando vectorial. Estos
procesadores tienen tres grandes ventajas:

- Pueden utilizar unidades funcionales vectoriales con segmentaciones muy profundas sin tener que preocuparse
por la existencia de dependencias de datos.
- Una única instrucción vectorial equivale a un bucle completo de instrucciones escalares. Esto tiene dos
consecuencias:

- El ancho de banda de instrucciones es menor y no es necesario extraer tantas instrucciones de la I-caché.


- No existen riesgos de control al eliminarse las instrucciones de salto condicional.

- Los accesos a memoria siguen un patrón fijo. Los elementos de los vectores están ordenados, con lo que es
posible amortizar el coste de acceso directo a la memoria principal.

El compilador es el encargado de detectar y extraer el paralelismo intrínseco que existe entre las instrucciones que
componen el código fuente.

3.11 ARQUITECTURA VECTORIAL BÁSICA

Consta de una unidad de procesamiento vectorial y una escalar. Según el código de operación de la instrucción
indique si se trata de una operación escalar o vectorial, se activarían unas unidades funcionales u otras.

- Unidad de procesamiento escalar: Compuesta por el fichero de registros escalares y por las unidades funcionales.
Su organización y funcionamiento es similar al de cualquier procesador escalar.

- Unidad de procesamiento vectorial: Está compuesta por:

- Fichero de registros vectoriales: Compuesto por un conjunto de entradas en donde cada una almacena los
elementos de que consta un vector. El número de registros oscila entre 64 y 256, el número de elementos por
registro de 8 a 256 y el tamaño de cada elemento suele ser de 8 bytes. Se necesita disponer de multitud de
puertos de lectura y escritura por registro para poder mantener el suministro continuo de datos a todas las
unidades funcionales.

- Unidades funcionales vectoriales: Están segmentadas y pueden iniciar una operación por ciclo. Las hay enteras
y de punto flotante. Pueden estar internamente organizadas en varios lanes o carriles con el objeto de aumentar
el número de operaciones que pueden procesar en paralelo.
Un problema es que hay que incrementar el número de puertos de lectura y escritura de todos los registros
vectoriales para poder suministrar en cada ciclo de reloj elementos a los carriles.

- Unidad funcional de carga/almacenamiento vectorial: Puede estar segmentada y se ocupa de cargar los
registros vectoriales con datos extraídos de la memoria y de almacenar en ella el contenido de los mismos.

Algunos de los procesadores vectoriales comercializados son los siguientes:


Procesador Año Reloj Registros Elementos por registro UF Lanes por UF Unidades de C/A
Cray-1 1976 80 8 64 6 1 1
Cray-2 1985 244 8 64 5 1 1
Cray Y-MP 1988 166 8 64 8 1 2C, 1A
Cray C-90 1991 240 8 128 8 2 4
Convex C-4 1994 135 16 128 3 1 4
Cray T-90 1996 460 8 128 8 2 4
NEC SX/5 1998 312 8-64 512 4 16 1
VPP500 1999 300 8-256 4096-128 3 16 1C, 1A
Cray SV1 1998 300 8 64 8 1 1+1A
Cray SV1ex 2001 500 8 64 8-2 2-8 1+1A
VMIPS 2001 500 8 64 5 1 1
NEC SX/6 2001 500 72 32 5 1 1
Cray X1 2002 800 32 64 4 2 ?
NEC SX/8 2004 2200 72 64 5 1 1
NEC SX/9 2008 3300 72 64 6 2 1

3.12 REPERTORIO GENÉRICO DE INSTRUCCIONES VECTORIALES


Instrucciones Significado
ADDV Vi, Vj, Vk Almacena en Vi el resultado de sumar los elementos de Vj y Vk
ADDSV Vi, Vj, Fi Almacena en Vi el resultado de sumar Fi a cada elemento de Vj
SUBV Vi, Vj, Vk Almacena en Vi resultado de restar los elementos de Vk a los de Vj
SUBVS Vi, Vj, Fi Almacena en Vi resultado de restar Fi a cada elemento de Vj
SUBSV Vi, Fi, Vj Almacena en Vi resultado de restar cada elemento de Vj a Fi
MULTV Vi, Vj, Vk Almacena en Vi resultado de multiplicar los elementos de Vj y Vk
MULTSV Vi, Vj, Fi Almacena en Vi resultado de multiplicar Fi por cada elemento de Vj
DIVV Vi, Vj, Vk Almacena en Vi resultado de dividir los elementos de Vj por los de Vk
DIVVS Vi, Vj, Fi Almacena en Vi resultado de dividir los elementos de Vj por Fi
DIVSV Vi, Fi, Vj Almacena en Vi resultado de dividir Fi por los elementos de Vj
LV Vi, Ri Carga en Vi los elementos ubicados en memoria a partir de M[Ri]
SV Ri, Vi Almacena los elementos de Vi a partir de M[Ri]
Ejemplo: Bucle DAXPY (Double precisión A times X Plus Y) para mostrar las diferencias entre código escalar y
vectorial para la operación 𝑌(𝑖) = 𝑎 · 𝑋(𝑖) + 𝑌(𝑖) con vectores de elementos de 8 bytes:

El código para un procesador escalar es el siguiente:

Se ejecutan 2 + 9 · 64 = 578 instrucciones. El código vectorial equivalente es:

Donde se ejecutan solo 6 instrucciones.

El código vectorial sufre menos detenciones ya que solo se detiene una vez por operación.

Se definen los siguientes dos registros:

- VLR: Controla la longitud de cualquier operación vectorial.


- VM: Vector de longitud MVL (Maximum Vector Length). Las operaciones vectoriales se aplican al elemento
i del vector, si y solo si el bit i-ésimo de VM es 1.

Para resolver las siguientes cuestiones:

- El tamaño del vector a procesar es mayor que el valor MVL: El compilador recurre a una técnica denominada
strip mining (troceado del vector), que consiste en procesar un vector de longitud mayor al MVL en secciones
de longitud igual o inferior al MVL.

Para poder manipular el VLR, los procesadores vectoriales cuentan con algún tipo de instrucciones especiales:
Instrucciones Significado
MOVI2S VLR, Ri Almacena en VLR el contenido del registro escalar Ri
MOVS2I Ri, VLR Almacena en el registro escalar Ri el contenido de VLR
- Ubicar en memoria los elementos de un vector de forma no consecutiva: Surge cuando hay que almacenar
estructuras de datos que presentan dimensiones superiores a la unidad.

Ejemplo: Multiplicar dos matrices de 100x100 almacenadas en los arrays A y B:

Dependiendo de si el almacenamiento se realiza por filas o columnas, el patrón de ubicación de los elementos
de los arrays bidimensionales A, B y C variará:

Para solucionar el problema de cargar y almacenar datos que se encuentran ubicados en memoria de forma no
consecutiva se dispone de instrucciones especiales de carga y almacenamiento con indicación de la separación
entre datos:
Instrucciones Significado
LVWS Vi, (Ri, Rj) Carga Vi comenzando desde la posición M[Ri] con una separación de Rj
SVWS (Ri, Ri), Vi Almacena Vi a partir de la posición M[Ri] con una separación de Rj

El contenido del registro Ri es la posición del primer dato y Rj mantiene la separación en memoria que existe
entre los datos restantes.

- Vectorizar bucles en cuyo cuerpo hay instrucciones ejecutadas condicionalmente: Se recurre a una máscara de
MVL de longitud almacenada en VM. Existen instrucciones especiales para gestionar el contenido de este
registro.
Instrucciones Significado
Compara (EQ, NE, GT, LT, GE, LE) elemento a elemento el contenido de Vi y Vj y
S_ _V Vi, Vj
el resultado se almacena en VM
S_ _SV Fi, Vi Similar al anterior, pero utilizando el valor escalar Fi
RVM Inicializa a 1 todos los bits de VM
MOVF2S VM, Fi Almacena en VM el contenido del registro Fi
MOVS2F Fi, VM Almacena en Fi el contenido de VM

Ejemplo:
Dado que existe una instrucción con ejecución condicionada, es necesario recurrir a la utilización del registro
de mascara vectorial, colocando previamente a 0 en el VM los bits correspondientes a los elementos de A[i] que
son iguales a 0.

Inconvenientes:

- Puede que el tiempo de procesamiento no se reduzca pese a que no se realicen las operaciones.
- Puede ocurrir que el enmascaramiento afecte únicamente al almacenamiento del resultado, pero no evite
la operación con lo que se podría dar lugar a la aparición de excepciones.

3.13 MEDIDA DEL RENDIMIENTO DE UN FRAGMENTO DE CÓDIGO


VECTORIAL
El tiempo de ejecución de una instrucción es:

𝑇𝑛 = 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 + 𝑛 · 𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜

donde n es el número de elementos en cada vector, Tarranque es la latencia de producir el primer resultado y Telemento es
el tiempo que se tarda en calcular el resto de elementos.
Con la excepción de la unidad funcional de carga/almacenamiento, en las restantes unidades funcionales, el tiempo
de arranque es similar al número de segmentos de que constan.

Ejemplo: Calcular el tiempo expresado en ciclos que tarda en procesarse una multiplicación de dos vectores de 64
elementos en una unidad funcional compuesta de 10 etapas donde cada etapa consume 1 ciclo:

𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 = 10 𝑐𝑖𝑐𝑙𝑜𝑠
𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜 = 1 𝑐𝑖𝑐𝑙𝑜
𝑇64 = 10 𝑐𝑖𝑐𝑙𝑜𝑠 + 64 𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜𝑠 · 1 𝑐𝑖𝑐𝑙𝑜 = 74 𝑐𝑖𝑐𝑙𝑜𝑠

Si no existen dependencias verdaderas y no hay riesgos estructurales, varias instrucciones pueden planificarse y
ejecutarse sin ningún tipo de penalización formando lo que se conoce cono convoy o paquete.
En caso de existan dependencias verdaderas o estructurales entre instrucciones será necesario esperar a que los
operandos estén disponibles o a que la unidad funcional se encuentre libre para poder formar varios convoyes.

Además del Tn, otra medida que permite expresar el rendimiento de un procesador vectorial es el número de
operaciones en coma flotante FLOP realizadas por ciclo:

𝑂𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑒𝑛 𝑐𝑜𝑚𝑎 𝑓𝑙𝑜𝑡𝑎𝑛𝑡𝑒 · 𝑛 𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜𝑠


𝑅𝑛 =
𝑇𝑛
Ejemplo: Se utiliza una unidad de suma y otra de multiplicación con tiempos de arranque de 6 y 7 ciclos
respectivamente, y una longitud de vector de 64.

Ejemplo: Se utiliza una unidad de suma y otra de multiplicación con tiempos de arranque de 6 y 7 ciclos
respectivamente, y una longitud de vector de 64.

A mayor valor de Rn, mejor será el rendimiento obtenido al ejecutar el código vectorial.

Una técnica que permite mejorar el rendimiento es el encadenamiento de resultados entre unidades funcionales, que
permite que una unidad funcional pueda comenzar a operar tan pronto como los resultados de la unidad funcional de
que depende estén disponibles.
Si se permite encadenamiento, dentro de un convoy pueden existir instrucciones dependientes, pero nunca deben
existir riesgos estructurales entre ellas.

Ejemplo:

La unidad de multiplicación puede comenzar a operar en cuanto el primer resultado de la unidad de suma está
disponible.

El encadenamiento permite reducir el tiempo por elemento gracias al solapamiento, pero no asi los tiempos de
arranque.
Otra posibilidad es permitir la ejecución solapada de diferentes convoyes, lo que implica que una instrucción
vectorial pueda comenzar a utilizar la unidad funcional antes de que una operación previa haya concluido. Esto
complica mucho la lógica de emisión, pero permite ocultar los tiempos de arranque para todos los convoyes excepto
el primero.

Ejemplo: 2 convoyes con encadenamiento entre unidades funcionales y sin solapamiento.

Ejemplo: 2 convoyes con encadenamiento y con solapamiento.

3.14 LA UNIDAD FUNCIONAL DE CARGA/ALMACENAMIENTO


VECTORIAL
Es el elemento hardware más crítico en un procesador vectorial. Debe ser capaz de poder intercambiar datos con los
registros vectoriales de forma sostenida y a una velocidad igual o superior a Telemento.

El tiempo de arranque es el tiempo de acceso a memoria y el tiempo por elemento es el número de ciclos que se
consumen en transferir el dato ya disponible en el banco de memoria al registro vectorial o viceversa.
Existen dos formas de realizar el solapamiento de las latencias de acceso a los n datos de un vector:

- Síncrona: Implica solicitar simultáneamente un dato a los m bancos cada Tn ciclos.

- Asíncrona: Implica solicitar los elementos de que consta el vector a cada uno de los m bancos de forma
periódica con periodo Ta con un desfase entre bancos consecutivos de Telemento ciclos.

Para que todo funcione correctamente las palabras que componen un vector deben estar distribuidas correctamente
entre los bancos de memoria, que tienen un ancho de palabra de 8 bytes, se direccionan por bytes y son un número
que es potencia de 2.
Dada una dirección de memoria, el número de banco en que se encuentra está determinado por los bits de orden
inferior de la dirección de memoria teniendo en cuenta que la distancia entre datos es de 8 bytes. Despreciando los 3
bits de orden inferior al direccionar datos de 8 bytes, los 3 bits siguientes determinan el banco de memoria en que se
encuentra la dirección de memoria que se pretende leer.

3.15 MEDIDA DEL RENDIMIENTO DE UN BUCLE VECTORIZADO


Para vectorizar un bucle, un compilador aplica la técnica strip mining y produce una mezcla de código escalar y
vectorial en el que el procesamiento del bucle se realiza por secciones o trozos.
Ejemplo: El siguiente pseudo-código muestra las operaciones que son necesarias para vectorizar el bucle DAXPY
de n elementos con la técnica strip mining:

donde n contiene el número de elementos del vector

Los costes de ejecucion de las instrucciones vectoriales y escalares obtenidas de la vectorizacion del bucle se pueden
desglosar en cuatro componentes:

- Tbase: Es el tiempo que consumen las instrucciones escalares de preparacion antes de abordar el bucle exterior.

- Tbucle: Costes derivados de ejecutar en cada iteracion del bucle exterior las instrucciones escalares necesarias
para realizar el seccionamiento.

- Tarranque: Suma de los tiempos de arranque visibles de las unidades funcionales que se utilizan en cada convoy
de instrucciones:

- Telemento: Es el numero de convoyes en que se organizan las instrucciones vectoriales que se derivan del bucle
interior.

La expresion que permite determinar el tiempo total de ejecucion es:

𝑛
𝑇𝑛 = 𝑇𝑏𝑎𝑠𝑒 + 𝑐𝑒𝑖𝑙 ( ) · (𝑇𝑏𝑢𝑐𝑙𝑒+ 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 ) + 𝑛 · 𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜
𝑀𝑉𝐿

La velocidad de procesamiento de un bucle de longitud infinita expresada en FLOP por ciclo es:

𝑂𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑣𝑒𝑐𝑡𝑜𝑟𝑖𝑎𝑙𝑒𝑠 · 𝑛
𝑅∞ = lim ( )
𝑛→∞ 𝑇𝑛

Ejemplo: Análisis de rendimiento de un procesador vectorial al ejecutar el código obtenido de vectorizar el bucle
DAXPY para vectores de n elementos. El procesador consta de una unidad de suma de 6 ciclos de latencia, una
unidad de multiplicación de 7 ciclos, una unidad de carga/almacenamiento de 12 ciclos, MVL es 64 y la frecuencia
de reloj es 500 MHz. El fragmento de código que se genera para realizar las operaciones 𝑌(𝑖) = 𝑎 · 𝑋(𝑖) + 𝑌(𝑖) es:

y los costes debidos a las instrucciones escalares son Tbase = 10 ciclos y Tbucle = 15 ciclos.
Caso 1: Sin encadenamiento de resultados entre unidades.

Si se considera que VLR es 64, la secuencia de ejecución de los 4 convoyes es:

𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜 = 4 𝑐𝑖𝑐𝑙𝑜𝑠
𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 = 2 · 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐿𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐴𝐷𝐷𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝑆𝑉 = 2 · 12 + 6 + 12 = 42 𝑐𝑖𝑐𝑙𝑜𝑠

Para el caso particular de 1000 elementos:

1000
𝑇1000 = 𝑇𝑏𝑎𝑠𝑒 + 𝑐𝑒𝑖𝑙 ( ) · (𝑇𝑏𝑢𝑐𝑙𝑒+ 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 ) + 1000 · 𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜
𝑀𝑉𝐿
1000
= 10 + 𝑐𝑒𝑖𝑙 ( ) · (15 + 42) + 1000 · 4 = 10 + 16 · 57 + 4000 = 4922 𝑐𝑖𝑐𝑙𝑜𝑠
64

El rendimiento expresado en FLOP por ciclo es:

𝑂𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑣𝑒𝑐𝑡𝑜𝑟𝑖𝑎𝑙𝑒𝑠 · 𝑛
𝑅∞ = lim ( )
𝑛→∞ 𝑇𝑛
2·𝑛
= lim ( 𝑛 ) = 0.409 𝐹𝐿𝑂𝑃/𝑐𝑖𝑐𝑙𝑜
𝑛→∞
10 + 𝑐𝑒𝑖𝑙 ( + 1) · (15 + 42) + 𝑛 · 4
64

1000 1000
Se ha sustituido la expresión 𝑐𝑒𝑖𝑙 ( ) por 𝑐𝑒𝑖𝑙 ( + 1) para simplificar los cálculos.
64 64

𝑅∞ = 0.409𝐹𝐿𝑂𝑃 ⁄𝑐𝑖𝑐𝑙𝑜 · 500 · 106 𝐻𝑧 = 204.5 𝑀𝐹𝐿𝑂𝑃𝑆

Caso 2: Con encadenamiento de resultados entre unidades.


𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜 = 3 𝑐𝑖𝑐𝑙𝑜𝑠
𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 = 2 · 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐿𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝑀𝑈𝐿𝑇𝑆𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐴𝐷𝐷𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝑆𝑉 = 2 · 12 + 7 + 6 + 12
= 49 𝑐𝑖𝑐𝑙𝑜𝑠

Para el caso particular de 1000 elementos:

1000
𝑇1000 = 𝑇𝑏𝑎𝑠𝑒 + 𝑐𝑒𝑖𝑙 ( ) · (𝑇𝑏𝑢𝑐𝑙𝑒+ 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 ) + 1000 · 𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜
𝑀𝑉𝐿
1000
= 10 + 𝑐𝑒𝑖𝑙 ( ) · (15 + 49) + 1000 · 3 = 10 + 16 · 64 + 3000 = 4034 𝑐𝑖𝑐𝑙𝑜𝑠
64

El rendimiento expresado en FLOP por ciclo es:

𝑂𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑣𝑒𝑐𝑡𝑜𝑟𝑖𝑎𝑙𝑒𝑠 · 𝑛 2·𝑛


𝑅∞ = lim ( ) = lim ( 𝑛 ) = 0.5 𝐹𝐿𝑂𝑃/𝑐𝑖𝑐𝑙𝑜
𝑛→∞ 𝑇𝑛 𝑛→∞
10 + 𝑐𝑒𝑖𝑙 ( + 1) · (15 + 49) + 𝑛 · 3
64

1000 1000
Se ha sustituido la expresión 𝑐𝑒𝑖𝑙 ( ) por 𝑐𝑒𝑖𝑙 ( + 1) para simplificar los cálculos.
64 64

𝑅∞ = 0.5𝐹𝐿𝑂𝑃 ⁄𝑐𝑖𝑐𝑙𝑜 · 500 · 106 𝐻𝑧 = 250 𝑀𝐹𝐿𝑂𝑃𝑆

Caso 3: Con encadenamiento y dos unidades de carga/almacenamiento:

𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜 = 2 𝑐𝑖𝑐𝑙𝑜𝑠
𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 = 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐿𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝑀𝑈𝐿𝑇𝑆𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐴𝐷𝐷𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝑆𝑉 = 12 + 7 + 6 + 12 = 37 𝑐𝑖𝑐𝑙𝑜𝑠

Para el caso particular de 1000 elementos:

1000
𝑇1000 = 𝑇𝑏𝑎𝑠𝑒 + 𝑐𝑒𝑖𝑙 ( ) · (𝑇𝑏𝑢𝑐𝑙𝑒+ 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 ) + 1000 · 𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜
𝑀𝑉𝐿
1000
= 10 + 𝑐𝑒𝑖𝑙 ( ) · (15 + 37) + 1000 · 2 = 10 + 16 · 52 + 2000 = 2842 𝑐𝑖𝑐𝑙𝑜𝑠
64
El rendimiento expresado en FLOP por ciclo es:

𝑂𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑣𝑒𝑐𝑡𝑜𝑟𝑖𝑎𝑙𝑒𝑠 · 𝑛
𝑅∞ = lim ( )
𝑛→∞ 𝑇𝑛
2·𝑛
= lim ( 𝑛 ) = 0.711 𝐹𝐿𝑂𝑃/𝑐𝑖𝑐𝑙𝑜
𝑛→∞
10 + 𝑐𝑒𝑖𝑙 ( + 1) · (15 + 37) + 𝑛 · 2
64

1000 1000
Se ha sustituido la expresión 𝑐𝑒𝑖𝑙 ( ) por 𝑐𝑒𝑖𝑙 ( + 1) para simplificar los cálculos.
64 64

𝑅∞ = 0.711𝐹𝐿𝑂𝑃 ⁄𝑐𝑖𝑐𝑙𝑜 · 500 · 106 𝐻𝑧 = 355.55 𝑀𝐹𝐿𝑂𝑃𝑆

Caso 4: Con encadenamiento, dos unidades de carga/almacenamiento y solapamiento entre convoyes dentro
de la misma iteración.

𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 = 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐿𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝑀𝑈𝐿𝑇𝑆𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝐴𝐷𝐷𝑉 + 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒𝑆𝑉 = 12 + 7 + 6 + 12 = 37 𝑐𝑖𝑐𝑙𝑜𝑠


𝑇64 − 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 140 − 37
𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜 = = = 1.6 𝑐𝑖𝑐𝑙𝑜𝑠
64 64

Para el caso particular de 1000 elementos:


1000
𝑇1000 = 𝑇𝑏𝑎𝑠𝑒 + 𝑐𝑒𝑖𝑙 ( ) · (𝑇𝑏𝑢𝑐𝑙𝑒+ 𝑇𝑎𝑟𝑟𝑎𝑛𝑞𝑢𝑒 ) + 1000 · 𝑇𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜
𝑀𝑉𝐿
1000
= 10 + 𝑐𝑒𝑖𝑙 ( ) · (15 + 37) + 1000 · 1.6 = 10 + 16 · 52 + 1600 = 2202 𝑐𝑖𝑐𝑙𝑜𝑠
64

El rendimiento expresado en FLOP por ciclo es:

𝑂𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑣𝑒𝑐𝑡𝑜𝑟𝑖𝑎𝑙𝑒𝑠 · 𝑛
𝑅∞ = lim ( )
𝑛→∞ 𝑇𝑛
2·𝑛
= lim ( 𝑛 ) = 0.918 𝐹𝐿𝑂𝑃/𝑐𝑖𝑐𝑙𝑜
𝑛→∞
10 + 𝑐𝑒𝑖𝑙 ( + 1) · (15 + 37) + 𝑛 · 1.6
64

1000 1000
Se ha sustituido la expresión 𝑐𝑒𝑖𝑙 ( ) por 𝑐𝑒𝑖𝑙 ( + 1) para simplificar los cálculos.
64 64

𝑅∞ = 0.918 𝐹𝐿𝑂𝑃 ⁄𝑐𝑖𝑐𝑙𝑜 · 500 · 106 𝐻𝑧 = 459 𝑀𝐹𝐿𝑂𝑃𝑆


TEMA 4 PROCESAMIENTO PARALELO
4.1 TIPOS DE PLATAFORMAS DE COMPUTACIÓN PARALELA
La estructura de los tipos de plataformas de computación paralela depende de:

- Organización lógica: Se refiere a la visión que el programador tiene de la plataforma (estructura de control y
modelo de comunicación).

- Organización física: Se refiere a la estructura del hardware que requiere la plataforma. Existen dos modelos
para impleméntalo:

- Sistemas de memoria compartida: Un único sistema de memoria física es compartido por todos los
procesadores.

- Sistemas de memoria distribuida: Cada procesador tiene su propia memoria física.

4.1.1 Organización basada en la estructura de control

Las aplicaciones paralelas se pueden clasificar en algunos paradigmas de programación claramente establecidos.
El paralelismo debido a la estructura de la aplicación se denomina paralelismo funcional, en el que las diferentes
partes de un programa pueden realizar distintas tareas de una manera concurrente y cooperativa, aunque el
paralelismo se puede controlar también en la estructura de datos, lo que se denomina paralelismo a nivel de
datos o estructural.

Los paradigmas más comunes son:

- Descomposición iterativa: Algunas aplicaciones están basadas en la ejecución de un lazo donde cada
iteración se puede realizar de forma independiente.

- Paralelismo algorítmico: Se centra en paralelizar el flujo de datos de entrada.

- Descomposición geométrica: El dominio del problema se divide en pequeños subdominios y cada


procesador se ejecuta en una parte del subdominio.

- Descomposición especulativa: Se intenta N técnicas de solución y N – 1 de ellas se eliminan cuando una


devuelve una respuesta correcta.

- Descomposición funcional: La aplicación se divide en distintas fases y cada una ejecuta una parte
diferente del algoritmo.

- Maestro/esclavo: El maestro es el responsable de descomponer el problema entre sus esclavos y de recoger


los resultados para ordenarlos.

- SPMD: Cada procesador ejecuta el mismo código, pero sobre distintas partes de los datos.

- Descomposición recursiva: El problema se divide en subproblemas que se resuelven de forma


independiente para combinar sus resultados.
4.1.1.1 Paradigma Maestro/Esclavo

El maestro es el responsable de la descomposición del problema en pequeñas tareas, de distribuirlas entre


los procesadores esclavos y de recoger los resultados para ordenarlos y obtener el resultado final. La
comunicación solo tiene lugar entre el maestro y los esclavos.

4.1.1.2 Paradigma SPMD (Single Program Multiple Data)

Cada procesador ejecuta básicamente el mismo código, pero sobre distintas partes de los datos. A diferencia
del paradigma maestro/esclavo, en este modelo la comunicación se establece entre esclavos.

4.1.2 Organización basada en el modelo de comunicación

Existen principalmente dos modelos de comunicación de información entre tareas paralelas:

- El espacio de direcciones único y compartido (memoria compartida).


- El paso de mensajes.
4.1.2.1 Espacio de direcciones compartido

Los procesadores de los sistemas con memoria


compartida se caracterizan por compartir físicamente la
memoria. En esta arquitectura, la memoria es igualmente
accesible por todos los procesadores a través de la red de
interconexión.
Hay dos parámetros que caracterizan la velocidad de
transferencia:

- Latencia de red: Tiempo que se tarda en enviar un


mensaje.

- Ancho de banda: Numero de bits que se pueden enviar por unidad de tiempo.

En esta arquitectura, se satisface que la latencia de red es baja y el ancho de banda alto, solo si no se
encuentran varios procesadores tratando de acceder al medio utilizado para la transmisión de datos
simultáneamente.

4.1.2.2 Paso de mensajes

Consiste en el intercambio de información en forma de mensajes entre los distintos procesadores que
componen el sistema. Los elementos necesarios para describir el sistema de comunicación por medio de
mensajes son:

- Emisor y receptor: Están representados por el procesador que inicia la comunicación y el que la
recibe.

- Canal de comunicación: Lo compone la red de interconexión.

- Mensaje: Compuesto por la información enviada.

Existen cuatro operaciones básicas necesarias para establecer un mecanismo de paso de mensajes:

- Envío: Usada para enviar el mensaje.


- Recepción: Usada para recibir el mensaje.
- Identificación: Usada para identificar cada uno de los procesadores del sistema.
- Número de participantes: Usada para identificar el número de procesadores.

4.2 SISTEMAS DE MEMORIA COMPARTIDA


4.2.1 Redes de interconexión

Las operaciones deben ser transmitidas por medio de una red de interconexión, que puede utilizarse para acceder
a memoria remota o para transportar mensajes entre los diferentes procesadores.

4.2.1.1 Redes estáticas (red directa)

Son redes cuya topología queda definida de manera definitiva y estable durante la construcción de la
maquina paralela. Pueden presentar distintas topologías en función de las conexiones punto a punto que se
establezcan entre sus procesadores.
- UNIDIMENSIONALES.

- Red lineal.

Un procesador puede enviar un mensaje simultáneamente a un procesador situado a su izquierda y


otro a su derecha. Es muy simple, pero presenta problemas de comunicación cuando el número de
procesadores es elevado.

- Red en anillo.
Es una red lineal en la que se enlazan los extremos finales, que permite notables
mejoras en la comunicación.

- BIDIMENSIONALES.

- Anillo cordal.
Es una topología en anillo en la que se incrementa el número de enlaces
por nodo.

- Malla.

- Red sistólica o array sistólico.


Es una malla con conexión en diagonal entre los procesadores de un cuadrado.
- Red completamente conectada.
Cada procesador se comunica directamente con cualquier otro. Es un anillo
cordal llevado a su máxima expresión. Al incrementarse el número de enlaces,
se incrementa el coste de la red.

- Estrella.
Se dispone de un procesador que actúa como procesador central

- Redes árbol.
Tienen la desventaja de que las comunicaciones pueden verse
comprometidas en un nodo cuando el número de
procesadores es grande y se realizan comunicaciones entre
procesadores situados en los niveles superiores.

- Red de árbol grueso.

Soluciona el problema de los arboles aumentando el número de conexiones de comunicación entre los
procesadores de menor nivel.
- Mesh cuadrado.
Los procesadores forman una estructura cuadrada con igual número de
procesadores en cada dimensión. Si el número de procesadores difiere, se
denomina mesh rectangular.

- Mesh cerrada o toro.


Es una mesh cuadrada en la que los extremos se conectan directamente.

- TRIDIMENSIONALES.

- Mesh tridimensional.
Puede establecerse con los procesadores periféricos conectados o no.

- HIPERCUBO.

Son mesh multidimensionales con dos procesadores en cada dimensión, de manera que un hipercubo de
dimensión d está constituido por p = 2d procesadores.

Los hipercubos pueden construirse de forma recursiva teniendo en cuenta que un hipercubo de dimensión
0 consta de 1 único procesador, uno de dimensión 2, se forma conectando dos hipercubos de dimensión 0
y asi sucesivamente de manera que un hipercubo de dimensión L se forma conectando los procesadores
correspondientes a dos hipercubos de dimensión L – 1.
Presentan las siguientes propiedades:

- Dos procesadores se conectan entre si, si y solo si sus etiquetas, en binario, tienen exactamente un
bit distinto en una posición determinada.

- Un procesador de un hipercubo de dimensión d se conecta con d procesadores.

- Todo hipercubo de dimensión d puede dividirse en dos de dimensión d – 1. Para ello se selecciona
la posición de un bit y se agrupan todos los procesadores que tengan un 0 en esa posición. Todos ellos
forman una partición y el resto forma la segunda partición.

- Un parámetro de especial interés es la distancia de Hamming, que se define como el número total de
posiciones de bits para los que las etiquetas de dos procesadores son diferentes. La distancia entre dos
procesadores a y b es el número de bits a 1 que hay tras el resultado de la operación a XOR b.

4.2.1.2 Caracterización de redes estáticas

- Diámetro: Es la máxima distancia entre dos procesadores cualesquiera.

- Conectividad: Medida de la multiplicidad de caminos entre dos procesadores. Los parámetros más
inmediatos para establecer la rapidez de las comunicaciones son:

- Ancho del canal: número de bits que pueden transmitirse simultáneamente.


- Velocidad del canal: Velocidad máxima con que se puede emitir por cada cable físico.
- Ancho de banda: Producto del ancho del canal por la velocidad del canal.

- Ancho de bisección: Mínimo numero de enlaces de comunicación que deben eliminarse para que la red
quede dividida en dos partes iguales.

- Coste: Numero de enlaces de comunicación o cantidad de cableado en la red.


Red Diámetro Conectividad Ancho de bisección Coste
Completamente conectada 1 𝑝−1 𝑝2 ⁄4 (𝑝(𝑝 − 1))⁄2
Estrella 2 1 1 𝑝−1
Árbol binario 2 log((𝑝 + 1)⁄2) 1 1 𝑝−1
Array lineal 𝑝−1 1 1 𝑝−1
Anillo ⌊𝑝⁄2⌋ 2 2 𝑝
Mesh bidimensional 2(√𝑝 − 1) 2 √𝑝 2(𝑝 − √𝑝)
Mesh bidimensional
2(⌊√𝑝⁄2⌋) 4 2 √𝑝 2𝑝
cerrado
Hipercubo log 𝑝 log 𝑝 𝑝⁄2 (𝑝 log 𝑝)⁄2
k-aria d-cubo cerrada 𝑑⌊𝑘 ⁄2⌋ 2𝑑 2𝑘 (𝑑−1) 𝑑𝑝
4.2.1.3 Redes dinámicas

Es la opción más recomendable cuando se quiere diseñar un sistema paralelo de propósito general. Las
redes dinámicas pueden clasificarse en los siguientes tipos:

- REDES BASADAS EN BUS.

Los nodos comparten un único medio de comunicación.


En un cierto instante de tiempo, solo un único procesador puede transmitir información por el bus. La
colisión de peticiones de acceso se soluciona usando una lógica o módulo de arbitraje, que se encarga de
asignar el acceso al bus a los diferentes procesadores que lo soliciten usando FIFO, Round Robin o LRU.

El rendimiento se puede mejorar incluyendo una cache en cada procesador ya que la mayoría de accesos
de un procesador se realizan a su memoria local.

- REDES CROSSBAR.
Permite conectar p procesadores con q elementos de memoria utilizando una matriz de conmutadores. El
número de conmutadores y sus conexiones crecen a un ritmo cuadrático según aumenta el número de
procesadores.
Es una red de tipo no-bloqueante y la latencia de comunicación entre elementos es constante.

- REDES MULTIETAPA.

Conectan dispositivos de entrada a dispositivos de salida a través de un conjunto de etapas de conmutadores


donde cada conmutador es una red de barra cruzada.

Existe una serio de etapas Gi compuestas de conmutadores conectados a las etapas adyacentes mediante
conexiones estáticas Cj. El número de etapas y los patrones de conexión entre ellas determinan la capacidad
de encaminamiento de las redes.
Los bloques básicos de construcción son los conmutadores unidireccionales, pudiendo reconfigurar
dinámicamente los modos de conmutación otorgando asi gran flexibilidad. Son redes bloqueantes.

Ejemplo: Red Omega: Se basa en la utilización de una permutación por barajamiento perfecto entre sus
etapas.

También existe un barajamiento perfecto inverso, en el que se efectúa la misma operación, pero con un
desplazamiento a la derecha.
Ejemplo: Red Baseline: Se construye recursivamente por bloques, conectando los conmutadores de la etapa
i con ambos sub-bloques de la etapa i + 1. La primera etapa se construye con un bloque de n x n entradas,
la segunda con un bloque de n/2 x n/2 y asi recursivamente hasta llegar en la última etapa a n/2 sub-bloques
de tamaño 2 x 2.

Ejemplo: Red Butterfly: Las salidas de un conmutador j se conectan a los conmutadores [i + 1, j] e


[i + 1, j XOR 2i]
4.2.1.4 Comparación del rendimiento de redes dinámicas

A pesar de ser bloqueantes, las redes multietapa son una solución aceptable teniendo en cuenta el coste y
las prestaciones de las arquitecturas bus y crossbar.

4.2.2 Protocolos de coherencia de caché

Con la utilización de las caches, se pretende conseguir reducir el tiempo de latencia de la memoria.

Un sistema de memoria es coherente si el valor devuelto por una operación de lectura sobre una dirección de
memoria es siempre el mismo valor que el almacenado por la última operación de escritura realizada sobre esa
misma dirección.
Solo se permiten dos tipos de accesos a memoria:

- Local: El procesador accede a datos privados y puede utilizar una caché local.
- Remoto: El procesador accede a datos externos que no se almacenan en caché.

Los problemas de coherencia de cache están provocados por tres factores:

- Por modificar datos compartidos: Si un procesador modifica su cache local, el contenido de la memoria
principal y las copias de las caches no serán coherentes.

- Por migración de procesos: Durante el tiempo de ejecución en un procesador dicho proceso realiza
modificaciones en los datos que usa, que son almacenadas en la caché local del procesador. Si el proceso
es intercambiado antes de que las modificaciones realizadas se actualicen en memoria principal, los datos
serán incoherentes.

- Por el uso de E/S mediante DMA: Se configuran los procesadores de E/S para que actúen directamente
sobre las caches locales de los procesadores, en lugar de sobre la memoria principal.

Hay dos opciones para solucionar los problemas de incoherencia de cache.

- Invalidar: Consiste en invalidar las copiar en las caches del dato modificado.
- Actualizar: Consiste en actualizar todas las copias en las caches del dato que se acaba de modificar.

Las principales desventajas de estas dos técnicas son que la primera requiere la espera de acceso al dato causada
por la carga del valor correcto de la variable invalidada y la segunda provoca un mayor tráfico de datos entre
los procesadores.
Para mantener la coherencia se monitoriza el número de copias existentes y el estado de cada copia. Los estados
y sus transiciones son:

- Compartido: Corresponde a una variable que ha sido cargada en las caches de varios procesadores.
- Invalido: Bloque de datos no cargado en cache.
- Sucio: Bloque de datos valido para un procesador, pero no para los demás.

Diferentes mecanismos hardware implementan el protocolo de coherencia de cache basado en invalidación:

- Sistemas snoopy o de vigilancia de bus.

El snoopy debe controlar las peticiones y avisas que le lleguen de su procesador local o del resto a
través del bus, y, en función ellas, decide el estado de los bloques de datos y genera las señales de
control adecuadas.
- Sistemas basados en directorios.

- Directorio centralizado.

Consiste en un único directorio o tabla


centralizada donde se guarda la
información sobre el lugar donde se
encuentra cada copia de la caché. Este
directorio centralizado es bastante grande
por lo que la búsqueda se realiza de forma
asociativa.

Un inconveniente es que sufre contención


por la competencia del acceso al directorio.
Otro problema son los largos tiempos de
búsqueda.

- Directorio distribuido.
En el directorio se guarda el estado de la cache asi como su
presencia. El estado es local, pero la presencia indica que
caches tienen una copia del bloque.
4.3 SISTEMAS DE MEMORIA DISTRIBUIDA
Cada procesador dispone de su propia memoria, independiente del
resto y accesible solo por su procesador. La comunicación se realiza
por paso de mensajes.
Esta arquitectura también es conocida como arquitectura de memoria
privada o arquitectura de paso de mensajes.

El paso de mensajes es la estrategia dominante en los sistemas con un gran número de procesadores (más de 100), y
es especialmente útil en entornos donde la ejecución de los programas puede dividirse en pequeños subprogramas
independientes.
Para analizar el ancho de banda es necesario atender a la granularidad del computador paralelo, que el cociente entre
el tiempo requerido para realizar una operación básico de comunicación y el tiempo requerido para realizar una
operación básica de cálculo de los procesos.

Por disminuir la granularidad de las comunicaciones se entiende minimizar el número de mensajes y maximizar su
tamaño.

Hay dos clasificaciones de sistemas con memoria distribuida:

- Procesadores masivamente paralelos (MPPs): Un único computador con múltiples CPUs comunicadas por un
bus de datos.

- Clusters: Múltiples computadores, cada uno con su propio procesador, enlazados por una red de interconexión
más o menos rápida. Pueden ser de dos tipos dependiendo de si cada computador del clúster este exclusivamente
dedicado a él (Beowulf) o no (Redes de estaciones de trabajo NOW).

Las características más relevantes de los sistemas Beowulf son las siguientes:

- Se evita el hardware innecesario a cambio de una fuerte dependencia entre el software y la topología de la red.
- Cada nodo se dedica exclusivamente a procesos del supercomputador.
- Para realizar las conexiones se usan conexiones placa a placa por cable RJ-45 cruzado.
- La programación es fuertemente dependiente de la arquitectura.
- Un sistema Beowulf es un conjunto de nodos minimalistas (constan de una placa madre, una CPU, las
memorias y algún dispositivo de comunicación) conectados por un medio de comunicación barato.

4.3.1 Consideraciones generales sobre los clusters

Los clusters presentan distintas topologías en función de las conexiones punto a punto que se establezcan entre
sus procesadores. Una implementación más natural y habitual de los clusters es como parte de una red de área
local, en la que las maquinas que forman el clúster se conectan a una red de alta velocidad y la máquina que
actúa como servidor se conecta además a la red exterior.
Se puede disponer de un Switch, por lo que cada procesador dispone del 100% del ancho de banda.

4.3.2 ¿Por qué clusters?

Las exigencias de cálculo de las aplicaciones comerciales y científicas crecen día a día. Para hacer frente a esta
demanda es preciso:

- Aumentar la capacidad de la unidad de procesamiento: Basta con aumentar la memoria o sustituir el


procesador por otro de tecnología superior y/o de mayor velocidad de reloj.
- Hacer que varias unidades colaboren para la resolución conjunta de una tarea: Hay que acelerar la
ejecución mediante pipelining, procesamiento superescalar, procesadores vectoriales o paralelismo sobre
un registro.

Las ventajas de los clusters son:

- Se pueden construir con un esfuerzo relativamente moderado.


- Son sistemas de bajo coste.
- Utilizan hardware convencional y accesible.
- Utilizan un sistema de comunicación basado en una red de área local rápida.
- Utilizan un software de libre distribución.
- Son sistemas escalables.
- Cada máquina de un clúster puede ser un sistema completo utilizable para otros propósitos.
- Es posible diseñar el clúster de tal forma que, si un nodo falla, el resto continúe trabajando.
- El rendimiento y los recursos de un clúster pueden crecer con el tiempo.
- Los programas escritos para un clúster solo necesitan ser recompilados para otro clúster.

Las desventajas son:

- Las redes ordinarias no están diseñadas para el procesamiento paralelo. La latencia de red es alta y el
ancho de banda relativamente bajo.

- En los sistemas operativos monoprocesador existe muy poco software para tratar un clúster como un único
sistema.

4.3.3 ¿Cuándo y cómo utilizar un clúster?

La razón de ser del procesamiento paralelo es acelerar la resolución de un problema. Con n procesadores es
posible resolver el problema n veces más rápido que haciendo uso de uno solo (salvo por el mínimo retraso que
supone el reparto de trabajo inicial y la recolección de datos final).

- Aceleración superlineal: El programa se


ejecuta aún más rápido que en el régimen lineal.

- Deceleración: Encuentran los problemas que


se paralelizan muy mal, es decir, necesitan estar
continuamente compartiendo datos entre
procesadores para obtener el resultado final. El
tiempo de ejecución aumenta a medida que
aumenta el número de procesadores.

- Típica: Cada procesador conoce, tras cada


paso, el estado del sistema completo, con lo que
todos los datos deben propagarse a todos los
computadores.

Hay que recurrir a la paralelización de los clusters para adaptar una aplicación propia de cálculo intensivo para
sacar provecho de ellos:

- Programando explícitamente el paso de mensajes usando sockets o el dispositivo de red directamente,


mediante las bibliotecas de paso de mensajes PVM (Maquina paralela virtual) y MPI (Interfaz para el paso
de mensajes) y mediante las bibliotecas de función colectiva AFAPI (API de funciones colectivas).
- Utilizando herramientas que lo hagan: Herramientas de paralelización automática y lenguajes de
programación paralelos.

4.3.4 Programación de clusters


En cualquier aplicación que implique procesamiento paralelo, los datos se tienen que intercambiar entre las
tareas que cooperan para resolver el problema planteado. Para llevar esto a cabo, existen dos paradigmas:

- Memoria compartida: Hay que tener la seguridad de que la información que un procesador lee es
semánticamente valida, y no son datos inválidos, debido a que uno de los procesadores que está escribiendo
los mismos datos con los que otro procesador está realizando una operación de lectura.

- Paso de mensajes: Un proceso envía a otro un mensaje que contiene la información que debe conocer.

En la actualidad el modelo de paso de mensajes es el más aceptado, desde el punto de vista del número y la
variedad de procesadores que lo soportan, asi como por los lenguajes y sistemas software que lo utilizan.

4.4 RENDIMIENTO Y COSTES EN SISTEMAS PARALELOS


Los factores que están directamente ligados con la velocidad computacional de un sistema paralelo son:

- GRANULARIDAD DE LOS PROCESOS.

Hace referencia a lo mucho que se ha dividido el problema en pedazos.

- Granularidad gruesa: Un gran número de instrucciones secuenciales que no necesitan de la comunicación con
otros procesos para ser ejecutadas.

- Granularidad fina: Dispone de pocas instrucciones secuenciales.

Normalmente es deseable incrementar la granularidad para reducir los costes de la creación y comunicación de
procesos manteniendo el paralelismo. Se puede utilizar la razón:

𝑡𝑖𝑒𝑚𝑝𝑜 𝑑𝑒 𝑐𝑜𝑚𝑝𝑢𝑡𝑎𝑐𝑖𝑜𝑛 𝑡𝑐𝑜𝑚𝑝


𝑔𝑟𝑎𝑛𝑢𝑙𝑎𝑟𝑖𝑑𝑎𝑑 = =
𝑡𝑖𝑒𝑚𝑝𝑜 𝑑𝑒 𝑐𝑜𝑚𝑢𝑛𝑖𝑐𝑎𝑐𝑖𝑜𝑛 𝑡𝑐𝑜𝑚

- FACTOR DE ACELERACION (SPEEDUP).

Es una medida del rendimiento relativo entre un sistema multiprocesador y un sistema con un único procesador:

𝑡𝑠
𝑆(𝑀) =
𝑡𝑝

donde ts es el tiempo de ejecución de un programa en un único procesador y tp es el tiempo de ejecución en un sistema


paralelo con M procesadores.
La aceleración máxima absoluta de M se debería alcanzar cuando la computación se puede dividir en procesos de
igual duración, cada proceso se localiza en un procesador y no hay sobrecarga.

𝑡𝑠
𝑆(𝑀) = 𝑡 = 𝑀
𝑠
𝑀

Existen varios factores que aparecen como sobrecarga en los programas paralelos y que limitan la aceleración:

- Existen partes secuenciales que no se pueden dividir y las tiene que ejecutar un único procesador.
- El tiempo de comunicación para enviar mensajes.
- LEY DE AMDAHL.

Determina una cota inferior para el tiempo de ejecución, aun cuando se utilicen al máximo las técnicas de
paralelismo.
Si se llama f a la fracción del programa que no se puede dividir en tareas paralelas 0 ≤ f ≤ 1, y se considera que no
hay sobrecarga cuando el programa se divide en tareas paralelas, el tiempo de computación necesario para ejecutar
el programa en M procesadores es:

(1 + 𝑓) · 𝑡𝑠
𝑡𝑝 (𝑀) = 𝑓 · 𝑡𝑠 +
𝑀

El factor de aceleración viene dado por:

𝑡𝑠 𝑀
𝑆(𝑀) = 𝑡 =
𝑓 · 𝑡𝑠 + (1 − 𝑓) · 𝑠 1 + (𝑀 − 1) · 𝑓
𝑀

La ley de Amdahl expresa que el tiempo de ejecución de la parte paralelizable de un programa es independiente del
número de procesadores. Según Amdahl la razón para aumentar el número de procesadores debe ser para resolver
problemas de tamaño mayor, y no para resolver rápidamente problemas de tamaño fijo.

- EFICIENCIA.

Si se normaliza la aceleración de un programa paralelo dividiéndola entre el número de procesadores se obtiene la


eficacia de un sistema:

𝑡𝑠
𝐸=
𝑡𝑝 · 𝑀

Si se tiene en cuenta la ecuación anterior se puede expresar la eficiencia E en función del factor de aceleración S(M)
mediante:

𝑆(𝑀) 1
𝐸= · 100% = · 100%
𝑀 1 + (𝑀 − 1) · 𝑓

donde E se expresa en porcentaje. La eficiencia da la fracción de tiempo que se utilizan los procesadores durante la
computación, es decir, da una medida de lo adecuadamente que han sido utilizados los procesadores.
- COSTE.

𝑡𝑠 · 𝑀 𝑡𝑠
𝐶 = 𝑇𝑖𝑒𝑚𝑝𝑜 𝑑𝑒 𝑒𝑗𝑒𝑐𝑢𝑐𝑖𝑜𝑛 · 𝑛𝑢𝑚𝑒𝑟𝑜 𝑑𝑒 𝑝𝑟𝑜𝑐𝑒𝑠𝑎𝑑𝑜𝑟𝑒𝑠 𝑢𝑡𝑖𝑙𝑖𝑧𝑎𝑑𝑜𝑠 = =
𝑆(𝑀) 𝐸

- ESCALABILIDAD.

Se suele utilizar para indicar un diseño hardware que permite ampliar su tamaño para obtener una mejora en el
rendimiento. Un sistema es escalable si el rendimiento del mismo se incrementa linealmente con relación al número
de procesadores usados para una cierta aplicación. Los principales parámetros que afectan a la escalabilidad son:

- Tamaño del sistema: Un tamaño grande implica más recursos y más potencia de procesamiento.
- Tamaño del problema: La cantidad de trabajo computacional para resolver un determinado problema.
- Tiempo de CPU: Usado en la ejecución de un determinado programa en un sistema de M procesadores.
- Capacidad de memoria: Se refiere a la máxima cantidad de memoria solicitada durante la ejecución.
- Perdidas de comunicación: Cantidad de tiempo gastada en comunicación entre procesadores, sincronización…
- Coste del sistema: Coste total de los recursos hardware y software.
- Frecuencia de reloj: Es deseable un sistema cuyos componentes estén controlados por un reloj cuya frecuencia
pueda incrementarse.

- BALANCE DE CARGA.

Consiste en distribuir de una forma equitativa la carga computacional entre todos los procesadores disponibles y con
ello conseguir la máxima velocidad de ejecución.

El balance de carga se puede tratar de dos formas:

- Balance de carga estático: Es muy difícil estimar de forma precisa el tiempo de ejecución de todas las partes y
dificulta incorporar la variable retardo de comunicación. A veces los problemas necesitan un número
indeterminado de pasos computacionales para alcanzar la solución.

- Balance de carga dinámico: Reparto de tareas durante la ejecución del programa. Resulta mucho más eficiente.
Dependiendo de donde y como se almacenen y repartan las tareas, el balance de carga puede ser:

- Centralizado.
Se corresponde con la estructura típica
de Maestro/Esclavo. Cuando un
proceso esclavo finaliza una tarea,
solicita una nueva.
En problemas con tareas de distintos tamaños es mejor repartir primero aquellas que tengan una mayor
carga computacional.

- Distribuido.

Se utilizan varios maestros y cada uno controla a un grupo de esclavos. Una gran desventaja es que el
proceso maestro únicamente puede repartir una tarea cada vez, y después de que haya enviado las
tareas iniciales solo podrá responder a nuevas peticiones de una en una.

4.4.1 Costes de la comunicación mediante paso de mensajes

El tiempo empleado en transmitir un mensaje entre dos componentes de un sistema paralelo es la latencia de
comunicación, y sus principales parámetros son:

- Tiempo de inicialización (ts): Tiempo en preparar el mensaje, tiempo de ejecución del algoritmo y tiempo
de conexión entre el emisor y el enrutador.

- Tiempo de salto (th): Tiempo que tarda la cabecera de un mensaje en viajar entre dos nodos directamente
conectados en la red.

- Tiempo de transferencia por palabra (tw): Es el inverso del ancho de banda.

El coste de la comunicación varía dependiendo del tiempo del algoritmo de enrutamiento utilizado. Dos
algoritmos ampliamente utilizados son:

- Almacenamiento y reenvío (store-and-forward).

Cada nodo intermedio entre el emisor y el receptor reenvía el mensaje únicamente cuando lo ha
recibido y almacenado completamente. El tiempo total para el mensaje de tamaño m que se transmite
a través de l enlaces seria:

𝑡𝑐𝑜𝑚 = 𝑡𝑠 + (𝑚𝑡𝑤 + 𝑡ℎ )𝑙

Teniendo en cuenta que el tiempo de salto en los sistemas paralelos actuales es mucho menor que el
tiempo de transferencia del mensaje, éste puede ser ignorado:

𝑡𝑐𝑜𝑚 = 𝑡𝑠 + 𝑚𝑡𝑤 𝑙
- Corte y continuación (cut-through).

Se basa en los sistemas de enrutamiento de paquetes para mejorar los costes de comunicación en
sistemas paralelos.
Este algoritmo divide cada mensaje en unidades de tamaño fijo llamados dígitos de control de flujo,
que son más pequeños que los paquetes.

𝑡𝑐𝑜𝑚 = 𝑡𝑠 + 𝑙𝑡ℎ + 𝑚𝑡𝑤

4.4.2 Costes de la comunicación mediante memoria compartida

Encontrar una unidad de medida para el coste de la comunicación de un programa paralelo mediante paso de
mensajes es mucho más sencillo que mediante el uso de memoria compartida por las siguientes razones:

- El programador tiene un control reducido sobre la localización de los objetos en memoria.

- Si el tamaño de los datos que necesita un nodo para realizar su trabajo es mayor que el tamaño disponible
de la caché local se produce hiper paginación.

- La sobrecarga incluida por las operaciones de coherencia de cache (invalidar y actualizar) es difícil de
cuantificar.

- La latencia de acceso a las diferentes palabras de una cache puede varias.

- La lectura anticipada de palabras depende del compilador y la disponibilidad de recursos.

- El fenómeno de false-sharing (diferentes procesadores acceden a diferentes datos que están almacenados
en el mismo bloque de cache) puede incluir sobrecarga.

- La competición por los recursos del sistema depende de la ejecución del programa.

También podría gustarte