Buscar en este blog

Tema 2.b: Explicación en detalle de las técnicas de resolución de riesgos en procesadores segmentados utilizando como ejemplo la arquitectura MIPS.


En este artículo vamos a usar como ejemplo el procesador MIPS64 que tiene como características el ser un procesador de tipo RISC (ver artículo anterior) con cauce segmentado de 5 etapas por instrucción de 1 ciclo de duración cada una, 64 registros de 64 bits cada uno, memoria cache tipo Harvard y 66 instrucciones simples de longitud fija de 32 bits. Pero vayamos poco a poco antes de que alguien se asuste.

Ahora que ya sabemos lo que es un procesador tipo RISC (Reduced Instruction Set Computer) lo siguiente es comentar en qué consiste segmentar el cauce de ejecución de las instrucciones de un procesador. La segmentación (o pipelining) surge de la necesidad constante de aumentar la velocidad de proceso de las computadoras, para ello nos vienen rápidamente a la mente tres formas de hacerlo: mejorar el hardware para que sea más rápido, explotar la concurrencia poniendo varios procesadores a trabajar en la misma tarea o hacer lo mismo intentando que un procesador pueda hacer varias tareas a la vez. Todas ellas tienen sus ventajas y sus inconvenientes que se suelen traducir en costes de fabricación y/o de programación.

La primera solución es probablemente la más cara y está limitada por el estado actual del arte, la segunda hoy en día es muy común pero antes su precio era prohibitivo y además se puede usar junto con la tercera. Evidentemente la tercera es la segmentación.

Se suele recurrir al ejemplo de la lavadora para explicar cómo funciona, pero se pueden encontrar multitud de ejemplos hoy en día que lo ilustran de igual manera. Hoy vamos a usar como modelo la fabricación de un automóvil, en honor a Henry Ford que fue uno de los primeros en aplicar las cadenas de montaje.

Fabricar un automóvil era tanto en 1913 como hoy una tarea compleja, que requería un grupo experto de mecánicos trabajando muchas horas. La idea básica era dividir esa tarea compleja es varias más pequeñas y más sencillas, de manera que las pudieran realizar operarios menos cualificados en menos tiempo. Vamos a suponer, de forma totalmente falsa, que Henry Ford dividió su línea de producción en 5 pasos “sencillos”:

  1. Recoger las piezas del almacén.
  2. Leer las especificaciones del cliente y configurar las máquinas.
  3. Ensamblar todas las piezas en el chasis.
  4. Enviar el coche al concesionario para su venta.
  5. Escribir los datos de venta en los libros de cuentas.

Si tuviéramos una persona que sabe hacer todas las tareas y tarda 1 día en cada una, podría construir el coche él sólo en 5 días, si contáramos con 5 de estas personas y empezaran a trabajar el mismo día, venderían 5 coches cada 5 días pero necesitaríamos multiplicar por 5 los recursos de la fábrica.

Lo siguiente es tener a una persona formada en cada operación, de forma que tenemos un transportista de piezas, un operario de las máquinas, un montador, un transportista de vehículos terminados y un contable. Seguimos teniendo 5 operarios pero si les ponemos a trabajar sobre el mismo coche sólo venderemos 1 cada 5 días y siempre habrá 4 esperando a que se les dé trabajo.

Ahora bien, si encadenamos las operaciones de manera que tengamos varios coches en fabricación a la vez podemos llegar a lo siguiente:


Con este esquema llegamos a vender 1 coche en 5 días (como al principio), pero a partir del día 5 vendemos 1 coche al día, teniendo una producción igual a la del ejemplo de 5 operarios expertos en todas las tareas pero con unos costes mucho menores y vendiendo un coche diario en vez de tener que esperar 5 días para vender 5 coches.
Si ahora dividiéramos cada tarea en dos subtareas tendríamos que contratar 5 operarios más pero venderíamos dos coches al día, etcétera, etcétera.

Como puede verse la segmentación no reduce el tiempo de proceso sino que aumenta el rendimiento, racionaliza los recursos y disminuye los costes. Este aumento del rendimiento depende directamente del número de etapas de que se componga nuestro proceso.

Ahora traduciremos este ejemplo a la arquitectura MIPS64, para ello las fases por las que pasa una instrucción son:

  1. Instruction Fetch (IF).
  2. Instruction Decode (ID).
  3. Execution (E).
  4. Memory Access (M).
  5. Write Back (WB).

Ahora se entiende lo rebuscado de las operaciones de la analogía con los automóviles; el objetivo era que fueran similares a las etapas del procesador.
Las 3 primeras son fáciles de entender y corresponden con la carga de la instrucción a ejecutar, la segunda con la decodificación de la instrucción y lectura de los registros de los operandos, y la tercera es donde se hacen los cálculos. Para las otras dos etapas es conveniente explicar muy por encima la jerarquía de memoria.

Cuanto más cerca está la memoria del procesador, más rápida debe ser y menos tamaño puede tener. Conforme nos vamos alejando del procesador tenemos: registros, cache, RAM y disco duro. Las dos primeras están alojadas directamente en el procesador, la RAM está en la placa base y el disco duro se engancha a la misma.
En el MIPS64 tenemos 64 registros de 64 bits (32 de propósito general y 32 para operaciones en coma flotante) y una caché de primer nivel (puede haber más niveles…) de 64 kilobytes para instrucciones y otros 64 para datos.

Por tanto la etapa de Memory Access representa una lectura o escritura en la memoria RAM y la de Write Back representa la escritura en los registros de los resultados de las operaciones.

Las ventajas de la segmentación son muchas pero como todo en la vida tienen un precio o unos inconvenientes. Los problemas que hacen que la segmentación no funcione al mismo rendimiento que en la teoría se llaman riesgos y puede haberlos de tres tipos: riesgos de datos, de control y estructurales.

Riesgos Estructurales: en este tipo de riesgo uno de los problemas es que no todas las instrucciones van a requerir hacer uso de todas las etapas pero pueden tener que esperar a que alguno de los recursos quede libre, tardando al final lo mismo que la instrucción más larga. Así mismo no todas las operaciones elementales tienen la misma duración, por ejemplo una suma se realiza en menos tiempo que una división.

Además, si queremos armonizar las etapas de manera que todas avancen a la vez, sincronizadas con el reloj del procesador, tenemos que hacer que todas las etapas duren lo mismo. Esta es la solución ideal y, por definición, inalcanzable por lo que en la realidad diseñaremos el procesador de manera que en un ciclo le dé tiempo a terminar a la etapa más lenta. En el resto de etapas estaremos “perdiendo” tiempo desde que se termine hasta que se pase a la siguiente.

Otra de las facetas de los riesgos estructurales es cuando dos instrucciones distintas tienen que hacer uso del mismo recurso:


Para resolver este problema se pueden introducir paradas (“no op”: etapas que no realizan ningún trabajo) que sincronizan el cauce a costa de hacerlo menos productivo. Otra opción, más cara, es replicar los recursos que más van a utilizarse. Una tercera opción es dividir las etapas en dos subetapas, en una de ellas se leen datos y en la otra se escriben de manera que en la misma etapa se pueden hacer operaciones concurrentes con el mismo registro.

Riesgos de Datos: este tipo de riesgos se producen cuando entre las instrucciones existen dependencias de datos (tienen operandos comunes). Hay tres tipos de dependencias de datos:

-          Verdadera dependencia o RAW (Read After Write).
-          Antidependencia o WAR (Write After Read).
-          Dependencia de salida o WAW (Write After Write).

En MIPS no hay riesgos WAR ya que se produce la emisión de instrucciones en orden, de manera que se leen los registros antes de que se puedan modificar por otra orden posterior.

Los riesgos WAW pueden entenderse con el siguiente esquema:


En la imagen siguiente puede verse un caso de riesgo RAW; la instrucción de multiplicación requiere un dato guardado en el registro 1 que es el resultado de una instrucción suma que aún no ha terminado.


Hay tres formas de solucionar esto: introduciendo paradas que sincronicen el código, reordenando las instrucciones para evitar esas dependencias o implementar la técnica del “data forwarding”.

La primera de estas técnicas ya se ha visto para riesgos estructurales, mientras que la segunda también se puede aplicar para estos y consiste en introducir otras instrucciones que no dependen de los registros en conflicto hasta que se resuelva la parte WB de la primera instrucción. Esta técnica forma parte de la llamada “planificación dinámica”.

El “data forwarding” o anticipación consiste en dotar al hardware de una manera de redirigir el resultado de la etapa E de la primera instrucción en conflicto, directamente a la entrada de la etapa  E de la otra instrucción en conflicto. Esto evita que se detenga el cauce pero hace que la etapa ID de la segunda instrucción lea valores incorrectos.


Riesgos de Control: este tipo de datos se dan cuando en el cauce de instrucciones hay “saltos” fruto de sentencias condicionales tipo if. Normalmente el registro contador de programa (PC) se va actualizando con el valor de la siguiente instrucción a ejecutar pero cuando tenemos sentencias condicionales tenemos que esperar hasta que se resuelva la comparación para saber qué instrucción va a ser la siguiente en ejecutarse.
Como siempre, podemos poner paradas en el cauce hasta que se resuelva la indeterminación pero ya sabemos que con eso estamos echando por tierra las ventajas de la segmentación.

Otras soluciones pasan por predecir el salto o retardar la decisión (bifurcación retardada). Además de esto, existe una mejora del cauce MIPS que intenta minimizar el efecto de estos riesgos adelantando el cálculo de la dirección de salto a la etapa ID, de esta manera sólo se retarda un ciclo la ejecución de la siguiente instrucción.

En la bifurcación retardada seguimos ejecutando las siguientes instrucciones que hay en cola hasta que se resuelva el salto, siempre y cuando estas instrucciones se deban ejecutar siempre independientemente de que se tome o no el salto.

En la predicción de saltos lo que se hace es asumir que siempre se va a tomar el salto (o lo contrario) y actuar en consecuencia mientras no se sepa el resultado. Una vez que se sabe el resultado del salto se sigue con la ejecución ya planeada o se desechan las instrucciones que se habían alimentado. Analizando los costes de las dos opciones (siempre sí o siempre no) se llega a la conclusión de que en MIPS lo que más cuenta trae es actuar siempre como si nunca se fuera a tomar el salto. Esto se puede apreciar en el esquema siguiente.


 Bibliografía:

-          Patterson and Hennessy: Computer Organization and Design. The Hardware/Software Interface. Morgan Kaufmann Publishers. ISBN 1-55860-604-1
-          Apuntes de Arquitectura de Computadores y AGM de varias facultades de Informática

No hay comentarios: