Como funcionan los pipelines de un CPU, 2a Parte

por

9414 posts

Esta es la segunda parte de la guía en que explicamos la lógica del procesamiento de datos que ocurre en los pipelines de un CPU. El presente artículo cuantifica, de manera más precisa, la aceleración que se logra mediante el pipelining, y detalla algunos de los incovenientes de los pipelines muy profundos (super pipeline), como aquellos usados en los Pentium IV (Intel).

Esta guía es la adaptación de un contenido original situado en Ars Technica (Understanding Pipelining Performance), con cuyo permiso contamos para esta publicación.

Indice

Introducción
La aceleración a partir de pipelining
Tasa de obtención de resultados y tiempo de ejecución de programas
Ejecución de instrucciones y retrasos en el pipeline
Cuando el pipeline se retrasa
Latencia de instrucciones y retrasos en el pipeline.
Los límites del pipelining
Ciclo de reloj y tasa de obtención de resultados
Computación superescalar y pipelining
Multitareas simultáneas y pipelining.
Conclusiones


Introducción
(volver al inicio)

La primera parte de este trabajo cubrió lo básico sobre pipelining y concluyó con una discusión acerca de cómo esto mejora el desempeño de las aplicaciones en general. Específicamente el pipelining aumenta la tasa de obtención de resultados, y esto hace que la ejecución general de un programa se lleve a cabo en una menor cantidad de tiempo. Otra forma de expresarlo es decir que el pipelining permite que el procesador complete más operaciones en un período de tiempo determinado, con el resultado que un lote determinado de instrucciones (un programa, por ejemplo) sea procesado más rápidamente.

El presente artículo cuantifica, de manera más precisa, la aceleración que se logra mediante el pipelining, y detalla algunos de los incovenientes de los pipelines muy profundos (super pipeline), como aquellos usados en los Pentium IV (Intel).

Nota para el lector: los dos artículos de esta serie se han escrito para ser leídos consecutivamente, así que si de inmediato empiezas a leer la siguiente sección y sientes que no entiendes nada, tal vez ayude retroceder un poco y revisar el breve -relativamente, 4 páginas- primer artículo. De hecho, si lees lo último de la I parte, deberías estar listo para esta segunda parte.

La aceleración a partir de pipelining
(volver al inicio)

En general, la aceleración en la tasa de obtención de resultados en comparación con la implementación de un ciclo único que se gana a partir del pipeline, idealmente, es igual número de etapas en el pipeline. Un pipeline de cuatro etapas entrega una aceleración de cuatro veces en la tasa de obtención de resultados, en comparación con un ciclo único, un pipeline de cinco etapas se acelera cinco veces más , un pipeline de 12 etapas da una celeración 12 veces mayor, y así. Esta aceleración es posible porque mientras más etapas de pipeline hay en un procesador, el procesador puede trabajar en más instrucciones simultáneamente y obtener más resultados en un período de tiempo dado. De manera tal que mientras más finamente se segmenten esas cuatro fases del ciclo de vida de una instrucción, más hardware del usado para implementar esas fases se puede poner a trabajar en cualquier momento dado.

Para volver a la analogía de la línea de ensamblaje, digamos que cada equipo está compuesto de seis trabajadores, y que cada tarea que cada equipo realiza en una hora podría subdividirse en dos tareas más cortas, de 30 minutos cada una. Así que podemos doblar la producción de nuestra fábrica al dividir cada equipo en dos más pequeños y más especializados, de tres trabajadores cada uno, y después hacer que cada uno de estos equipos lleve a cabo una de las tareas más cortas que en un SUV por 30 minutos.

  1. Etapa 1: Ensamblar el chassis
  2. Equipo 1a: Acoplar las partes del chasis y soldar por puntos las junturas
  3. Equipo1b: Soldar por completo todas las partes del chasis
  4. Etapa 2: Dejar el motor en el chasis
  5. Equipo 2a: Ubicar y dejar en su lugar al motor en el chasis
  6. Equipo 2b: Conectar el motor a las partes móviles del auto
  7. Etapa 3: Colocar puertas, capote y revestimientos en el chasis
  8. Equipo 3a: Colocar las puertas y el capote en el chasis
  9. Equipo 3b: Colocar los otros revestimientos en el chasis
  10. Etapa 4: Ensamblar las ruedas
  11. Equipo 4a: Ensamblar las dos ruedas delanteras
  12. Equipo 4b: Ensamblar las dos ruedas traseras
  13. Etapa 5: Pintar el SUV
  14. Equipo 5a: Pintar los lados del SUV
  15. Equipo 5b: Pintar la parte de arriba del SUV

Luego de las modificaciones ya descritas, los diez grupos más pequeños en nuestra fábrica, en conjunto, ahora podrían tener un total de 10 SUV en construcción durante un período dado de 30 minutos. Más aun, nuestra fábrica ahora podría terminar un nuevo SUV cada 30 minutos, una mejora diez veces mayor sobre la tasa de cumplimiento de nuestra primera fábrica, que era de un SUV cada cinco horas. Así que al usar pipelines en nuestra línea de ensamblaje, hemos puesto a más gente a trabajar simultáneamente, y así se incrementa el número de SUVs en los que se puede trabajar simultáneamente, a la par que se incremente el número de SUVs que se pueden terminar en un período de tiempo determinado.

Profundizar el pipeline de nuestro procesador de cuatro fases funciona con principios similares y tiene efectos parecido en cuanto a tasas de obtención de resultados. Al igual que las cinco etapas en la línea de ensamblaje de los SUVs podría segmentarse cada vez más en una secuencia más larga compuesta de etapas especializadas, podemos tener que este proceso de ejecución descompone la instrucción que lo recorre en una serie de etapas que son mu más que las cuatro fases discretas. Al segmentar el pipeline de cuatro etapas del procesador en una serie más larga compuesta de etapas más cortas y especializadas, el procesador puede tener más hardware especialzado trabajando simultáneamente en más instrucciones y así incrementar el número de instrucciones que el pipeline completa cada nanosegundo.

Primero pasamos de un procesador con un ciclo único a un procesador con pipeline al tomar el período de tiempo de cuatro nanosegundos en los que la instrucción pasaba por el procesador y se segmentaba en cuatro etapas discretas de pipeline, de un nanosegundo de duración cada una. Estas cuatro etapas discretas del pipeline corresponden a las cuatro fases del ciclo de vida de una instrucción. Las
etapas de pipeline de un procesador no siempre van a corresponder exactamente con las cuatro fases del ciclo de vida de un procesador. Algunos procesadores tienen un pipeline de cinco etapas, otros lo tienen de seis etapas, y muchos tienen pipelines que van más allá de diez o veinte etapas. En tales casos, el CPU (CPU designer) debe segmentar el ciclo de vida de la instrucción en el número correspondiente de etapas de manera tal que todas las etapas tengan igual duración.

Ahora tomemos el proceso de ejecución y separarémoslo en ocho etapas discretas. Dado que para que funcione el pipelining las ocho etapas deben tener exactamente la misma duración, cada una de estas etapas debe durar 0,5 nanosegundos (4ns/8=0,5ns). Ya que estamos trabajando con un ejemplo idealizado, pensemos que dividir el ciclo de vida del procesador de cuatro fases de pipelines en ocho de igual duración (0,5 ns) es un asunto trivial, y que los resultados se parecen a los que se muestran en las figuras PIPELINING 6.1 y PIPELINING 6.2. (En realidad, esta tarea no es trivial, e involucra una serie de compensaciones. Como una concesión a esa realidad, he decidido usar las ocho etapas de un pipeline real, el pipeline MIPS, en los diagramas de más abajo, en vez de simplemente dividir en dos cada una de las cuatro fases tradicionales)

Dado que el pipeline pide que cada etapa del mismo tome exactamente un ciclo de reloj (clock cycle) para completarse, entonces nuestro ciclo (clock cycle) ahora puede acortarse a 0,5 ns para así ajustarse a la duración de las ocho etapas del pipeline. Echa un vistazo a las figuras PIPELINING 6.1 y PIPELINING 6.2, que se encuentran más abajo, para observar el impacto que este incremento de número en las etapas del pipeline tiene en el número de instrucciones procesadas por unidad de tiempo.


Figura PIPELINING.6.1: Pipeline de 8 etapas


Figura PIPELINING.6.2: Pipeline de 8 etapas

El procesador de ciclo único podía terminar de procesar una intrucción cada cuatro nanosegundos, y daba una tasa de obtención de resultados de 0,25 instrucciones/ns, y el procesador con un pipeline cuatro fases puede terminar de procesar una instrucción cada nanosegundo, y esto da una tasa de resultado de 1 instrucción/ns. El procesador de ocho etapas descrito antes mejora estas dos tasas, al terminar de procesar una instrucción cada 0,5 ns, lo que es una tasa de de obtención de resultados de 2 instruciones/ns. Nótese que dado que cada instrucción todavía toma 4ns en ejecutarse, los primeros cuatro nanosegundos del procesador de cuatro etapas todavían están dedicados a rellenar (filling up) el pipeline. Pero una vez que el pipeline está lleno (full), el procesador puede empezar a terminar de procesar las instrucciones dos veces más rápido que el procesador de cuatro etapas, y ocho veces más rápido que el procesador de una sola etapa.

Este aumento en ocho veces en la tasa de obtención de resultados con respecto a un diseño de un solo ciclo significa que nuestro procesador de ocho fases puede ejecutar programas mucho más rápido que un procesador de un ciclo único o uno de cuatro etapas. Pero, ¿en realidad este aumento de ocho veces en la tasa de obtención de resultados se traduce en un ejecución de programas ocho veces más rápida? Bueno, no exactamente.

Tasa de obtención de resultados y tiempo de ejecución de programas
(volver al inicio)

Si el programa que está ejecutando el procesador de un solo ciclo de la figura 4 consistiese, solamente, en las cuatro instrucciones ya descritas, entonces ese programa tendría un tiempo de ejecución de 16 nanosegundos, o 4 nanosegundos por instrucción. Si el programa consistiese en, digamos, siete instrucciones, tendría un tiempo de ejecución total de 4 nanosegundos por instrucción mutiplicado por 7 instrucciones, lo que daría un total de 28 nanosegundos. En general, el tiempo de ejecución de un programa es igual a la tasa de instrucciones manejadas y finalizadas por el procesador (número de instrucciones que se terminan de procesar por nanosegundo) multiplicado por el total de instrucciones del programa.

Tiempo de ejecución del programa = tasa de instrucciones manejadas y finalizadas x número de instrucciones del programa

En el caso de procesador que tiene un ciclo único, sin pipeline, la tasa de instrucciones manejadas (X ns por instrucción) es simplemente la inversa del tiempo de ejecución de una instrucción (1 instrucción por X ns). En el caso de los procesadores que usan el pipeline, este no ocurre.

Si ves en la figura PIPELINING. 5, que corresponde al procesador de cuatro fases, la parte de “Instrucciones manejadas y terminadas (Completed instructions)”, verás que al comienzo del noveno nanosegundo ya se han manejado y finalizado un total de cinco instrucciones. En cambio, el procesador sin pipeline tiene dos instrucciones terminadas y completadas al comienzo del noveno nanosegundo. Obviamente, cinco instrucciones terminadas en el espacio de ocho nanosegundos no es un cuatro veces mejor que dos instrucciones terminadas en el mismo período de tiempo, entonces, ¿qué sucede?

Hay que tener en mente que el procesador necesitó los primeros cuatro nanosegundos para llenarse (fill up) de instrucciones; entonces, el procesador no terminó de manejar su primera instrucción hasta el final del cuarto nanosegundo. Por ello terminó de procesar menos instrucciones de ese programa en los primeros 8 ns que las que habría procesado si el pipeline hubiese estado lleno por esos 8 ns.

Se empieza a desvanecer el impacto de esos cuatro nanosegundos iniciales, durante los cuales sólo una instrucción se terminó de procesar, cuando el procesador está ejecutando programas que consisten en miles de instrucciones; cuando el número de nanosegundos se acerca a los miles, la ventaja del procesador con pipeline empieza a ser cuatro veces mayor, acercándose al punto de referencia. Por ejemplo, después de 1000 nanosegundos, el procesador sin pipeline habrá terminado de procesar 250 instrucciones (1000ns x 0,25 instrucciones/ns = 250 instrucciones) mientras que el procesador con pipeline habrá completado 996 instrucciones ((1000ns – 4ns) / 1instrucción/ns), lo que da una mejora en 3,984 veces.

Al usar el ejemplo concreto de más arriba, se ha descrito la diferencia que hay entre la tasa máxima de obtención de resultados de un pipeline, en teoría, y su tasa de obtención de resultados de la realidad. En el ejemplo previo, la tasa máxima, en teoría, de obtención de resultados del procesador de cuatro fases, esto es, su tasa de obtención de obtención de resultados cuando el pipeline está completamente lleno, es de 1 instrucción/ ns. Sin embargo, la tasa promedio de obtención de resultados durante los primeros 8 nanosegundos es de 5 instrucciones/8ns = 0,65 instrucciones/ns. Esta tasa promedio de obtención de resultados del procesador m
ejora conforme transcurren más ciclos de reloj con el pipeline lleno, hasta llegar, a los 1.000 nanosegundos a su tasa promedio de obtención de resultados de 996 instrucciones/1000ns = 0,996 instrucciones/ns

En este punto, podría ayudar ver un gráfico de la tasa de obtención de resultados de nuestro pipeline de cuatro fases mientras el número de nanosegundos se incrementa:

Gráfico pipelining 1: Tasa promedio de obtención de resultados de un pipeline de cuatro fases.

Se puede ver cómo la tasa de obtención de resultados del procesador permanece en cero hasta el punto de los 4 ns, en donde el pipeline está lleno y el procesador puede empezar a terminar de manejar una instrucción nueva cada nanosegundo. Esto hace que la curva que describe la tasa promedio de obtención de resultados se curve hacia arriba, y eventualmente se acerque a la tasa máxima de 1 instrucción/ns.

Así que, en conclusión, un procesador con pipeline sólo puede acercarse a su tasa de obtención de resultados ideal si puede funcionar por largos períodos con su pipeline lleno en cada ciclo de reloj.

Ejecución de instrucciones y retrasos en el pipeline
(volver al inicio)

A pesar de lo que les he podido hacer creer en las páginas anteriores, el pipeline tiene sus contratiempos. El pipelining le añade complejidad al control lógico del microprocesador, puesto que todas estas fases tienen que mantenerse sincronizadas. Aun más importante para esta discusión es el hecho de que el pipelining hace más complejo tanto el diseño del microprocesador como las formas para evaluar el desempeño del mismo.

Hasta ahora hemos hablado acerca del desempeño de microprocesador sólo en términos de tasa de manejo de instrucciones, o número de instrucciones que el pipeline del procesador podía completar cada nanosegundo. En la realidad, es más frecuente medir el desempeño según las instrucciones por ciclo, o el número de instrucciones que el procesador puede completar en cada ciclo de reloj.

Se podría suponer que las instrucciones por ciclo de un pipeline siempre deberían ser de 1 instrucción/ciclo de reloj, puesto que se ha dicho que un procesador con pipeline completa una nueva instrucción al final de cada ciclo de reloj en donde ha estado activa la etapa de escritura.

Hay que resaltar que la importancia que se le da a la fase de la escritura en la última definición de alguna manera confirma esta primera suposición; ya hemos visto que la fase de la escritura está inactiva durante los ciclos de relojs en los que el pipeline se está llenando, así que en esos ciclos de reloj el número de instrucciones por ciclo es de 0 instrucciones/ciclo de reloj. En cambio, cuando el pipeline está lleno y la etapa de escritura está activa, el pipeline del procesador tiene un desempeño de instrucciones por ciclo de 1 instrucción/ciclo de reloj.

Así que, de la misma forma en que había una diferencia entre la teórica tasa máxima de obtención de resultados de un procesador y su tasa de obtención de resultados promedio o reales, también hay una diferencia entre el máximo teórico de instrucciones por ciclo y las instrucciones por ciclo promedio.

1) Instrucciones por ciclo: se refiere al número de instrucciones que el procesador termina de ejecutar en cada ciclo de reloj. También se le llama IPC (Instructions per clock).

2) Máximo teórico de instrucciones por ciclo: se refiere al número máximo de instrucciones que, en teoría, el procesador puede terminar de ejecutar en cada ciclo de reloj. Para las clases mas sencillas de procesadores, con pipeline y sin él, descritos hasta el momento, este número siempre es de una instrucción por ciclo (1 instrucción/ciclo o 1 IPC)

3) Promedio de instrucciones ejecutadas por ciclo: el número promedio de instrucciones por ciclo (IPC) que el procesador ha terminado por completo en un cierto número de ciclos.

Las instrucciones por ciclo de un procesador están íntimamente relacionadas con su tasa de obtención de resultados, puesto que mientras el procesador complete más instrucciones durante cada ciclo de reloj, más instrucciones terminará de ejecutar cada nanosegundo. Más adelante hablaremos más de la relación que existe entre estas dos medidas, pero por ahora sólo es necesario recordar que un índice de instrucción por ciclo más alto conlleva una tasa de obtención de resultados más alta, y, por tanto, un mejor desempeño.

Cuando el pipeline se retrasa
(volver al inicio)

En la cruda realidad, un pipeline puede encontrarse en muchas más situaciones que las descritas hasta el momento, como por ejemplo, un pipeline lleno o vacío. A veces las instrucciones quedan atascadas por varios ciclos en una fase del pipeline. Hay una serie de razones por las cuales esto podría ocurrir, pero cuando ocurre, se dice que el pipeline se ha parado. Cuando el pipeline se detiene, o queda parado en una cierta fase, todas las instrucciones que se encuentran en las etapas posteriores a este fallo siguen avanzando normalmente, mientras que la instrucción que quedó detenida simplemente se queda en su fase y retrasa a todas las instrucciones que vienen después de ella. En la figura de más abajo, la instrucción anaranjada se ha retasado por dos ciclos adicionales en la etapa de fetch (traer). Como consecuencia de esto, por cada ciclo que este pipeline se retrase, va a haber una casilla vacía adelante de él. Una vez que la instrucción empieza a circular otra vez por el pipeline, los vacíos que había en el pipeline por el retraso empiezan a avanzar también más allá del punto en que ocurrió el error hasta que, eventualmente, dejan el pipeline. A estos vacíos se les suele llamar “burbujas en el pipeline”.


Figura PIPELINING.7: Pipeline stalls en un pipeline de 4 etapas

Los retrasos en el pipeline, o burbujas, bajan el promedio de instrucciones ejecutadas por ciclo de un pipeline, dado que no dejan que el pipeline llegue al máximo de instrucciones completadas por ciclo. En la figura de más arriba, PIPELINING.4, la instrucción anaranjada ha quedado retrasada en la etapa de fetch (traer) por dos ciclos más, lo que ha creado dos burbujas que se propagarán por el pipeline. Repetimos: la burbuja simplemente es una forma de representar que esa fase -en donde se encuentra la burbuja- no está realizando ninguna operación durante ese ciclo. Una vez que las instrucciones que siguen a la burbuja han terminado de procesarse, el procesador no seguirá manejando nuevas instrucciones hasta que las burbujas salgan del pipeline. Así que, al final de los ciclos 9 y 10, no se añaden nuevas instrucciones a la sección de “Instrucciones completadas –Completed Instructions-“; normalmente, se anadirían dos nuevas instrucciones a esta re
gión al final de estos dos ciclos ya mencionados. A pesar de esto último, el procesador está retrasado dos instrucciones cuando llega al ciclo número 11 y otra vez empieza a acumular instrucciones completadas.

Mientras más de estas burbujas se acumulen en pipeline, el índice real de instrucciones por ciclo del procesador se va alejando cada vez másde su índice máximo de instrucciones por ciclo. En el ejemplo anterior, idealmente el procesador debería haber completado 7 instrucciones para el momento en que termina el 10 ciclo, para tener un promedio de isntrucciones ejecutadas por ciclo de 0,7 instrucciones por ciclo. Hay que recordar que el máximo posible de instrucciones por ciclo, en condiciones ideales, es de 1 instrucción por ciclo de reloj, pero se necesitarán muchos ciclos sin burbujas para poder llegar a este máximo. Por la burbuja en el pipeline, el procesador sólo está completando 5 instrucciones en 10 ciclos, y esto da un índice de instrucciones por segundo de 0,5 instrucciones por segundo. Esto es la mitad del máximo teórico de instrucciones por ciclo, pero, por supuesto, el procesador pasó varios ciclos llenando el pipeline, así que no habría podido llegar al máximo teórico incluso en condiciones ideales. Incluso, es más importante considerar el hecho de que 0,5 intrucciones por ciclo de reloj es solamente un 71% del rendimiento que podría haber tenido si no hubiese existidos la burbuja (en este caso, 0,7 instrucciones por ciclo de reloj).

Dado que las burbujas reducen el promedio de instrucciones ejecutadas por ciclo del procesador, incrementan el tiempo que se necesita para ejecutar un determinado programa que está corriendo en nuestro computador. Si el programa del ejempo anterior consistiese solamente en las siete instrucciones planteadas, entonces la burbuja habría hecho que el programa tomara un 29% más en el tiempo de ejecución del programa.

Ahora veamos un gráfico que muestra el impacto de un retraso de dos ciclos en promedio de instrucciones ejecutadas por ciclo:


Gráfico PIPELINING.2: Resultado promedio de instrucciones en un pipeline de 4 etapas con un stall de 2 ciclos

El promedio de instrucciones ejecutadas por ciclo de un procesador deja de elevarse y empieza a desplomarse cuando la primera burbuja llega a la fase de escritura, y esto no se recupera hasta que las burbujas han salido del pipeline.

Para tener una idea más clara del impacto que las burbujas o los retrasos pueden tener en el promedio de instrucciones ejecutadas por ciclo de reloj, veamos el impacto que tiene un retraso de 10 ciclos (empezando en la fase de fetch o traer del ciclo 18) tendría durante el transcurso de 100 ciclos en el pipeline de cuatro fases que se ha descrito hasta ahora.


Gráfico PIPELINING.3: Resultado promedio de un pipeline de 4 etapas con un stall de 2 ciclos

Luego que de que la primera burbuja del retraso llegue a la fase de escritura en el ciclo 20, el promedio de instrucciones ejecutadas por ciclo deja de incrementarse y empieza a decaer. Para cada ciclo en donde hay una burbuja en la etapa de escritura, el índice de instrucciones por ciclo es de 0 instrucciones/ciclo de reloj, así que el promedio de instrucciones ejecutadas por ciclo sigue cayendo por todo este tiempo. Después de que la última burbuja ha salidos de la etapa de escritura, entnonces el pipeline empieza a completar nuevas instrucciones, otra vez a una tasa de 1 instrucción/ciclo y su promedio de instrucciones ejecutadas por ciclo empieza a ascender, como también empieza a ascender su tasa de obtención de resultados y su desempeño con respecto a ejecución de programas.

Un porcentaje bastante alto de las características en la arquitectura de los procesadores en los que he trabajado a lo largo de los años han estado dedicadas a prevenir las burbujas, las paradas y los retrasos. Particularmente, estoy pensando el predictor de ramificaciones (branch prediction), dado que es una herramienta esencial que evita que los procesadores se paren, por un gran número de ciclos, en la fase de fetch (traer).

Latencia de instrucciones y retrasos en el pipeline
(volver al inicio)

Antes de cerrar nuestra discusión sobre el retraso o paradas en los pipelines, debemos intoducir otro término que encontraremos en todo lo que sigue del artículo: latencia de instrucciones. La latencia de una instrucción es el número de ciclos de reloj que demora la instrucción en pasar por el pipeline. Para un procesador de un ciclo único, todas las instrucciones tienen una latencia de un ciclo de reloj. Por el contrario, para un pipeline sencillo, como el de cuatro fases que hemos descrito hasta el momento, todas las instucciones tienen una latencia de cuatro ciclos. Para tener una imagen visual de esto hay que revisar lo que sucede con la instrucción azul en la figura de más arriba PIPELINING.4; esta instrucción toma cuatro ciclos de reloj en avanzar, a una tasa de un ciclo de reloj por fase, por cada una de las cuatro etapas del pipeline. Igualmente, las instrucciones tienen una latencia de ocho ciclos en un pipeline de ocho fases, de veinte ciclos en un pipeline de veinte fases, y así.

En los procesadores del mundo real, la latencia de una instrucción no es necesariamente un número predeterminado que es igual al número de fases del pipeline. Esto sucede porque las instrucciones pueden quedar atascadas en una o más fases del pipeline por varios ciclos, y así, cada ciclo extra que pasan esperando, se va añadiendo un ciclo más a su latencia. Así que las latencias de instrucciones que se dieron antes, como cuatro ciclos para un pipeline de cuatro fases, ocho ciclos para un pipeline de ocho fases, y así, tan sólo representan el mínimo de latencias de instrucción. Las latencias de instrucción reales de un pipeline de cualquier tamaño pueden ser más grandes que la profundidad del pipeline, esto depende se si la instrucción se para o no en una o más fases.

Los límites del pipelining
(volver al inicio)

Como probablemente te lo habrás imaginado, en una línea de ensamblaje o de un procesador, en la práctica, hay límites para cuán profundo puede ser un pipeline antes de que la aceleración en la tasa de obtención de resultados que se gana por el pipelining empiece a ser notoriamente menos que la aceleración ideal que se podría esperar. En la realidad, las diferentes fases del ciclo de vida de una instrucción no se descomponen fácilmente en un número arbitrario y alto de fases más cortas y que tienen exactamente la misma duración. Esencialmente, algunas fases son más complejas y tomán más tiempo en procesarse que otras. Pero dado que cada fase del pipeline debe tomar exactamente un ciclo de reloj para completarse, entonces el pulso del reloj (clock pulse) que coordina todas las
fases no puede ser más rápido que la fase más lenta del pipeline. En otras palabras, el tiempo que toma en completarse la fase más lenta del pipeline determinará la duración del ciclo de reloj del CPU, y así, también determinará la duración de cada fase del pipeline. Esto siginifica que la fase más lenta del pipeline estará activa durante todo el ciclo de reloj, mientras que las fases más rápidas estarán de ociosas durante alguna parte del ciclo de reloj. Esto no sólo desperdicia recursos, sino que también incrementa el tiempo total de ejecución de cada instrucción al hacer que algunas fases del ciclo de vida de la instrucción tomen más tiempo del que se tardarían si el procesador no tuviese pipeline. En esta situación, todas las otras fases deben esperar durante un tiempo extra meintras que la fase más lenta se pone al día.

Así que mientras más detalladamente se segmenta el pipeline para añadir fases e incrementar la productividad, las fases individuales empiezan a ser cada vez menos uniformes en cuanto a complejidad y duración, y esto hace que se alargue el tiempo total que toma el procesador en completar la ejecución de una instrucción. Dado que el pipeline tiene esta característica, uno de los desafíos más complicados e importantes para los diseñadores de CPU es equilibrar el pipeline para ninguna fase tenga más trabajo que otra. El diseñador debe distribuir de forma pareja la carga para procesar una instrucción, para que así ninguna fase tome demasiado tiempo y ralentice a todo el pipeline.

Se puede ver claramente lo difícil que es lograr este equilibrio en las fases de drive del Pentium 4. El único fin de estas fases es el de llevar o amplificar señales a través del chip. Si Intel no le hubiese dado sus dos fases separadas a estos dos períodos de propagación de señales, entonces todas las fases del pipeline del Pentium 4 se habrían alargado dado que la propagación de la señal se demora en sólo dos partes del pipeline.

Ciclo de reloj y tasa de obtención de resultados
(volver al inicio)

Si el tiempo del ciclo de reloj del pipeline, o período de reloj, es más largo que su duración ideal (lo que es, tiempo de ejecución de instrucción sin pipeline/profundidad del pipeline), cosa que siempre ocurre, entonces esto se observará en la tasa de obtención de resultados. Si la tasa de instrucciones completadas queda fija, digamos, en 1 instrucción/ciclo de reloj, entonces mientras el período de reloj aumenta, la tasa de obtención resultados disminuye. Dado que las nuevas instrucciones sólo pueden ser completadas al final de cada ciclo de reloj, un ciclo de reloj más largo pasa a ser menos instrucciones completadas por nanosegundo, y esto al final resulta en un tiempo más largo de ejecución de programas.

Para tener una mejor idea de la relación que existe entre la tasa de obtención de resultados, las instrucciones completadas y el tiempo del ciclo de reloj, tomaremos nuestro pipeline de ocho fases de la figure PIPELINING.6 e incrementaremos su tiempo de ciclo de reloj a 1 nanosegundos, en lugar de los 0,5 nanosegundos iniciales. El los primeros nueve nanosegundos de ejecución esto se vería así:


Figura PIPELINING.8: Un pipeline de 8 etapas con un periodo de clock de 1ns

Como se puede ver, el tiempo de ejecución de instrucciones ahora se ha incrementado de un tiempo original de cuatro nanosegundos a un tiempo nuevo de ocho nanosegundos, lo que significa que el pipeline de ocho etapas no termina de ocmpletar su primera instrucción hasta el final de nanosegundo número ocho. Una vez que el pipeline está lleno, el procesador ya descrito empieza a completar instrucciones a una tasa de una instrucción por nanosegundo. Esta tasa de obtención de resultados es la mitad de la tasa de obtención de resultados ideal del pipeline de ocho etapas con el ciclo de reloj de 0,5 nanosegundos. También es exactamente la misma tasa de obtención de resultados de 1 instrucción/ns que tenía, idealmente, el pipeline de cuatro etapas. En resumen: el tiempo más largo del ciclo de reloj le ha robado a nuestro pipeline de ocho etapas la ventaja del pipeline profundo, y esto se observa en su tasa de obtención de resultados. Más aun, el pipeline de ocho etapas ahroa se demora el doble en llenarse. Veamos la influencia de este tiempo de ejecución duplicado sobre a la curva promedio de la tasa de obtención de resultados, en comparación a la misma curva en un pipeline de cuatro etapas.


Gráfico PIPELINING.4: Tasa promedio de completar una instruccion para un pipeline con 4 y 8 etapas con un periodo de clock de 1ns

El pipeline de ocho fases demora más en llenarse, lo que significa que la tasa promedio de obtención de resultados, y, por ende, su desempeño, subirá más lentamente cuando el pipeline ya esté lleno de instrucciones. Hay muchas situaciones en las que el procesador que está ejecutando un programa debe vaciar por completo su pipeline y empezar a rellenarlo a partir de un punto diferente en flujo de datos (code stream). En tales casos, esa subida más lenta en la curva de la tasa de obtención de datos influye mucho en el desempeño.

Computación superescalar y pipelining
(volver al inicio)

La computación superescalar permite a los microprocesadores aumentar el número de instrucciones por ciclo de reloj que completa más allá de 1 instrucción/ciclo de reloj. Recordando que 1 instrucción/ciclo de reloj es el máximo teórico de traspaso de instrucciones que realiza un procesador con pipelines como está definido más arriba. Debido a que una máquina superescalar puede tener múltiples instrucciones en múltiples etapas de escritura por cada ciclo de reloj, la máquina superescalar puede completar múltiples instrucciones por ciclo. Si adaptamos nuestros anteriores diagramas sobre pipelines para representar la ejecución superescalar de 2 vías, lucen como sigue:


Figura PIPELINING.11: Ejecucion superescalar y pipeline combinados

En la figura superior, se agregan dos instrucciones al espacio de “Instrucciones Completadas” en cada ciclo de reloj una vez que el pipeline esta lleno. Entre más pipelines ALU tenga operando en paralelo un procesador, más instrucciones se agregan al espacio en cada ciclo. Es por esto que la computación superescalar nos permite incrementar el IPC del procesador si agregamos más hardware. Hay algunos límites prácticos por los que muchas instrucciones pueden ser ejecutadas en paralelo, por lo que el procesador no siempre alcanza la tasa ideal de llenado de dos instrucciones por ciclo de reloj. A veces el procesador no puede encontrar dos instrucciones pa
ra ejecutar en paralelo en un ciclo particular, lo que significa que debe insertar una “burbuja de pipeline” en uno de los pipelines del ciclo, llevando hacia abajo la tasa de llenado.

Multitareas simultáneas y pipelining
(volver al inicio)

Manteniendo alta la tasa promedio de llenado del procesador en un superescalar, una máquina con pipelines involucra encontrar formas de programar las instrucciones para que se ejecuten en paralelo en cada ciclo. Pero debido a que el flujo del código está diseñado para ser secuencial, hay algunas limitantes inherentes para cuanto paralelismo puede extraer el procesador de el.

Una de la maneras en que los últimos procesadores de Intel, IBM y AMD resuelven este problema es incluyendo soporte para multitareas simultáneas (llamado hyperthreading o SMT) en los procesadores y después diciéndole al programador y/o compilador que haga el flujo de código tan explícitamente paralelo como sea posible. Solo las aplicaciones que soportan multitareas pueden sacar el máximo provecho del SMT, y la multitarea solo puede ser ejecutada por los diseñadores de la aplicación.

El diseño de aplicaciones con soporte multitarea involucra identificar porciones de una aplicación que pueden ser divididos en tareas discretas e independientes, y asignarlas a diferentes ejecuciones. Este proceso de separar en tareas una aplicación convierte un flujo de código secuencial en un conjunto de dos o más flujos de códigos relacionados que pueden ser ejecutados en paralelo.

Y cuando dos tareas que se están ejecutando al mismo tiempo son parte del mismo programa, la tasa de llenado promedio de ese programa se incrementará debido a que más de sus instrucciones serán completadas en casa ciclo. En este sentido, el SMT puede aumentar la tasa promedio de llenado del programa al permitirle tomar una mejor ventaja de del hardware superescalar.


Figura PIPELINING.12: Diseño de aplicacion multitarea

Al diseñar el flujo de código desde cero hasta un conjunto de pequeños flujos, y que se ejecutan simultáneamente, algo de la carga de extraer el paralelismo de nivel de instrucción es movido desde el procesador hacia el programador/compilador. Nótese que los compiladores son notoriamente pobres ante tal paralelismo, por lo que usualmente recaen en los programadores diseñar que la aplicación se puede separar en múltiples tareas.

No todas las aplicaciones funcionan con implementación multitarea. En esos casos el SMT ofrece muy pocas ventajas. A pesar de que habría que hablar de por qué se produce este caso en un momento. Por ahora, veamos otra forma en que el SMT puede mejorar el rendimiento de una aplicación.

El SMT no solo mejora el rendimiento de una aplicación al incrementar la tasa promedio de llenado de un programa con soporte multitarea (es decir, todas las instrucciones se encuentran en el caché L1), sino que también puede prevenir que la tasa de llenado caiga a cero como resultado de la perdida de caché y de las latencias de las memorias. Cuando el procesador está ejecutando dos tareas simultáneamente, y una de ellas se queda detenida en la etapa de “toma” (es decir, se produce una perdida de caché por lo que la tarea debe tomar instrucciones o datos desde la memoria principal), el procesador `puede continuar ejecutando normalmente las tarea que no está detenida. En un procesador sin STM, las burbujas de pipelines saldrían debajo de la instrucción detenida y se propagarían en el núcleo de la ejecución, matando la tasa de llenado promedio de la aplicación. Por otro lado, El procesador SMT puede programar instrucciones de la tarea no detenida para que se ejecuten en aquellos espacios disponibles en los pipelines.

Si las dos tareas nombradas arriba pertenecen a la misma aplicación, el SMT previene que la tasa de llenado caiga a cero durante la duración de la detención de la tarea, mediante la continuación de la ejecución de código desde la tarea no detenida. Esto ayuda a mantener la tasa de llenado alta, y también ayuda a reducir el tiempo que toma en ejecutar la aplicación. Por otro lado, si las dos tareas pertenecen a 2 aplicaciones sin soporte multitarea separadas, la tasa de llenado en la aplicación con la tarea detenida caerá a cera, mientras que la tasa de la otra aplicación no detenida puede mantenerse como mejorar, debido a que la tarea detenida no continua usando recursos del procesador que la tarea no detenida necesita.

Al final, es posible que dos aplicaciones sin soporte multiproceso se ejecuten un poco más lentas en un procesador con SMT, dependiendo del tipo de aplicaciones y otras condiciones, debido a que estaría compitiendo entre ellas por los recursos compartidos (por ejemplo, caché, unidades de ejecución, entradas en cola, etc.). La mayoría de los diseños de SMT usan una variedad de técnicas para minimizar tal competencia y sus efectos, pero siempre es un factor.

Conclusiones
(volver al inicio)

Al final, la ganancia de rendimiento producida por el pipelining depende de dos factores:

1. La detención en el pipeline debe ser evitada. Como vimos anteriormente, este problema causa que bajen la tasa de llenado del procesador y el rendimiento.

2. El llenado en el pipeline debe ser evitado a toda costa. La saturación del pipeline del procesador pasa la cuenta seriamente a la tasa de llenado y al rendimiento. Esto es especialmente verdadero cuando un pipeline es muy largo pero posee una tasa que es comparable con la de un procesador con un pipeline más corto.

Los profundos buffers que se encuentran en la ventana de instrucción de los Pentium 4 (descrito en detalle AQUÍ), están hechos para eliminar las detenciones en el pipeline. Otra vez, los Pentium 4 pagan un relativo alto precio en términos de seguimiento de instrucciones y saturación del buffering al intentar que las detenciones afecten el rendimiento.
Como he dicho en articulos anteriores, el Pentium 4 y el Presscott gastan una tonelada de recursos en transistores predictivos para poder eliminar llenados de pipelines innecesarios. El llenado de pipelines es extremadamente mortal para el rendimiento en una máquina con muchos pipelines, especialmente cuando los clocks no son suficientes para compensar, por lo que, especialmente el Presscott, paga un relativo alto costo en transistores y consumo de poder para su elaborada lógica predictiva.
Más aún, el Presscott tendrá que llegar a clocks más altos que el de los Pentium 4 si quiere tener un mejor rendimiento en código poco predictible y on branchy. A sus actuales bajos clocks, los Presscott van a tener muy mal rendimiento, excepto en los tipos de códigos más predecible y non-branchy (por ejemplo, aplicaciones multimedia y juegos). Mintras Intel intenta estrujar 1 0 2 años más al Presscot, espera verlo a la par con una extremadamente gran cantidad de caché integrado. Este caché ayudará con el código integrado branchy, como el que se enc
uentra en aplicaciones para servidores.

Debería ser claro también por qué el Presscott es un problema tan grande, que Intel ha volteado el curso de toda la empresa y se ha embarcado en una ruta completamente diferente que involucra pipelines superficiales y diseños multinúcleo. Como demostré anteriormente, el aumento en la frecuencia es crítico si una máquina con muchos pipelines va a funcionar de manera correcta, y el aumento de la frecuencia es exactamente los que todas las empresas de procesadores están enfrentando sus problemas relacionados con el poder en sus procesadores de 90nm. De hecho, los problemas del aumento de frecuencia puede decirse que es el talón de Aquiles de los procesadores con diseños de muchos pipelines, y los problemas que están previniendo son como la flecha envenenada en el talón del Presscott.

Queremos agradecer a Ars Technica por contribuir a la difusión del conocimiento permitiéndonos hacer una adaptación de su trabajo, cuyo mérito evidentemente corresponde a los autores originales.

Si quieres hacer comentarios o te quedaron dudas, no dudes en hacerlos en el foro.