PIPELINE.pdf
Document Details
Uploaded by LighterTheme9174
Unpamplona
Full Transcript
UNPAZ 2021 – LGTI – AC2 Unidad 2 – Segmentación del Cauce (Pipeline) 1- Conceptos Básicos En este capítulo vamos a ver cómo mejorar las prestaciones de la CPU mediante los procesadores segmentados (o en pipeline), los cuales incorporan una técnica para acelerar el ritmo de ejecución de las instrucci...
UNPAZ 2021 – LGTI – AC2 Unidad 2 – Segmentación del Cauce (Pipeline) 1- Conceptos Básicos En este capítulo vamos a ver cómo mejorar las prestaciones de la CPU mediante los procesadores segmentados (o en pipeline), los cuales incorporan una técnica para acelerar el ritmo de ejecución de las instrucciones. En primer lugar, empezaremos por comentar algunos aspectos básicos para introducir los conceptos en los que se apoya esta técnica. A continuación, nos centraremos en nuestro procesador de referencia, el MIPS64, viendo la configuración de las etapas que componen su cauce. Por último, abordaremos una mejora más del pipeline mediante el concepto de operaciones multiciclo que reducirán las esperas o paradas generadas por las instrucciones pesadas, como las multiplicaciones, divisiones o las operaciones en coma flotante. La primera opción que se nos ocurre para aumentar la velocidad de un procesador es aumentar la velocidad del reloj o clock, lo cual es muy fácil, pero dependerá de que los circuitos que componen las etapas de ejecución del procesador la soporten, si no puede quemarse o dejar de funcionar correctamente. Así, para poder aumentar la velocidad del reloj, primero se deben 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 de 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, 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 70 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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. Ahora ya podemos abordar el concepto de pipeline. El proceso en pipeline (o segmentado) es similar al utilizado en cualquier cadena de montaje, y el nombre pipeline (tubería) se debe al hecho de que, como en una tubería, en la entrada se aceptan nuevos elementos (instrucciones) antes de que los previamente aceptados salgan por la salida. Empecemos con el ejemplo de una cadena de montaje. Supongamos una gran pastelería en la que las tartas primero se hacen en el horno y después se empaquetan para la venta. El proceso de empaquetar una tarta consiste en: 1. Poner una caja vacía en la mesa. 2. Meter una tarta en la caja. 3. Cerrar y precintar la caja. 4. Poner una etiqueta en la caja. 5. Llevar la caja a un gran contenedor. Si cada una de estas operaciones la realiza un operario en 10 segundos, parece claro que se tarda 50 s en empaquetar una tarta y, por lo tanto, en empaquetar 10 tartas se tardaría 500 s. 71 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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, solamente se tardará100 segundos en tener 10 tartas empaquetadas, 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). Ha aumentado el rendimiento, pero se mantiene el tiempo de empaquetado de cada tarta. Si calculamos el rendimiento de los dos sistemas de empaquetado de tartas, veremos que el rendimiento en este último caso se ha multiplicado por 5 (¡igual al número de etapas!). 72 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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. 73 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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. 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 / Lectura de registros 3. E: Ejecución (en la ALU) / Cálculo de la dirección efectiva 4. M: Acceso a memoria 5. W: Escritura del resultado en registros de la CPU 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 de la segmentación (o 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. Debemos tener en cuenta que: Cada etapa dispone de los recursos hardware necesarios para realizar su cometido. Las ejecuciones de las instrucciones se solapan. Todas las etapas tienen la misma duración (ciclo de reloj). La duración del ciclo de reloj lo fija la etapa más lenta. 74 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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 prestaciones. Obsérvese que el pipelining no es lo mismo que el paralelismo (aunque, en cierto modo, en el pipeline también hay paralelismo). Ambas técnicas están dirigidas a mejorar el rendimiento (número de instrucciones por unidad de tiempo) incrementando el número de módulos hardware 75 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 que operan simultáneamente, pero en el primer caso, el hardware para ejecutar una instrucción no está replicado, simplemente está dividido en varias etapas distintas especializadas, mientras que en las arquitecturas paralelas, el hardware (la CPU) sí está replicado (hay varios procesadores), por lo que varias operaciones pueden ejecutarse de manera completamente simultánea. El incremento del rendimiento con el pipelining está limitado al máximo número de etapas del procesador, mientras que, con el paralelismo, las prestaciones mejoran siempre que se añadan más procesadores (en situaciones ideales) y el trabajo se pueda descomponer en varias tareas para poder repartirlo. Veamos, a continuación, los diagramas correspondientes a la ejecución en serie, en paralelo y mediante pipeline, suponiendo que las instrucciones se ejecutan en cinco pasos o etapas de un ciclo de reloj cada una. En la ejecución en serie, la primera instrucción debe ejecutarse completamente antes de comenzar la segunda, y ésta debe completarse a su vez antes de que comience la tercera. De esta manera, si las instrucciones son sumas, por ejemplo, se obtiene un resultado cada cinco ciclos (en t1, en t2 en t3,....). Con un paralelismo de N vías (N procesadores), se pueden ejecutar simultáneamente N instrucciones, pero producirán resultados solamente cada 5 ciclos (N resultados cada 5 ciclos). Ya que se producen N resultados en el mismo tiempo en el que la ejecución en serie obtiene un único resultado, el incremento o aceleración (en el caso ideal) es N. 76 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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. Esto significa que con el mismo HW se pudo mejorar su rendimiento general. 77 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 En este histograma podemos ver la comparación entre dos procesadores de similares características y que su gran diferencia consiste en estar o no estar segmentados. Utilizaremos el procesador del ejemplo que hemos visto en la página anterior, en el que supondremos que el ciclo de reloj es 1 ns. El ciclo de reloj es igual para ambos casos (1 ns), ya que partimos de dos procesadores con similares características. El tiempo de ejecución de la instrucción (si todas las instrucciones pasan por todas las etapas) también es igual en ambos casos (5 ciclos). En cuanto al rendimiento, el procesador no segmentado ejecuta una instrucción, que tarda 5 ciclos, y hasta que no termina, no comienza la ejecución de otra instrucción, por lo que termina una instrucción cada 5 ciclos, o sea, que se consigue un rendimiento de 1 instrucción cada 5 ciclos. En cambio, en el caso del procesador segmentado, cuando una instrucción arranca, al ciclo siguiente se arranca la siguiente instrucción y, de esta manera, cuando la primera instrucción termina, al ciclo siguiente termina la segunda. De esta manera, tenemos que a cada ciclo finaliza una instrucción, consiguiente un rendimiento de una instrucción por ciclo o, como vemos en el gráfico, de 5 instrucciones cada 5 ciclos. 78 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 Como habíamos visto anteriormente, la aceleración o speed up, es el cociente entre el tiempo medio que transcurre entre la finalización de las instrucciones en el procesador no segmentado y en el segmentado. En el procesador segmentado, en condiciones ideales, a cada ciclo finaliza la ejecución de una instrucción, consiguiendo un número medio de ciclos por instrucción (CPI) igual a 1. No debemos olvidar que en un procesador segmentado (una vez que está lleno el cauce y en condiciones ideales), en un momento dado hay tantas instrucciones en ejecución como etapas tiene; no obstante, cada etapa se está ocupando de una función distinta de cada instrucción. Veamos la mejora de aceleración que se experimenta al segmentar un procesador convencional, cuando ejecuta un programa que consta del 40% de instrucciones aritméticas, un 20% de instrucciones de salto y otro 40% de instrucciones de acceso a operandos en memoria principal. Las instrucciones aritméticas del procesador no segmentado requieren 4 ciclos, mientras que las de salto y acceso a memoria consumen 5 ciclos de reloj. El procesador segmentado consta de 5 etapas, y tiene un retardo de 0,2 ns para el control y el paso de datos a cada etapa posterior. El ciclo de reloj en ambos casos es de 1 ns. El tiempo medio de ejecución de una instrucción en el procesador NO segmentado se obtiene mediante la suma de los tiempos de ejecución de cada tipo de instrucción por sus frecuencias de aparición en los programas, obteniendo así, un valor medio de 4,6 ns. En el caso del procesador segmentado, tenemos que cada etapa requiere 1 ciclo de reloj (1 ns). Así tenemos que cada instrucción tardará en ejecutarse un total de 5 ns, pero, en realidad, cada 1 ns finalizará una instrucción. De esta manera, la mejora en rendimiento (speed up) que se experimenta es el cociente entre el tiempo medio que transcurre entre la finalización de las instrucciones en el procesador no segmentado y el segmentado, consiguiendo así, una mejora del 4,6. Esto significa que, para programas con esta distribución de frecuencias en sus instrucciones, el procesador segmentado es 4,6 veces más rápido que el convencional. 79 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 2 - Etapas del MIPS64 Una vez vistos los conceptos básicos del pipeline, vamos a concretarlo en una versión simplificada de la arquitectura MIPS64. En un principio vamos a considerar una versión básica de 5 etapas, en la que la Unidad Aritmético-Lógica solamente va a operar con datos enteros. El tiempo de ejecución de cada etapa va a ser un ciclo. En esta versión básica (solo para un grupo reducido de instrucciones) vamos a disponer de una estructura en la que veremos que las instrucciones de salto generan ciertos retardos. Más adelante mejoraremos la estructura para conseguir mejorar los problemas de los retardos. Ahora iremos mostrando en las siguientes páginas la descripción de cada una de las 5 etapas de este pipeline simplificado de MIPS64. En esta primera etapa se extrae o alimenta una instrucción de memoria, de la dirección indicada en el registro Contador de Programa (PC), y se almacena en el registro de instrucción IR (Instruction Register). A continuación, se calcula la dirección de la siguiente instrucción en secuencia (por dirección en memoria), añadiéndole al PC el tamaño de la instrucción alimentada. En nuestro caso, todas las instrucciones son de longitud constante, 4 bytes. La dirección calculada se guarda en el registro NPC (Next Program Counter). La instrucción queda en el registro IR de donde se irá extrayendo información en las sucesivas etapas. 80 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 Aquí se realizan 4 funciones: Decodificación de la instrucción alimentada, es decir, se averigua cuál es la operación que se debe realizar. En paralelo se lee el contenido de los registros indicados como operandos en la instrucción, y se guardan en los registros temporales A y B. Si la instrucción contiene un campo de 16 bits con un valor inmediato, se le hace una extensión de signo a 64 bits y se guarda en el registro Inm. Si la instrucción es de bifurcación, la ALU hace dos cosas: − Primero añade al NPC el desplazamiento correspondiente al valor inmediato que se indica en la instrucción de salto (desplazado 2 bits a la izquierda, o sea, multiplicado por 4) llevando el resultado a la entrada del multiplexor de la etapa F. (En la instrucción de salto, el desplazamiento del salto se indica como múltiplo de 4, pues como todas las instrucciones ocupan 4 bytes, siempre están en una dirección múltiplo de 4). − A continuación, se comprueba si el registro A tiene el valor 0, y el resultado de la comparación se lleva a la entrada de control del multiplexor de la etapa de Fetch. La salida de este multiplexor proporciona la dirección de la siguiente instrucción que se va a ejecutar, bien la siguiente en secuencia, o bien la de la dirección indicada en la instrucción de salto. Es posible que, según la operación a realizar, que algunas de estas acciones no tengan sentido realizarlas. No obstante, si la operación no requiere alguno de los registros leídos, el valor inmediato, o la dirección de salto calculada, simplemente no se utiliza. En cualquier caso, no se pierde tiempo al hacerlo, ya que estas acciones se realizan, en paralelo, al mismo tiempo que la decodificación. 81 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 Esta es la etapa de la Unidad Aritmético-Lógica (ALU), donde se realiza una de estas tres posibles funciones, dependiendo del tipo de instrucción: Instrucción con registros (A op B a - ALUoutput). La ALU realiza la operación indicada por el código de función u operación, utilizando los valores de los registros temporales A y B. El resultado se deja en el registro temporal ALUoutput. Instrucción registro-Inmediato (A op Inm a - ALUoutput). La ALU realiza la operación indicada por el código de función u operación, utilizando los valores del registro temporal A y el valor del registro Inm (que contiene el valor inmediato de la instrucción). El resultado se deja en el registro temporal ALUoutput. Referencia a memoria (A+Inm a - ALUoutput). La ALU suma estos operandos para formar una dirección absoluta de memoria. El resultado se deja en el registro ALUoutput. Estas tres funciones distintas pueden estar en la misma etapa (aunque solo se ejecuta una de ellas en cada instrucción) ya que nunca se requiere que se haga más de una de estas operaciones sobre la misma instrucción. Los desplazamientos de los saltos se indican como múltiplos de 4 pues como todas las instrucciones ocupan 4 bytes, siempre están en una dirección múltiplo de 4. Referencia a memoria. Dependiendo de si se trata de una instrucción de carga o de almacenamiento, se realiza una de estas acciones: Si es una carga, el dato traído de memoria se deja en el registro temporal LMD. Si se trata de un almacenamiento, el dato del registro temporal B se escribe en memoria. En cualquier caso, la dirección de memoria utilizada es la calculada en el ciclo (etapa) anterior y que se dejó en ALUoutput. 82 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 En esta etapa se escribe un valor en alguno de los registros generales (si la instrucción lo requiere), bien si el valor viene de una posición de memoria (valor en registro temporal LMD) o se trata del resultado de una operación en la ALU. El registro en el que se deja el resultado de la operación con la ALU, depende del tipo concreto de instrucción ejecutada (tipo R o tipo I). A modo de resumen, aquí tenemos un esquema general de las etapas de MIPS64 1. F (Alimentación de instrucción – Instruction Fetch). Se extrae una instrucción de la dirección de memoria indicada por el Contador de Programa (PC) y se incrementa éste en 4, para que apunte a la dirección de la siguiente instrucción. 2. D (Decodificación de Instrucción / Lectura de registros). Se decodifica la instrucción extraída y se lee el contenido de los registros indicados como operandos en la instrucción. Si es una instrucción de salto, se calcula la dirección de destino incrementando el PC con 83 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 el desplazamiento indicado en la instrucción. En cualquier caso, la decodificación se realiza en paralelo con la lectura de los registros. 3. E (Ejecución/Cálculo de la dirección efectiva). Aquí, dependiendo del tipo de instrucción, se realiza una de estas funciones: Ejecución de una operación en la ALU (Unidad Aritmético-Lógica), si es una instrucción aritmética, lógica, desplazamiento… Cálculo de dirección efectiva (registro base + desplazamiento), si es un salto o carga/ almacenamiento. 4. M (Acceso a memoria). Si la instrucción es de acceso a memoria, para lectura o escritura (carga/almacenamiento), se realiza en esta fase, utilizando para ello el valor del registro leído en la fase D (si es una escritura en memoria) y la dirección de memoria calculada en la fase E. 5. W (Escritura en registros). Si el resultado de la instrucción, o lectura de memoria, tiene como destino un registro, se escribe en éste en esta fase. En esta figura se muestra el pipeline de la arquitectura de MIPS que se ha comentado en las páginas anteriores, y se puede ver cómo fluye una instrucción a través del cauce. Se trata de un pipeline simplificado para la ejecución de estas operaciones: − Load/Store − Operaciones con enteros − Salto condicional "si igual a cero” Como puede verse, al final de cada etapa, los valores obtenidos en ella, deben guardarse para poder acceder a ellos desde una etapa posterior. Por ello, se salvan en unos registros temporales (IR, A, B, Inm, ALUoutput, LMD). 84 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 Se puede apreciar que algunos registros están ubicados en las etapas en las que se lee su contenido, pero su escritura o actualización se produce en otras etapas. Así, el registro PC está situado en la etapa de extracción de la instrucción (F), pero, en realidad, se escribe desde la etapa D. Los registros generales están ubicados en la etapa D, pero se escriben durante la etapa W. Obsérvese que cuando la instrucción de salto se encuentra en la etapa D, sería deseable que en la etapa F se pudiera alimentar ya la siguiente instrucción. Con este cauce, la siguiente instrucción se alimenta un ciclo después de la etapa D, por lo que solamente se pierde un ciclo en los saltos. Estas presentaciones son solo para poder comprender las ventajas que ofrece el pipelining y entender que en la realidad existen 17 o más diferentes instancias y lugares físicos donde se descompone una operación. Como dijimos, cuantas más etapas mejor, y eso es lo que se buscar como mejora del rendimiento, sin entrar en paralelismo. Cuando abordemos paralelismo en la U3, recordar que este mecanismo sigue activo en cada módulo o core. 85 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 86 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 En la figura de arriba se muestran algunas de las características del pipeline básico de la arquitectura MIPS64. 87 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 3. Riesgos (Ralentización del Cauce) 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. 88 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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 arriba vemos que la instrucción I2 no puede completar la fase de ejecución 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 E2, 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, 89 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 y en el siguiente ciclo de reloj no terminará ninguna instrucción; por lo tanto, el rendimiento no varía. Si desde dos etapas se accede a un mismo recurso, una de las etapas tendrá que detener su ejecución y esperar a que quede libre el recurso necesario. Por ejemplo, en las etapas de Fectch y Ejecución puede ser necesario un acceso simultáneo a la ALU para realizar una operación aritmética, como el incremento que debe hacer al PC para actualizarlo (en F) o la suma que se requiere para realizar el cálculo de la dirección efectiva de un operando (en E). Esto se soluciona fácilmente mediante dos sumadores distintos. Por otra parte, también se puede requerir un acceso simultáneo a memoria desde las etapas F y M; para extraer la siguiente instrucción a ejecutar y para leer o escribir un operando o resultado en memoria. Para mejorar el acceso simultáneo a algunos recursos, como los registros, el ciclo se divide en dos subciclos, de tal manera que dos etapas pueden coincidir en el recurso en el mismo ciclo. 90 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 Por ejemplo, la etapa W podría escribir en un registro en el primer subciclo, y otra instrucción podría leer su contenido en el segundo subciclo, desde la etapa D (lectura de registros) Las dependencias de datos se producen cuando dos instrucciones comparten un dato (operando o resultado). Hay tres tipos de dependencias de datos: Verdadera dependencia de datos a → RAW (Read After Write) Antidependencia a → WAR (Write After Read) Dependencia de salida a → WAW (Write After Write) Los distintos tipos de dependencias pueden dan lugar a otros tantos tipos de “riesgos de datos” (RAW, WAR y WAW). Realmente, la única dependencia verdadera de datos es la que da lugar a riesgo de tipo RAW, que viene dada por la propia lógica del programa; los otros dos tipos son “dependencias de nombre” y dependen de las características del cauce. Más adelante trataremos las dependencias de nombre, pero ahora vamos a ocuparnos de la verdadera dependencia de datos (RAW). La situación es la siguiente: Una instrucción Ij actualiza el valor de una variable, pero una instrucción posterior, Ik, realiza una lectura de esa variable antes de que Ij haya terminado la operación escribiendo la variable compartida. Veamos, en las siguientes páginas ejemplos concretos de esta dependencia de datos y varias soluciones propuestas. 91 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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 dmul lee el contenido de R1 (en el segundo subciclo de t4) antes de que el resultado de la suma anterior (en el primer subciclo de t6) se cargue en él. Obviamente, la operación dmul 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. La dependencia de datos: Prevención. El problema de la dependencia de datos entre una instrucción I1 y otra instrucción I2 que le sigue puede prevenirse retrasando la ejecución de I2 un número K de etapas hasta que desaparezca el problema de que I2 lea un operando que I1 no ha escrito todavía. Este retraso puede conseguirse insertando un número K de instrucciones entre I1 e I2. Esto significa que el 92 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 compilador tiene que reordenar el programa para encontrar K instrucciones que puedan ejecutarse después de I1 y antes de I2 sin que por ello varíe la estructura lógica del programa. Ejemplo 1: En la figura tenemos el ejemplo de un programa en el que hay una dependencia entre las instrucciones I2 e I3 a causa del registro R1. Como vemos, en este programa el compilador puede detectar la dependencia de datos y reorganizar las instrucciones para retardar el acceso al registro R1 hasta que esté actualizado. Debe quedar claro que esta reorganización solamente puede hacerse si se mantiene la semántica original del programa. Por lo que hemos visto en el ejemplo de la página anterior, para evitar la dependencia de I3 respecto a I2, se requiere que I3 comience su ejecución tres ciclos después de que lo haga I2. (Suponemos que en el primer subciclo de t6, se escribe el resultado de daddi en R1, y en el segundo subciclo, se lee el operando R1 de dmul). Como se puede apreciar, esto se ha conseguido con la reorganización que ha realizado el compilador, intercambiando el orden I2 por I1 e I3 por I4. Si el compilador no puede reorganizar el código para encontrar estas K instrucciones que decíamos arriba, sin modificar la lógica del programa, debe insertar operaciones NOP (No Operación) entre las operaciones dependientes. Ejemplo 2: En el ejemplo inferior tenemos el fragmento de un programa en el que también hay dependencias entre las instrucciones I1, I2 e I3. En este caso vemos que las instrucciones de este fragmento no se pueden reordenar sin alterar la lógica del programa, por lo que el compilador inserta las instrucciones NOP necesarias para evitar la ejecución errónea de instrucciones por las dependencias de datos. La ventaja de la solución basada en la prevención es que no se requiere hardware adicional, pero a expensas de un compilador más complejo y una pérdida de tiempo si es necesario insertar instrucciones NOP (cuando no se puede reordenar el programa para insertar instrucciones útiles). 93 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 La dependencia de datos: Detección y resolución. Este método requiere un hardware adicional en la CPU, pues se deben detectar las dependencias de datos durante la ejecución y resolver estas dependencias. Detectarlas significa que debe darse cuenta de que en un momento dado hay dos instrucciones arrancadas I1 e I2, tal que I2 depende de un resultado establecido por I1. El dispositivo hardware que detecta estas dependencias se denomina interlock. Resolver las dependencias significa hacer algo para retrasar la ejecución de I2 o para acelerar, en la medida de lo posible, la entrega a I2 del resultado que produce I1. Veamos dos posibilidades de resolución (no excluyentes) para cuando se detecta una dependencia de datos entre dos etapas del pipeline. 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 figura, esto significa detener las instrucciones que siguen a la instrucción daddi desde el ciclo 4 hasta que el registro R1 pueda leerse debidamente actualizado en segundo subciclo de t6. 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. Obsérvese que la detención de n ciclos del cauce tiene el mismo efecto que la inserción de n instrucciones NOP. 94 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 El otro modo de resolver las dependencias de datos mediante Detección y Resolución es acelerando, en la medida de lo posible, la entrega a una instrucción I2 del resultado que produce una instrucción previa I1. Veámoslo en detalle: Anticipación (data forwarding). En una dependencia de datos RAW, puede suceder que una instrucción I2 necesite un operando en la etapa D (decodificación y obtención de operandos) que debe producirlo en el mismo ciclo la instrucción I1 en su etapa E (ejecución). Esto obligaría a detener la instrucción I2 hasta que I1 escriba el resultado en su etapa W, y entonces pueda continuar I2 en su etapa D y leer el resultado que produjo I1. Este retraso puede evitarse redirigiendo (forwarding) el resultado de la etapa E de la instrucción I1 directamente a la entrada de la etapa E de la instrucción I2, obteniendo el mismo efecto que se obtendría en la ejecución de la etapa D de I2. En la figura se muestra el esquema de la ejecución de 3 instrucciones, donde la I2 tiene una dependencia de datos de I1 (por R1), e I3 la tiene a su vez de I2 (por R2). Para evitar la lectura incorrecta de datos o las paradas del cauce, se puede dirigir el resultado de la etapa E de I1 directamente a la entrada de la etapa E de I2. Igualmente, también se puede dirigir el resultado de la etapa E de I2 directamente a la entrada de la etapa E de I3. En este caso se consigue evitar totalmente la detención del pipeline, pero hay otras situaciones en las que esto no es posible. 95 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 Aquí tenemos otro caso de dependencias múltiples que se intentan resolver con anticipación, pero en la situación de la instrucción de carga I1, su resultado (R1), no se consigue hasta el final de la etapa M (la de acceso a memoria), por lo que hay que detener I2 en su etapa D para dar tiempo a que I1 ejecute su etapa M y, en el ciclo siguiente, la instrucción de suma pueda obtener el valor actualizado de R1 a la entrada de su etapa de ejecución. Como vemos, en las instrucciones de carga no se consigue evitar por completo la parada del cauce (se requiere una parada de un ciclo), pero si no hubiera adelantamiento, se necesitaría una parada de 2 ciclos adicionales ciclos en la etapa F de la suma, pues habría que esperar a que se completara la etapa W de la instrucción de carga para dar paso a la etapa D (obtención de operandos) de la suma. Como se ha mostrado en las páginas anteriores, hay más de una secuencia de instrucciones que conduce a distintas situaciones de adelantamientos. Aquí mostramos algunas situaciones de adelantamientos en MIPS, en cuyo caso, los adelantamientos siempre se producen desde el final de la etapa de ejecución E (casos 1, 2 y 3) o desde el final de una lectura en memoria (casos 4, 5 y 6) para cargar un registro. 96 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 El adelantamiento se produce hacia la etapa de la instrucción en la que se requiere el dato de entrada. En los casos 3 y 5, el adelantamiento se produce hacia la etapa D, ya que es la encargada de comprobar la condición de salto (considerando la arquitectura mejorada que veremos en breve). En el caso 5, lo cierto es que no se consigue ninguna ventaja con un adelantamiento, pues en el mismo ciclo en el que la etapa M de la instrucción de carga puede adelantarle R1 a la instrucción de bifurcación en su etapa D, se ejecuta la etapa W de la instrucción de carga, escribiendo R1 en el primer subciclo, y la instrucción de bifurcación puede leerlo en el segundo subciclo de su etapa D. 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 suele hacer en la etapa Fetch es incrementar el registro Contador de Programa (PC) para conocer la dirección de la siguiente instrucción a ejecutar y extraerla en el siguiente ciclo de reloj. Pero si se trata de una instrucción de salto condicional, hay que esperar a una etapa posterior para que se pueda saber si se cumple o no la condición de salto, por lo que la etapa de alimentación de instrucción no sabe de dónde seguir alimentando instrucciones. ¡Tenemos un problema con los saltos! 97 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 Una instrucción de bifurcación condicional hace que la dirección de la siguiente instrucción a ejecutar no se conozca en la etapa F, por lo que esta etapa de alimentación no puede extraer la siguiente instrucción a una bifurcación hasta que ésta llegue a la etapa en la que ya se conoce la dirección de la siguiente instrucción a ejecutar. (En la ruta de datos que hemos visto anteriormente, esto sucede en la etapa M). Por esto, a continuación de la alimentación de la instrucción de bifurcación, las etapas F, D… se van quedando vacías al no saber qué instrucción alimentar. A estas etapas vacías que aparecen se las denomina huecos de retardo (delay slots). En algunos sistemas, las bifurcaciones incondicionales pueden detectarse en la fase de alimentación o extracción (fetch) si se le añade un poco hardware a esta primera etapa. Este hardware de ayuda también extrae la dirección de salto, con lo que se puede proseguir la extracción de instrucciones de la dirección del salto. Sin embargo, en el caso de las bifurcaciones condicionales no se puede hacer esto, pues puede ocurrir que la condición del salto se establezca precisamente en la instrucción anterior, con lo que no hay más remedio que esperar a que la bifurcación llegue a la etapa de comprobación de la condición de salto y establezca la dirección de la siguiente instrucción a ejecutar. Esto quiere decir que se debe detener la alimentación de instrucciones al pipeline hasta que en la etapa que corresponda se averigüe la dirección de la siguiente instrucción a ejecutar. Afortunadamente, hay diversas técnicas que pueden evitar o minimizar el impacto de las instrucciones de bifurcación, tales como la “bifurcación retardada”, la “predicción del salto” y algunas más. Veamos estas dos primeras técnicas. 98 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 El problema de los saltos: Bifurcación retardada. Ya hemos visto que cuando entra una instrucción de salto en el pipeline, se producen h huecos de retardo por lo que hay que esperar h ciclos hasta que llega la siguiente instrucción de la secuencia a ejecutar. Estaría bien que estos huecos se pudieran rellenar con instrucciones que siempre se deban ejecutar, independientemente de si se toma la bifurcación o no. Pero claro, si estas instrucciones están en memoria inmediatamente después de la instrucción de bifurcación, según lo que sabemos hasta ahora, no se ejecutarán si la instrucción de bifurcación decide saltar. Para conseguir que se ejecuten siempre las h instrucciones que siguen a una bifurcación, en los procesadores que tienen bifurcaciones retardadas, las instrucciones de salto no tienen efecto hasta h instrucciones después de su ejecución, por lo que independientemente del resultado de la ejecución de la bifurcación, siempre se ejecutan las h instrucciones siguientes. De esta manera no se producen los huecos de retardo. Supongamos la serie de instrucciones que se muestra aquí arriba, en la que I6 es la instrucción de salto condicional a la instrucción I11. En una CPU sin saltos retardados, la secuencia de instrucciones ejecutadas cuando la bifurcación no tiene lugar es: I1, I2, I3, I4, I5, I6, I7, I8, I9, 99 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 I10, I11, I12,... Mientras que cuando sí se produce la bifurcación, la secuencia es: I1, I2, I3, I4, I5, I6, I11, I12,... Si a este mismo procesador (con un hueco de retardo en las bifurcaciones) se le ponen bifurcaciones retardadas, la secuencia de instrucciones ejecutadas cuando se produce la bifurcación es: I1, I2, I3, I4, I5, I6, I7, I11, I12,... Es decir, I7, se ejecuta siempre, haya o no bifurcación. Esto es así porque el efecto de la bifurcación se retarda un ciclo de reloj, es decir, después de alimentar una instrucción de salto, se siguen extrayendo y ejecutando las instrucciones siguientes, según su orden en la memoria, durante un ciclo más de reloj. Después, se establece en el contador de programa la dirección indicada en la instrucción de salto, con lo que la 2ª instrucción después de la de salto, será ya la correspondiente a la de la dirección indicada en dicha instrucción de salto. Aquí tenemos un ejemplo, en un ensamblador hipotético, en el que para un programa dado (el de la izquierda), se muestran en la parte derecha las dos posibilidades de ejecución en un procesador con un hueco de retardo en los saltos: tanto cuando no se toma la bifurcación, como cuando sí se toma. 100 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 A partir de un fragmento de código escrito para un procesador sin saltos retardados, si se va a ejecutar en una CPU con saltos con un hueco de retardo, sabiendo lo que sucede en los procesadores con bifurcaciones retardadas (tarda un ciclo más en bifurcar realmente), vamos a intentar reordenar nuestro fragmento de código para que su ejecución se produzca con la misma semántica que la esperada en un procesador sin saltos retardados. Así, después de la instrucción de salto vamos a poner una de las instrucciones que hay antes de la de salto, es decir, una instrucción que queremos que se ejecute siempre (se produzca el salto o no). Así, suponiendo que no se altera la semántica del programa, podríamos mover la instrucción I4 y ponerla justo a continuación de I6 (la bifurcación condicional), quedando entonces la secuencia de instrucciones en memoria así: I1, I2, I3, I5, I6, I4, I7, I8, I9, I10, I11, I12,... Ahora vamos a ejecutar este programa en nuestro procesador con bifurcaciones retardadas de un ciclo. Cuando no se produce la bifurcación, la secuencia de instrucciones que se ejecuta es: I1 – I2 – I3 – I5 – I6 – I4 – I7 – I8 – I9 – I10 – I11 – I12 –... Si la bifurcación tiene lugar, las instrucciones se ejecutarán en este orden: I1 – I2 – I3 – I5 – I6 – I4 –I11 – I12 –... Si no hubiese bifurcación retardada, al alimentar una instrucción de salto condicional habría que detener la alimentación de instrucciones hasta que la instrucción de salto pasase por la etapa de decodificación (hasta saber cuál es la siguiente instrucción a ejecutar). Con bifurcación retardada se aprovechan ese ciclo perdido en alimentar y ejecutar una instrucción que se desea ejecutar incondicionalmente antes de que la instrucción de bifurcación tenga lugar (se salte o no). 101 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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, tantas operaciones NOP con huecos de retardo como sean necesarias para las bifurcaciones de ese procesador. El problema de los saltos: Predicción del salto. Otra técnica para reducir el problema de las bifurcaciones consiste en intentar predecir si una instrucción de bifurcación saltará o no. Por ejemplo, una bifurcación al final de un bucle salta al comienzo de éste todas las veces excepto la última. Según esto, sería ventajoso que cuando el procesador se encuentra una instrucción de salto suponga que el salto sí se va a efectuar realmente, y cuando la etapa de alimentación detecte una bifurcación, empiece a extraer instrucciones de la dirección de destino del salto. Si una vez que se ejecuta la instrucción de bifurcación, resulta que efectivamente se salta, la ejecución continúa normalmente, pues las instrucciones de la dirección del salto son las que ya se están alimentando. Si por el contrario resulta que no se realiza el salto, se debe vaciar el pipeline (desechar todas las instrucciones erróneamente alimentadas) y empezar a alimentar las instrucciones que siguen en secuencia. 102 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 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. La repetición de la bifurcación que se toma en el bucle la puede detectar el compilador y establecer la predicción mediante un cierto código de operación en la bifurcación, lo que quiere decir que, en ejecución, siempre que se alimente esa instrucción de bifurcación se hará la misma predicción. Por esto, se la conoce como predicción estática. 103 Fabián Palacios - 2022 UNPAZ 2021 – LGTI – AC2 La predicción se puede mejorar si se realiza dinámicamente, para lo cual el hardware del procesador debe establecer la posibilidad de que haya o no salto cada vez que se encuentre una cierta instrucción de bifurcación. Para ello, en la CPU se debe llevar la cuenta de los resultados de las últimas ejecuciones de cada bifurcación. Ciertos estudios estadísticos dicen que conservando solamente el resultado de la última ejecución (con un bit) ya se obtiene una gran probabilidad de acierto (del orden del 90%) y con la historia de las cuatro últimas ejecuciones (dos bits) se mejora ligeramente; manteniendo una historia mayor, la mejora es despreciable. Como hemos visto, la predicción ante un salto puede ser “saltar” o “no saltar”, para decidir por dónde continúa extrayendo instrucciones. Para la arquitectura MIPS, con una porción de código concreta y con los mismos datos, vamos a ver lo que sucede cuando la predicción es “no saltar” y, posteriormente, cuando es “saltar”. Predicción NO SALTAR: Después de alimentar la instrucción de salto, se continúa con la Decodificación, pero hasta el final de esta etapa no se sabrá si habrá que saltar o no. Como la predicción es “no saltar”, en la etapa F se alimenta la siguiente instrucción en memoria. Al final de la etapa D se ve que se ha cometido un fallo en la predicción, por lo que habrá que eliminar la instrucción daddi del cauce y empezar a extraer y ejecutar la instrucción de destino del salto. Se ha perdido un ciclo de tiempo. Predicción SALTAR: Igual que antes, después de alimentar la instrucción de salto, se continúa con la Decodificación. Como la predicción es “saltar”, en este ciclo se querría extraer la siguiente instrucción de la dirección del salto, pero como hasta el final de la Decodificación tampoco se conoce la dirección del salto, en este ciclo no se puede extraer ninguna instrucción, y hay que esperar al final de la Decodificación para poder alimentar la instrucción de destino del salto. Como vemos, en MIPS, ante una condición que establezca que se debe saltar, se pierde un ciclo tanto con una predicción de “saltar” como de “no saltar”. Por el contrario, si la condición se evaluara como falsa (no saltar), con la predicción de NO SALTAR, sí se podría seguir alimentando instrucciones de la siguiente dirección de memoria. Por lo que hemos visto, en MIPS 64 no tiene ninguna utilidad predecir “Tomar el salto”, por lo que su predicción es NO SALTAR. 104 Fabián Palacios - 2022