Documentos de Académico
Documentos de Profesional
Documentos de Cultura
6 Pipeline PDF
6 Pipeline PDF
de Computadores
En los dos capítulos siguientes vamos a tratar dos formas de optimizar substancialmente la
arquitectura convencional que hemos estudiado hasta ahora. Esta mejora se centra en la CPU y en
la memoria.
En primer lugar empezaremos por mejorar las prestaciones de la CPU, que, en cuanto a velocidad,
se pueden mejorar mediante los procesadores segmentados (o en pipeline) los cuales incorporan
una técnica para acelerar el ritmo de ejecución de instrucciones.
Como veremos, hay diversos factores que pueden impedir un aprovechamiento óptimo del
concepto de pipeline en la CPU. Trataremos estos factores y las técnicas para evitarlos en la
medida de lo posible.
¿ ?
Cómo aumentar la Construir circuitos más
velocidad del rápidos, pero
procesador - ¿A qué precio?
- ¿Estado del arte?
La velocidad de ejecución de los programas depende de diversos factores. Una forma de aumentar
esta velocidad es hacer más rápidos los circuitos con los que se construyen los procesadores y la
memoria principal. No obstante, se debe considerar el coste que supone una mejora y que el límite
a esta velocidad lo impone el estado del arte actual de la tecnología.
Otra posibilidad es organizar el hardware para poder ejecutar más de una instrucción
simultáneamente: concurrencia. La concurrencia se puede obtener en dos niveles: al nivel del
procesador y al nivel de la instrucción. La concurrencia al nivel de la CPU se obtiene disponiendo
de múltiples procesadores ejecutando simultáneamente varias instrucciones. Obtener concurrencia
a nivel de la instrucción significa poder ejecutar varias instrucciones simultáneamente con una
única CPU. Este último tipo de paralelismo se denomina segmentación o encadenamiento, aunque
suele ser más conocido por su denominación en inglés: pipelining.
Las arquitecturas con múltiples procesadores suelen utilizarse en máquinas de muy altas
prestaciones (y muy alto precio). Sin embargo, con arquitecturas segmentadas se consigue una
muy buena mejora del rendimiento y a un coste asequible. Por esto, es normal que todos los
microprocesadores actuales de propósito general incorporen el pipelining. Ya que es muy común
su utilización en los actuales procesadores, vamos a abordar aquí esta técnica del pipelining,
mientras que las arquitecturas multiprocesador las dejaremos para asignaturas o textos de
arquitecturas paralelas o avanzadas.
Tar Tar
tas tas
ACM ACM
E E
Tar
tas
ACM
E
10 s 10 s 10 s 10 s 10 s
Tar Tar
tas tas
ACM ACM
E E
Tar
tas
ACM
E
Tar
tas
ACM
E
Tar
tas
ACM
E
Tar
10 s 10 s 10 s 10 s 10 s
tas
ACM
E
Tar
tas
ACM
E
Ahora supongamos que se dispone de una cadena de empaquetado de tartas con una cinta
transportadora sobre la que trabajan cinco operarios especializados en tareas distintas. El primer
operario pone la caja-1 en la cinta transportadora, y ésta avanza hasta que la caja-1 está donde el
segundo operario, que introduce una tarta dentro de la caja-1, al mismo tiempo que el primer
operario pone otra caja-2 en la cinta. La caja-1 sigue avanzando hasta el tercer operario, que la
cierra y la precinta, al mismo tiempo que el segundo operario mete otra tarta en la caja-2 y el
primer operario pone otra caja-3 en la cinta. La caja-1 sigue su camino en la cinta pasando por el
cuarto operario, que pone una etiqueta, hasta llegar al quinto operario, que la retira de la cinta.
En el momento que el quinto operario retira la caja de la cinta, hay cuatro cajas más en la cinta. Si
cada una de estas fases de empaquetado se realiza en 10 s, a partir de ahora, cada 10 s saldrá
una nueva tarta empaquetada, en lugar de hacerlo cada 50 s que se tardaba cuando no había
cadena de empaquetado. A partir de ahora, en tener 10 tartas empaquetadas se tardará solamente
100 segundos, mientras que en el caso de cuando se tenía un solo operario se tardaba 500
segundos.
Debe quedar claro que aunque ahora sale una nueva tarta empaquetada cada 10 s, la preparación
completa de cada tarta sigue requiriendo 50 s (igual que cuando había una sola persona
preparando las tartas).
I1 I2 I3
Ejecución
Secuencial F1 E1 F2 E2 F3 E3 ...
tiempo
buffer
Nueva unidad unidad
organización In
F E
del hardware
t1 t2 t3 t4
Instrucciones
tiempo
I1 F1 E1
I2 F2 E2
I3 F3 E3
En una primera aproximación, se puede observar que para ejecutar una instrucción en la CPU se
requieren 2 pasos:
1. Alimentación o extracción de la instrucción desde memoria (fetching).
2. Ejecución de la instrucción.
En 1959, el ordenador Stretch de IBM, teniendo en cuenta que durante la fase de ejecución hay
momentos en los que no se accede a memoria principal, aprovechaba para alimentar instrucciones
por adelantado y guardarlas en un buffer de prealimentación, todo ello en paralelo con la ejecución
de la instrucción en curso, con lo que al terminar de ejecutar dicha instrucción podía cargar la
siguiente instrucción directamente desde el buffer sin tener que esperar a traerla de memoria.
Esta técnica de prealimentación o prefetching puede verse como un pipeline de dos etapas. En la
primera etapa se alimenta una instrucción de memoria y se guarda en un buffer. La segunda etapa
toma una instrucción del buffer y la ejecuta. Mientras en la segunda etapa se está ejecutando una
instrucción, la primera etapa aprovecha (los ciclos en los que la segunda etapa no accede a
memoria) para leer la siguiente instrucción y guardarla en el buffer. Cuando la segunda etapa
acabe la ejecución y vacíe el buffer de prealimentación, la primera etapa puede volver a leer una
nueva instrucción de memoria.
Con estas dos etapas de alimentación y ejecución de instrucciones, parece que la velocidad de
ejecución de instrucciones por segundo (rendimiento) se duplica. Y si nos fijamos en el ejemplo de
la línea de empaquetado de tartas, tenemos que su velocidad de tartas empaquetadas por minuto
se multiplica por cinco cuando se establece una cadena de empaquetado de cinco etapas. Esto es
así, simplemente porque el número de etapas dice cuántas cosas se están haciendo
simultáneamente, y claro, cuantas más mejor.
t1 t2 t3 t4 t5 t6 t7
I1 F1 D1 O1 E1 W1 tiempo
I2 F2 D2 O2 E2 W2
I3 F3 D3 O3 E3 W3
Según lo que acabamos de ver, parece que interesa dividir las fases de ejecución de las
instrucciones en más etapas, para así obtener un mayor rendimiento en la ejecución. Poco
después del Stretch, UNIVAC sacó el LARC, con 4 etapas. Actualmente, tenemos que el PowerPC
750 tiene 6 etapas; el Pentium de Intel consta de tres unidades de proceso en pipeline, cada una
dividida a su vez en varias etapas. El Motorola 68040 tenía 6 etapas para el tratamiento de los
enteros.
La ejecución de una instrucción podría descomponerse en las siguientes 5 etapas:
1. F: Alimentación de la instrucción (fetch)
2. D: Decodificación de la instrucción
3. O: Extracción y cálculo de los operandos
4. E: Ejecución (en la ALU)
5. W: Escritura del resultado (write)
Si ahora la ejecución de una instrucción está descompuesta en 5 etapas, cada etapa puede durar
aproximadamente 1/5 de la duración total de la ejecución de la instrucción. Si suponemos que la
duración de un ciclo de reloj es igual a la duración de cada una de estas pequeñas etapas,
podemos decir, en principio, que con la técnica del pipelining se consigue que a cada ciclo de reloj
finalice una instrucción, o lo que es lo mismo, una velocidad de instrucción por ciclo.
Más etapas
Más rendimiento
Avanza la
Aumenta número de etapas
Técnica
Arquitectura de Computadores Segmentación (Pipeline) - 7
Por lo que hemos dicho hasta ahora, esta técnica puede reducir el número de ciclos/instrucción en
un factor igual a la profundidad del pipeline (número de etapas). Según esto, parece que cuanto
mayor sea el número de etapas de un pipeline, mayor es la velocidad de ejecución.
Sin embargo, los diseñadores del S/360 de IBM (años 60) ya se dieron cuenta de que la cantidad
de lógica de control necesaria para gestionar y optimizar los buffers intermedios y las
dependencias entre las etapas del pipeline crece enormemente con el número de etapas, hasta el
punto de que esta lógica de control entre etapas puede llegar a ser más compleja y costosa (en
tiempo) que la lógica propia de cada etapa.
Dada la conveniencia de un alto número de etapas, a medida que se consiguen avances en la
tecnología, los procesadores cada vez disfrutan de un mayor número de etapas, consiguiendo así,
la correspondiente mejora en sus pretaciones.
Pipelining ≠ Paralelismo
(Especialización) (Replicación)
t1 t2 t3
I1 F1 D1 O1 E1 W1 tiempo
I2 F1 D1 O1 E1 W1
I3 F1 D1 O1 E1 W1
Pipelining ≠ Paralelismo
(Especialización) (Replicación)
t1
I1 F1 D1 O1 E1 W1 tiempo
I2 F2 D2 O2 E2 W2
. . . . . . . . . . .
I3 F3 D3 O3 E3 W3
Pipelining ≠ Paralelismo
(Especialización) (Replicación)
t1 t2 t3
I1 F1 D1 O1 E1 W1 tiempo
I2 F2 D2 O2 E2 W2
I3 F3 D3 O3 E3 W3
En el caso del pipeline, la segunda instrucción puede comenzar en cuanto la primera instrucción
haya finalizado su primera etapa. A partir del momento en que se llena el pipeline (después de
cinco ciclos) se tienen cinco instrucciones ejecutándose en distintas fases, y se puede empezar a
obtener un resultado por ciclo, pues finalizará una instrucción después de cada ciclo.
Obsérvese que el rendimiento de un pipeline no depende exactamente del número de etapas,
sino de la duración de su etapa más larga.
Aunque con una organización totalmente distinta, en cuanto al rendimiento, el paralelismo y el
pipeline se pueden considerar equivalentes.
No olvidar que la técnica de la segmentación o pipelining mejora el rendimiento no el tiempo de
ejecución de cada instrucción.
Mantener siempre
activas todas las etapas
Pero
¡ No Resulta Fácil !
Motivos Estructurales
Dependencias de Operandos
Instrucciones de Bifurcación
Una vez elegido el número óptimo de etapas, para que el factor de aceleración sea igual al número
de etapas se requiere que todas las etapas del pipeline siempre estén llenas de instrucciones
útiles, y que nada retrase el avance de las instrucciones a través del pipeline.
Por desgracia, no es fácil mantener siempre ocupadas todas las etapas del pipeline. Hay tres
causas que lo impiden:
• Motivos estructurales.
• Dependencias de operandos.
• Instrucciones de bifurcación.
En las siguientes transparencias las comentaremos con cierto detalle.
Como ya veremos, se tiende a que la ejecución de cada etapa se realice en un ciclo de reloj. Pues
bien, cuando una etapa no es capaz de realizar su cometido en un ciclo de reloj, el pipeline
se detiene hasta que dicha etapa finaliza su trabajo. Hay varias causas estructurales (arquitectura
del pipeline) que pueden hacer que el pipeline se detenga.
Por ejemplo, puede ocurrir que no todas las etapas sean de la misma duración, con lo que
alguna etapa de corta duración debería esperar a que acabe la siguiente que es más larga. Esto
hará que la duración efectiva de cada etapa sea igual a la duración de la etapa más larga.
Normalmente los procesadores actuales tienden a un alto número de etapas, con lo que
automáticamente tienden a igualarse los tiempos.
Otra cosa que también puede ocurrir es que desde varias etapas se quiera acceder a memoria
simultáneamente (por ejemplo en la etapa de alimentación de instrucción y en la escritura del
resultado). Y, claro, si una etapa se detiene para esperar a poder realizar el acceso a memoria, el
pipeline se para.
También tenemos que considerar que no todas las instrucciones hacen las mismas cosas, por
lo que requieren tiempos distintos de CPU. Pasemos a la siguiente página para tratar este caso
con más detalle.
No todas las instrucciones hacen las mismas cosas y requieren el mismo tiempo de CPU.
Unas pueden necesitar más tiempo en la etapa de ejecución (por ejemplo, la carga o escritura de
un registro requiere menos trabajo de ALU que una división en coma flotante), mientras que otras
pueden necesitar más tiempo para obtener los operandos o escribir el resultado (si están en
memoria principal se tarda más que si están en registros).
En el ejemplo de la transparencia vemos que la instrucción I2 no puede completar la fase de
alimentación de operandos en el ciclo 4, necesitando para ello también los ciclos 5 y 6. Esto hace
que en el 5º ciclo no pueda alimentarse la instrucción I5 por estar ocupada la etapa de extracción
de instrucción, debiendo esperar ésta al ciclo 7 para poder continuar extrayendo instrucciones.
Obsérvese que como consecuencia del sobretiempo de O2, al término de los ciclos 6 y 7 no finaliza
ninguna instrucción (lo cual va en perjuicio del rendimiento).
Puede suceder incluso que alguna de las etapas ni siquiera necesite ejecutarse. Por ejemplo,
en un procesador cuya última etapa se dedique a escribir en memoria principal, la carga de un
registro no requerirá dicha última etapa. En este caso, simplemente sucederá que cuando una
instrucción corta va después de una normal, ambas finalicen su ejecución simultáneamente, y en el
siguiente ciclo de reloj no termine ninguna instrucción; por lo tanto, el rendimiento no varía.
Aquí, para simplificar el problema, supondremos que todas las instrucciones pasan por todas las
etapas y que todas son de la misma duración.
Nos permitimos realizar estas simplificaciones para poder centrarnos en las principales causas de
la ralentización del pipeline: las dependencias de datos y las bifurcaciones. Veámoslas a
continuación.
- RAW
Dependencia - WAR
de Datos
- WAW
Las dependencias de datos se producen cuando dos instrucciones comparten un dato (operando o
resultado). La situación es la siguiente: Una instrucción Ij actualiza el valor de una variable, pero
una instrucción posterior, Ik, accede a esa variable antes de que Ij haya terminado la operación.
Hay tres tipos de dependencias de datos, pero aquí vamos a comentar solamente el más evidente,
el que se denomina “lectura después de escritura” (Read After Write, o RAW).
En el programa del ejemplo, la dependencia que se denomina “lectura después de escritura” (Read
After Write, o RAW) puede producirse entre las instrucciones I2 e I3 si la instrucción MUL lee el
contenido de R3 (en el ciclo 5) antes de que el resultado de la suma anterior (al final del ciclo 6) se
cargue en él. Obviamente, la operación MUL no se ejecutará con los operandos esperados por el
programador, por lo que el resultado del programa será incorrecto.
Hay dos opciones básicas para resolver este problema de dependencia de datos; uno es mediante
la prevención: evitando que pueda llegarse a esta situación de dependencia; el otro es mediante
la detección y resolución, es decir, no preocupándose de evitarlo, pero sí de detectarlo en caso
de que se produzca y solucionarlo de alguna manera. Veámoslas en detalle.
Ejemplo 1
... ...
I1 load R7,200 I2 add R1,R2,R3
I2 add R1,R2,R3 I1 load R7,200
I3 mul R3,R4,R5 I4 load R1,200
I4 load R1,200 I3 mul R3,R4,R5
I5 load R2,300 I5 load R2,300
... ...
¡ !
...Y si no se pueden reordenar
las instrucciones sin alterar Insertar NOP
la lógica del programa ...
I1 load R1,200
Ejemplo 2 NOP
... NOP
I1 load R1,200 I2 add R1,R2,R3
I2 add R1,R2,R3 NOP
I3 mul R3,R4,R5 NOP
I4 add #1,R3,R3 I3 mul R3,R4,R5
... I4 add #1,R3,R3
...
Detección y Resolución
Detener el Pipeline
- Anticipación
t1 t2 t3 t4 t5 t6 t7 t8 t9
load R7,200 F D O E →r7
load R1,R2 F F F D O E
load R3,R4 F D O
• Detener el pipeline. La aproximación más sencilla para evitar los problemas de dependencias
de datos con ayuda del hardware es detener la actividad en las etapas necesarias del pipeline
hasta que desaparezca la dependencia, es decir, hasta que se pueda ejecutar correctamente
la instrucción dependiente. En el ejemplo de la transparencia, esto significa detener las
instrucciones que siguen a la instrucción add desde el ciclo 5 hasta que el registro R3 pueda
leerse debidamente actualizado en el ciclo 7.
Con esta estrategia, primero se detecta la dependencia y después se detiene el pipeline a
partir de la instrucción dependiente hasta que desaparece la dependencia.
Detección y Resolución
- Detener el Pipeline
Anticipación
F D r1,r2 ¬ →r3
op1 op2
Regs.
F D D r3,r4 ² →r5
ALU F F D O E
Result. F D O
Dir. Contenido
8 ...
10 LOAD R7,200
12 ADD R1,R2,R3 El flujo normal Instrucciones
de un programa en direcciones
14 MUL R3,R4,R5
es secuencial consecutivas de
16 LOAD R1,R2 memoria
18 LOAD R3,R4
20 JMP 24 ¡ La siguiente instrucción no
es la siguiente en memoria !
22 SHR R2,#1
¿ ?
24 OR R2,R3,R2
Cuál es la dirección
26 JNZ 50 de la siguiente instrucción
28 ADD #4,R6,R6
30 ...
Ya hemos comentado que uno de los principales problemas en el diseño de un pipeline consiste en
asegurar el mantenimiento de un flujo constante de instrucciones alimentando sus diversas etapas
para así poder mantener también constante el ritmo de ejecución de instrucciones (idealmente, una
por ciclo).
El flujo normal de ejecución de un programa es secuencial, por lo que las instrucciones que se van
alimentando y ejecutando están en direcciones consecutivas de memoria. Por desgracia, las
instrucciones de bifurcación (que suelen representar alrededor del 20% de las instrucciones
ejecutadas) pueden romper el flujo constante de instrucciones alimentadas.
Cuando se alimenta una instrucción en la CPU, lo primero que se hace es incrementar el registro
Contador de Programa para conocer la dirección de la siguiente instrucción a ejecutar y extraerla.
Pero si se trata de una instrucción de salto, hasta que no llega a la etapa de ejecución no se
establece en el Contador de Programa la dirección de la siguiente instrucción a ejecutar, por lo que
la etapa de alimentación de instrucción no sabe por dónde seguir alimentando instrucciones.
¡Tenemos un problema con los saltos!
F D O E W
20 JMP 24 t24
F D O E W
24 OR R2,R3,R2
26 JNZ 50
F D O E W
t?? t50
50 LOAD R2,R4 F D O E W
Huecos
de
retardo
Bifurcación Retardada
El compilador debe insertar
Si una bifurcación h instrucciones después de
produce h huecos la bifurcación.
de retardo El efecto de la bifurcación
se debe retardar h ciclos.
Secuencia de Secuencia de
Ejecución Cuando NO Ejecución Cuando SÍ
Programa se toma la Bifurcación se toma la Bifurcación
... ... ...
... ... ...
I1: MOV #0,R2 I1: MOV #0,R2 I1: MOV #0,R2
I2: MOV #0,R7 I2: MOV #0,R7 I2: MOV #0,R7
I3: LD R8,VALOR I3: LD R8,VALOR I3: LD R8,VALOR
I4: ADD #1,R8 I4: ADD #1,R8 I4: ADD #1,R8
I5: CMP R2,R3 I5: CMP R2,R3 I5: CMP R2,R3
I6: BEQ I11 I6: BEQ I11 I6: BEQ I11
I7: ADD #1,R3 I7: ADD #1,R3 I7: ADD #1,R3
I8: ADD #2,R4 I8: ADD #2,R4 I8: ADD #2,R4
I9: MUL R3,R4 I9: MUL R3,R4 I9: MUL R3,R4
I10: ST R4,TOTAL I10: ST R4,TOTAL I11: ADD #1,R5
I11: ADD #1,R5 I11: ADD #1,R5 I12: ST R5,TOTAL
I12: ST R5,TOTAL I12: ST R5,TOTAL ...
... ... ...
... ... ...
... ...
Veamos con un ejemplo el efecto de las bifurcaciones retardadas en un procesador con 3 huecos
de retardo en los saltos.
Supongamos que tenemos el fragmento de un programa como el de arriba a la izquierda. Ahora lo
ejecutaremos y veremos su comportamiento.
En el cuadro del medio tenemos la secuencia de ejecución del programa en el caso en que, de
acuerdo a la instrucción de comparación I5, en el salto condicional I6 se decide continuar la
ejecución sin tomar el salto, con lo que se continúan ejecutando las instrucciones I7, I8, I9, I10,
I11, I12, …
Ahora veamos en el cuadro de la derecha el comportamiento del programa cuando, en ejecución,
sí se debe tomar la bifurcación de la instrucción I6. Como se puede observar, después de la
ejecución de la instrucción de salto I6, a pesar de que se determina que se debe saltar, se
continúan ejecutando las 3 instrucciones siguientes: I7, I8 e I9, y luego ya, sí se salta a la
instrucción I11, como establece la sentencia de salto I6.
¡ !
... y si no se pueden reordenar
las instrucciones
sin alterar la lógica del programa
Insertar
Instrucciones
NOP
I1→I2→I3→NOP→NOP→NOP→I11→I12→...
Esta técnica de los saltos retardados requiere la colaboración del compilador, que debe saber
cómo reorganizar el código para rellenar los huecos de retardo con instrucciones útiles (de la
misma manera que se hacía con las dependencias de datos).
Si el compilador no encuentra una manera de reordenar el código sin afectar a su semántica, debe
insertar operaciones NOP en los huecos de retardo.
Si ahora suponemos que el control del bucle se realiza mediante una instrucción al comienzo del
mismo, ahora lo normal será suponer que la bifurcación no se tomará hasta la última pasada del
bucle. Es decir, hay veces que conviene suponer una cosa y otras veces otra.
Esto que hemos visto se denomina ejecución especulativa, pues las instrucciones pueden
empezar a ejecutarse antes de que el procesador sepa que las instrucciones alimentadas son las
realmente correctas. Supongamos que se predice que el salto tendrá lugar, por lo que se empiezan
a alimentar instrucciones y a pasarlas a las siguientes etapas del pipeline antes de que la
instrucción de bifurcación finalice su etapa de ejecución. ¡Y si al ejecutar la bifurcación no se
realiza el salto! ¡Nos encontramos que algunas instrucciones ya se han empezado a ejecutar en las
etapas anteriores!
Con ejecución especulativa se debe tener cuidado de que en las etapas anteriores a la de
ejecución no se modifiquen registros o posiciones de memoria hasta que no se confirme que la
predicción realizada ha sido la acertada.
Mejor si el procesador
lleva la cuenta de cada PREDICCIÓN DINÁMICA
instrucción de salto