viernes, 23 de octubre de 2009

Resolución de riesgos

En la actualidad, como ya hemos visto, la mayoría de los procesadores son RISC y su arquitectura se basa en muchos puntos en la arquitectura MIPS, que se ve en la siguiente imagen:


Como se puede ver en la imagen, el procesador está segmentado en 5 etapas, que son:
  1. Emisión de instrucciones
  2. Decodificación de instrucciones
  3. Ejecución
  4. Acceso a memoria
  5. Escritura de resultados
En teoría, un procesador de este tipo podría finalizar una instrucción por ciclo pero en la práctica hay una serie de circunstancias que hacen que no sea así. A estas circunstancias las llamamos riesgos. Veremos 3 tipos de riesgos y qué se puede hacer para evitarlos o minimizarlos.

En primer lugar hay riesgos estructurales. Estos se dan cuando 2 o más instrucciones necesitan utilizar el mismo recurso a la vez. Si por ejemplo intentamos leer una instrucción a la vez que un dato de memoria (LOAD), nos encontramos con un riesgo estructural. Hay varias formas de actuar ante una situación así:
  • Que todas las instrucciones en conflicto menos una esperen a otro ciclo.
  • Replicar recursos.
  • Si el recurso lo permite, establecer turnos de uso dentro de un ciclo.
Por ejemplo, para resolver el riesgo que se ha planteado en el parrafo anterior, los procesadores MIPS duplican la memoria, pasando a tener una de instrucciones y una de datos. Cada solución tiene su inconveniente. Si hacemos que las instrucciones esperen, el procesador está perdiendo rendimiento. Si replicamos recursos, la fabricación será más cara. Si establecemos turnos, el recurso debe ser lo suficientemente rápido para relizar varias operaciones por ciclo, lo que seguramente aumente la complejidad y el precio.

El segundo tipo de riesgos que nos podemos encontrar son los riesgos de datos y son de alguno de estos 3 tipos:
  • RAW (Read After Write): Se produce cuando una instrucción necesita el resultado de una anterior que aún no se ha computado. Por ejemplo:
ADD R3,R1,R4
SUB R9,R3,R7
  • WAR (Write After Read): Sucede cuando una instrucción escribe en un registro que lee una instrucción anterior, como en este ejemplo:
ADD R3,R1,R4
SUB R1,R7,R8

Si la 2ª instrucción termina antes de que la 1ª lea sus operandos, el valor de R1 será incorrecto.
  • WAW (Write after Write): Se produce cuando una instrucción escribe en un registro en el que también escribe otra instrucción anterior, como en este ejemplo:
ADD R3,R1,R4
SUB R3,R8,R5

En este caso, si la 2ª instrucción termina antes que la primera, cuando esta lo haga modificará el valor de R3 incorrectamente.
Para evitar estos riesgos, se pueden utilizar las siguientes técnicas:
  • Se puede parar la ejecución hasta que la instrucción correspondiente haya terminado
  • Si nos fijamos en el diagrama del principio, tras la fase de ejecución ya podemos tener el resultado de la operación por si una posterior lo necesita. Si modificamos el procesador para conectar la salida de la ejecución directamente con la decodificación de instrucciones, podemos minimizar los riesgos RAW. Esto está implementado en los procesadores MIPS.
  • Como no todas las instrucciones tienen dependencias entre sí, podemos reordenarlas en tiempo de compilación de manera que las que sí tienen dependencias estén los más separadas que se pueda. De esta manera se minimizan los riesgos vistos anteriormente.
El último tipo de riesgos son los riesgos de control. Aparecen cuando hay un salto condicional. La resolución del salto no es inmediata, por lo que no sabemos cual es la siguiente instrucción hasta que la instrucción pase por la fase de ejecución. Esto hace que el procesador tenga que pararse momentáneamente. Para mitigar esto podemos hacer lo siguiente:
  • Como ya hemos dicho, una posibilidad es pararse hasta que se resuelva el salto
  • El procesador predice si se salta o no. En caso de acertar no hay penalización. Si no, el resultado es el mismo que si se hubiera parado.
  • Se añade lógica a la fase de decodificación de instrucción para saber si se toma el salto lo antes posible. De todas formas en esta arquitectura habría un ciclo en el que todavía no se ha resuelto el salto. Para evitar perderlo, ese ciclo se utiliza para ejecutar (si es posible) una instrucción que no dependa del salto. Esto último puede realizarlo el procesador o el compilador.
Aunque en todos los casos parar la ejecución, o lo que es lo mismo intercalar instrucciones NOOP (no operation) en el flujo de ejecución evita todos los riesgos, esta solución siempre reduce el rendimiento del procesador y hay que recurrir a otras técnicas que añaden más o menos complejidad al procesador. Más adelante veremos cómo en la práctica las técnicas que se utilizan son más complejas que las descritas y los procesadores son capaces de reordenar las instrucciones dinámicamente para evitar los riesgos que hemos visto, evitando las paradas y la pérdida de rendimiento que conllevan.

Patxi Astiz

No hay comentarios:

Publicar un comentario