04 noviembre 2010

Tema 2. Técnicas de resolución de riesgos en procesadores segmentados utilizando como ejemplo la arquitectura MIPS




             Haciendo bueno el dicho del gran emperador romano Julio César, "Divide y Vencerás", seguiremos su táctica de conquista para abordar este tema. Comentaremos primero qué es la segmentación, para pasar luego brevemente por los procesadores MIPS y, finalmente, terminaremos explicando con los riesgos de procesadores segmentados y las soluciones posibles.

La segmentación y sus problemas


              A finales de los años 90 aparecieron en el mundo de la informática los llamados microprocesadores supersegmentados. Estos microprocesadores se caracterizan por utilizar pipelines muy segmentadas, para tratar de alcanzar frecuencias lo más altas posibles. Los microprocesadores no ejecutan instrucciones “de una vez”, sino que dividen el proceso de ejecución en varias partes, que son ejecutadas una detrás de otra. Esto se llama “pipelining” y es un principio usado en muchos campos de la informática, como el envío de paquetes en redes, la ejecución de instrucciones en procesadores… Para ilustrar esta idea, podemos poner el ejemplo de una manzana. Imaginemos que una persona quiere comerse una manzana. Podríamos ver esta acción como una serie de pasos que deben realizarse en orden: primero se lava la fruta, luego se pela la piel, después se corta la manzana en partes más pequeñas, y finalmente se comen los trozos.


             Suponemos que una persona sola pretende comerse esa manzana, y supongamos que tarda un minuto en realizar cada uno de los pasos anteriores. Es evidente que en total tardaría cuatro minutos en comérsela, y que, además, sería capaz de comer una manzana cada cuatro minutos.


                Pongamos ahora que tenemos, en vez de una sola persona, cuatro personas, cada una de las cuales está encargada de una función. Vamos a suponer que nuestro objetivo es comernos el mayor número de manzanas posible. Para ello, distribuimos el trabajo entre las cuatro personas: una de ellas sólo lavará manzanas, otra las pelará, otra las troceará, y la última finalmente se las comerá.

De modo que, en el minuto cero, el “lavador” está limpiando la primera manzana y el resto ociosos. Tras un minuto, el “lavador” pasa la primera manzana al “pelador” y mientras tanto el “lavador” agarra una segunda manzana. Tenemos una manzana en el “estado 1: lavar” y otra en el “estado 2: pelar”. Al llegar al segundo minuto, la primera manzana, ya pelada, pasa a manos del “troceador”, mientras que la segunda manzana paralelamente es entregada al “pelador” para que empiece a ser pelada; y al mismo tiempo, una nueva manzana (la tercera en juego) comienza a ser lavada…

Parece un juego de béisbol, donde las manzanas son como jugadores
que van recorriendo las mismas bases continuamente

              Pensemos que tenemos una bolsa entera de manzanas, y que comenzamos a realizar nuestro trabajo. La primera manzana tarda, al igual que antes cuando sólo había una persona, cuatro minutos en ser comida, y esto es igual para todas las demás. Sin embargo, a partir del cuarto minuto, cada minuto podemos estar comiendo una manzana. No hemos mejorado el tiempo de “proceso” de cada manzana, pero sí hemos mejorado considerablemente el número medio de manzanas consumidas por minuto.


Figura 3. "Pipeline", nuestro procesamiento de manzanas

         Este proceso es análogo al proceso de pipelining que utilizan los microprocesadores modernos. Hemos conseguido mejorar la velocidad a la que somos capaces de comer manzanas dividiendo el proceso en partes, que pueden ejecutarse de forma independiente. Comer cada manzana, de forma individual, nos sigue costando 4 minutos, eso no varía. Sin embargo, ahora podemos comer una manzana cada minuto, mientras que antes comíamos una manzana cada cuatro minutos.

              ¿Qué pasaría si, en vez de dividir el proceso en cuatro partes, lo hubiéramos dividido en ocho partes diferentes (por ejemplo: lavar media manzana, lavar la otra media, pelar media manzana, pelar la otra media, ...)? En ese caso, cada parte del proceso tardaría medio minuto en realizarse, y podríamos, a partir del minuto cuatro, comer una manzana entera cada medio minuto. Necesitaríamos, lógicamente, ocho personas, pero si somos capaces de conseguirlas, habríamos duplicado nuestra velocidad.

               Con los microprocesadores pasa exactamente lo mismo. Cada ordenador tiene una señal eléctrica periódica, que se denomina reloj. Esta señal sirve para “controlar” todo el sistema, para asegurarnos de que todas las partes funcionan sincronizadas entre sí. En nuestro caso, esta señal indica los instantes en los que cada instrucción “cambia de etapa”, es decir, pasa a la etapa siguiente de ejecución (en el caso de las manzanas sería el instante en el que una manzana pasa de estar siendo lavada a estar siendo pelada, por ejemplo).

         Una forma inmediata que se nos puede ocurrir para aumentar la velocidad de ejecución es precisamente esa: dividir las etapas en subetapas, de forma que cada subetapa tarde mucho menos tiempo en ejecutarse. Haciendo esto, podríamos aumentar la frecuencia del reloj, dado que el margen de tiempo que necesita cada etapa es ahora mucho menor, y con ello, podríamos aumentar la cantidad media de instrucciones por segundo que ejecutamos, consiguiendo así mayor velocidad.

         Sin embargo, no todo es tan sencillo. Existen dos posibles tipos de problemas: los problemas térmicos, y los problemas lógicos. Ambos problemas se ven potenciados de forma muy considerable si se elige una estrategia de supersegmentación.

         Los problemas térmicos son los más sencillos de explicar. Cuando un chip se intenta utilizar a frecuencias demasiado elevadas, el consumo de energía y el calentamiento se disparan, de manera que incluso el chip puede llegarse a fundir, si no se refrigera adecuadamente.

          Los problemas lógicos son más complicados. Puede suceder, por ejemplo, que en una etapa intermedia nos demos cuenta de que la instrucción que estamos intentando ejecutar es en realidad un salto, y que las instrucciones que deben venir a continuación no son las que estamos ejecutando. En el caso de las manzanas, podría suceder, si éstas estuvieran ordenadas de alguna manera, que estuviéramos ejecutando las manzanas 3 y 4 a continuación de la 2, y que descubriéramos que después de la 2 no viene la manzana 3, sino la 10. Esto nos obligaría a eliminar las manzanas anteriores a la 2 que estamos tratando de comer, y empezar desde el principio con la manzana 10. Esto implica una pérdida grande de rendimiento, que se acentúa además a medida que aumentan las etapas de ejecución (porque tendríamos que tirar más manzanas, o lo que es lo mismo, estaríamos perdiendo más tiempo). Nuestro ratio medio de “una manzana cada ciclo” se vería disminuido de una forma muy considerable.

Figura 4: Problemas en el pipelining

             Otro posible problema lógico es que las instrucciones dependan unas de otras, de forma que una instrucción no se pueda ejecutar hasta que alguna de las instrucciones anteriores ya haya sido ejecutada completamente. En este caso, una solución típica es sencillamente parar el pipeline, esperar a que la primera instrucción termine, y después empezar a ejecutar la segunda instrucción. Esta solución también es muy peligrosa, puesto que hace que nuestro ratio de “una instrucción por ciclo” disminuya, y por consiguiente, también lo hace nuestro rendimiento real.

Más tarde profundizaremos sobre esto.


La arquitectura MIPS

             MIPS (Microprocessor without Interlocked Pipeline Stages) es una arquitectura de procesadores tipo RISC desarrollada por MIPS Computer Systems Inc. Los diseños de MIPS se usan en las estaciones de trabajo de SGI, y tienen mucha implantación en sistemas empotrados, dispositivos que soportan Windows CE, y en los routers de Cisco. La consola Nintendo 64, la Sony PlayStation, la Sony PlayStation 2, y la consola portátil Sony PSP usan procesadores MIPS. A finales de los 90, se estimó que uno de cada tres chips tipo RISC que salieron al mercado estaban basados en MIPS.

       Debido a que los diseñadores crearon un conjunto de instrucciones tan claro, los cursos sobre arquitectura de computadores en universidades y escuelas técnicas a menudo se basan en la arquitectura MIPS.

             La idea básica de un procesador MIPS es incrementar el rendimiento usando pipelines. Un pipeline descompone cada instrucción en una serie de pasos, empezando a ejecutar el primer paso de la instrucción sin haber acabado la anterior. En oposición a esta técnica, los diseños tradicionales de la época esperaban a terminar la instrucción completa antes de comenzar la siguiente, lo cual dejaba gran parte de la CPU ociosa durante mucho tiempo.



Pipelining o Segmentación de Instrucciones
      La segmentación de instrucciones es igual que una cadena de montaje en una fábrica de manufacturación. En las cadenas de montaje, el producto pasa a través de varias etapas de producción antes de tener el producto terminado. Cada etapa o segmento de la cadena está especializada en un área específica de la línea de producción y lleva a cabo siempre la misma actividad. Esta tecnología es aplicada en el diseño de procesadores eficientes. A los procesadores que siguen esta técnica se les conoce como pipeline processors (procesador segmentado).

           Hay tres apectos importantes que deben ser considerados en pipeline. Lo primero que debemos observar es que el trabajo es dividido en piezas que más o menos ajustan dentro de los segmentos que componen el pipeline. Segundo, para que el pipeline trabaje de forma eficiente es necesario que las particiones de trabajo tomen aproximadamente la misma cantidad de tiempo. De no ser así, el segmento que requiera más tiempo ( T ) hará que el pipeline se retrase y cada segmento requerirá T unidades de tiempo para completar su trabajo. Esto quiere decir que los segmentos rápidos estarán mucho tiempo ociosos. Tercero, para que el pipeline funcione adecuadamente, deben ocurrir pocas excepciones o hazards (riesgos) que puedan causar retardos o errores en el pipeline. En caso de ocurrir errores, la instrucción tiene que ser cargada nuevamente en el pipeline y se debe reiniciar la misma instrucción que ocasionó la excepción.

             Como una primera aproximación, consideremos una subdivisión del procesamiento de una instrucción en dos etapas o segmentos:
Captación de la instrucción
- Ejecución de la instrucción

            La primera etapa capta una instrucción y la almacena en un buffer. Cuando la segunda etapa está libre, la primera le pasa la instrucción almacenada. Mientras tanto la segunda etapa ejecuta la instrucción, la primera etapa utiliza algún ciclo de memoria no utilizado para captar y almacenar la siguiente instrucción. A esto se le conoce como prebúsqueda o precaptación de instrucción. Este proceso acelera la ejecución de instrucciones. Si las dos etapas consideradas fueran de igual duración, el tiempo de ciclo de instrucción se reduciría a la mitad. Para poder tener una aceleración mayor, el pipeline debe tener más etapas.




                Para implementar eficientemente las instrucciones MIPS en un procesador pipeline, se debe asegurar que las instrucciones sean de la misma longitud y por lo tanto se facilitan las etapas de IF e ID. Además esta arquitectura posee pocos formatos de instrucción que son consistentes entre sí que permiten una decodificación rápida de las instrucciones pues los campos de registros en los formatos siempre ocupan la misma posición dentro del formato. Adicionalmente, la decodificación de las instrucciones y la lectura de los contenidos de los registros ocurren al mismo tiempo.

                  El Pipelining incrementa la productividad del CPU, es decir, el número de instrucciones completadas por unidad de tiempo, pero no reduce el tiempo de ejecución de una instrucción individual. De hecho, usualmente se incrementa el tiempo de ejecución de cada instrucción debido al overhead producido por el control del pipeline. El incremento en la productividad de las instrucciones significa que un programa corre más rápido y tiene menor tiempo total de ejecución.

               Para poder implementar un procesador con pipeline se debe determinar que sucede en cada ciclo de reloj y estar seguros que en el solapamiento de las instrucciones no tengamos competencia por ciertos recursos. Por ejemplo, si se dispone de una única ALU entonces no se podrá calcular la dirección efectiva de un operando y al mismo tiempo llevar a cabo una operación de resta. Cada etapa del pipeline está activa en cada ciclo de reloj. Esto requiere que todas las operaciones en una etapa se completen en un ciclo de reloj.



Riesgos en el pipeline
             Los procesadores con pipeline (o procesadores segmentados) presentan una serie de problemas conocidos como hazards, y que pueden ser de tres tipos:

Riesgos Estructurales: ocurren cuando diversas instrucciones presentan conflictos al tratar de acceder a la misma pieza de hardware. Este tipo de problema puede ser aliviado teniendo hardware redundante que evitan estas colisiones. También se pueden agregar ciertas paradas (stall) en el pipeline o aplicar reordenamiento de instrucciones
para evitar este tipo de riesgo.

Riesgos de Datos: ocurren cuando una instrucción depende del resultado de una instrucción previa que aún está en el pipeline y cuyo resultado aún no ha sido calculado.
La solución más fácil es introducir paradas en la secuencia de ejecución pero esto reduce la eficiencia del pipeline.

Riesgos de Control: son resultado de las instrucciones de salto que necesitan tomar una decisión basada en un resultado de una instrucción mientras se están ejecutando otras.


* Riesgos Estructurales o Structural Hazards
                Ocurre cuando no se dispone de suficiente hardware para soportar ciertos cómputos dentro de un segmento particular del pipeline. Por ejemplo en la figura 6 se puede observar que en el ciclo de reloj 5 (CC5) se debe llevar a cabo una escritura en el archivo de registros y una lectura del archivo de registros. Esto puede resolverse duplicando el archivo de registros pero puede llevarnos a problemas de inconsistencia.

               Otra alternativa es modificar el hardware existente para que pueda soportar operaciones de lectura y escritura concurrentes. Se puede modificar el archivo de registros de forma tal que la escritura de registros se lleva a cabo en la primera mitad del ciclo de reloj y la lectura de registros se efectúa en la segunda mitad del ciclo de reloj.


Figura 6. Riesgo estructural en el acceso al archivo de registros

              Otro riesgo estructural puede ocurrir durante una instrucción de salto, si no disponemos de dos ALU en el segmento EX. Para este tipo de instrucción es necesario calcular la dirección de salto pues en la instrucción se dispone del desplazamiento necesario para llegar al destino y también es necesario calcular la diferencia entre los operandos fuentes para determinar si se cumple o no la condición.


* Riesgos de Datos o Data Hazards
              Un data hazard ocurre cuando la instrucción requiere del resultado de una instrucción previa y el resultado aún no ha sido calculado y escrito en el archivo de registros. En la figura 7 se presenta un ejemplo de riesgo de datos.


Figura 7. Riesgo de Datos en el registro $10

Hay diversas maneras para solventar este problema:

Forwarding: se adiciona circuitos especiales en el pipeline de forma que el valor deseado es transmitido/adelantado al segmento del pipeline que lo necesita (Figura 8).


Figura 8. Forwarding para evitar riesgo de datos en el registro $10

Inserción de paradas (Stall insertion): es posible insertar una o más paradas en el
pipeline (no-op) que retardan la ejecución de la instrucción actual hasta que el dato
requerido sea escrito en el archivo de registros (Figura 9).


Figura 9. Inserción de no-op para evitar riesgo de datos en el registro $10

Reordenamiento de código: en este caso, el compilador reordena instrucciones en el código fuente o el ensamblador reordena el código objeto de forma que se colocan una o más instrucciones entre las instrucciones que presentan dependencia de datos. Para esto se necesitan compiladores o ensambladores inteligentes.


Figura 10. Ejemplo de riesgo de datos

En la tercera instrucción no se dispone del resultado de la segunda y no se puede aplicar adelantamiento del resultado (Figura 10). Una alternativa es que se incluya una parada o burbuja en el pipeline (Figura 11).


Figura 11. Ejemplo de riesgo de datos con inserción de no-op

Otra forma más eficiente es cambiando el orden de ciertas instrucciones. En este caso si intercambiamos la instrucción 3 y 4 resolvemos el problema de dependencia de datos (Figura 12).


Figura 12. Reordenamiento de instrucciones para evitar un riesgo de datos


* Riesgos de Control o Control Hazards
                    Un riesgo de control se produce cuando es necesario llevar a cabo una decisión basada en el resultado de una instrucción mientras se están ejecutando otras instrucciones. Cuando se ejecuta un salto no se conoce de antemano cuál será la siguiente instrucción que deberá ser ejecutada. Si la condición del salto falla entonces se debe ejecutar la instrucción inmediata, si la condición del salto se cumple se debe actualizar el PC con la dirección de la siguiente instrucción que debe ejecutarse.

               Una estrategia para atacar este tipo de problema es asumir que la condición del salto no se cumplirá y por lo tanto la ejecución continuará con la instrucción que se encuentra inmediatamente después de la instrucción de salto. Si la condición del salto se cumplió entonces se deberá desechar del pipeline las instrucciones que fueron captadas y la ejecución continua con la instrucción que se encuentra en la dirección de salto (Figura 13).


Figura 13. Riesgo de Control con decisión del salto en MEM

                 En el ejemplo de la figura 13, si la condición del salto se cumple, se deben desechar del pipeline las tres instrucciones que entraron en el pipeline pues se había asumido que dicha condición no se iba a cumplir.

                  Una forma para mejorar el rendimiento de los saltos es reduciendo el costo de no haber tomado el salto. La decisión del salto se toma en el segmento MEM del pipeline. En este segmento se tiene un sumador que le adiciona al PC el desplazamiento para llegar a la instrucción destino. Si esta decisión se puede mover a una etapa o segmento anterior entonces sólo una instrucción tendrá que ser sacada del pipeline cuando se tome una decisión incorrecta en el caso de los saltos. Muchas implementaciones MIPS mueven la ejecución del branch a la etapa ID. Para ello mueven el sumador que usa el branch para la etapa ID (Figura 14).

Figura 14. Riesgo de Control con decisión del salto en la etapa ID



Referencias y más información:
• Patterson and Hennessy: Computer Organization and Design. The Hardware/Software Interface. Morgan Kaufmann Publishers. ISBN 1-55860-604-1
Este libro trata de diseño de computadoras en general y de tipo RISC en particular, con ejemplos sacados de la arquitectura MIPS.

• Dominic Sweetman: See MIPS Run. Morgan Kaufmann Publishers. ISBN 1-55860-410-3
El libro definitivo sobre la arquitectura MIPS. Trata la arquitectura de hardware y también habla del software que corre sobre MIPS, como por ejemplo de compiladores y sistemas operativos.

• Artículo sobre MIPS, procesadores segmentados y pipeline: http://www.wikipedia.org/

• Un artículo de ArsTechnica bastante interesante:
http://arstechnica.com/articles/paedia/cpu/pentium-m.ars/


• Un artículo sobre el futuro de Mac, MIPS, pipeline... http://www.macuarium.com/cms/index.php?option=com_content&task=view&id=218&Itemid=93

• Toda la documentación de Intel sobre el Pentium-M:
http://www.intel.com/design/mobile/pentiumm/documentation.htm

No hay comentarios:

Publicar un comentario