En arquitectura de computadoras , una ranura de retardo es una ranura de instrucción que se ejecuta sin los efectos de una instrucción precedente. [ 1 ] La forma más común es una instrucción arbitraria ubicada inmediatamente después de una instrucción de salto en una arquitectura RISC o DSP ; esta instrucción se ejecutará incluso si se toma el salto precedente. Esto hace que la instrucción se ejecute fuera de orden con respecto a su ubicación en el código ensamblador original .
Los diseños de procesadores modernos generalmente no utilizan ranuras de retardo, sino que implementan formas cada vez más complejas de predicción de bifurcaciones . En estos sistemas, la CPU pasa inmediatamente a lo que cree que será la rama correcta, eliminando así la necesidad de que el código especifique alguna instrucción no relacionada, que no siempre resulta obvia en tiempo de compilación. Si la suposición es errónea y se debe llamar a la otra rama, esto puede generar un retardo considerable. Sin embargo, esto ocurre con tan poca frecuencia que la mejora en la velocidad que supone evitar la ranura de retardo se compensa fácilmente con el menor número de decisiones erróneas.
Tuberías
Una unidad central de procesamiento generalmente ejecuta instrucciones del código máquina mediante un proceso de cuatro pasos: primero, la instrucción se lee de la memoria; luego, se decodifica para comprender qué se debe hacer; a continuación, se ejecutan esas acciones; y finalmente, los resultados se escriben de nuevo en la memoria. En los primeros diseños, cada una de estas etapas se realizaba en serie, de modo que las instrucciones tardaban un múltiplo del ciclo de reloj de la máquina en completarse. Por ejemplo, en el Zilog Z80 , el número mínimo de ciclos de reloj necesarios para completar una instrucción era de cuatro, pero podía llegar a ser de hasta 23 ciclos para algunas instrucciones (poco frecuentes). [ 2 ]
En cualquier etapa del procesamiento de una instrucción, solo interviene una parte del chip. Por ejemplo, durante la etapa de ejecución, normalmente solo está activa la unidad aritmético-lógica (ALU), mientras que otras unidades, como las que interactúan con la memoria principal o decodifican la instrucción, permanecen inactivas. Una forma de mejorar el rendimiento general de un ordenador es mediante el uso de una tubería de instrucciones . Esto añade circuitos adicionales para mantener los estados intermedios de la instrucción a medida que fluye a través de las unidades. Si bien esto no mejora la temporización del ciclo de ninguna instrucción individual, la idea es permitir que una segunda instrucción utilice las demás subunidades de la CPU cuando la instrucción anterior haya avanzado. [ 3 ]
For instance, while one instruction is using the ALU, the next instruction from the program can be in the decoder, and a third can be fetched from memory. In this assembly line type arrangement, the total number of instructions processed at any time can be improved by up to the number of pipeline stages. In the Z80, for example, a four-stage pipeline could improve overall throughput by four times. However, due to the complexity of the instruction timing, this would not be easy to implement. The much simpler instruction set architecture (ISA) of the MOS 6502 allowed a two-stage pipeline to be included, which gave it performance that was about double that of the Z80 at any given clock speed.[4]
Branching problems
A major issue with the implementation of pipelines in early systems was that instructions had widely varying cycle counts. For instance, the instruction to add two values would often be offered in multiple versions, or opcodes, which varied on where they read in the data. One version of add might take the value found in one processor register and add it to the value in another, another version might add the value found in memory to a register, while another might add the value in one memory location to another memory location. Each of these instructions takes a different amount of bytes to represent it in memory, meaning they take different amounts of time to fetch, may require multiple trips through the memory interface to gather values, etc. This greatly complicates the pipeline logic. One of the goals of the RISC chip design concept was to remove these variants so that the pipeline logic was simplified, which leads to the classic RISC pipeline which completes one instruction every cycle.
However, there is one problem that comes up in pipeline systems that can slow the performance down. This occurs when the next instruction may change depending on the results of the last. In most systems, this happens when a branch occurs. For instance, consider the following pseudo-MIPS assembly:
arriba: lw t0 , 0 ( a0 ) ; carga el valor de la dirección de memoria en el registro a0 en el registro t0 lw t1 , 4 ( a0 ) ; carga el valor de la dirección de memoria [valor del registro a0 más 4] en el registro t1 add t2 , t0 , t1 ; suma los registros t0, t1 en el registro t2 sw t2 , 0 ( a1 ) ; almacena el valor del registro t2 en la dirección de memoria en el registro a1 lw t4 , 8 ( a0 ) ; carga el valor de la dirección de memoria [valor del registro a0 más 8] en el registro t4En este caso, el programa es lineal y se puede segmentar fácilmente. Tan pronto como lwse lee la primera instrucción (de carga de palabra) y se decodifica, lwse puede leer la segunda instrucción de la memoria. Cuando la primera se ejecuta, addse lee de la memoria mientras la segunda lwse decodifica, y así sucesivamente. Aunque la primera instrucción tarda el mismo número de ciclos en completarse lw, para cuando se completa, el valor de la segunda instrucción está listo y la CPU puede sumarlos inmediatamente. En un procesador sin segmentación, las primeras cuatro instrucciones tardarán 16 ciclos en completarse; en uno segmentado, solo cinco.
Ahora consideremos qué sucede cuando se agrega una rama:
arriba: lw t0 , 0 ( a0 ) ; carga el valor de la dirección de memoria en el registro a0 en el registro t0 lw t1 , 4 ( a0 ) ; carga el valor de la dirección de memoria [valor del registro a0 más 4] en el registro t1 add t2 , t0 , t1 ; suma los registros t0, t1 en el registro t2 li t3 , 1000 ; establece el registro t3 en 1000 bgt t2 , t3 , arriba ; si t2 > t3, vuelve a "arriba:" ; de lo contrario: sw t2 , 0 ( a1 ) ; almacena el valor en el registro t2 en la dirección de memoria en el registro a1 lw t4 , 8 ( a0 ) ; carga el valor de la dirección de memoria [valor del registro a0 más 8] en el registro t4En este ejemplo, el resultado de la comparación en la línea cuatro hará que la "siguiente instrucción" cambie; a veces será la siguiente sw(almacenar palabra) en memoria, y a veces será la lwde memoria en la parte superior. La tubería del procesador normalmente ya habrá leído la siguiente instrucción, la sw, para cuando la ALU haya calculado qué ruta tomará. Esto se conoce como riesgo de bifurcación . Si tiene que volver al principio, la swinstrucción debe descartarse y la lwinstrucción debe leerse de memoria en su lugar. Eso toma un ciclo de instrucción completo, como mínimo, y resulta en que la tubería esté vacía durante al menos el tiempo de una instrucción. Esto se conoce como "bloqueo de tubería" o "burbuja", y, dependiendo del número de bifurcaciones en el código, puede tener un impacto notable en el rendimiento general.
Ranuras de retardo de rama
Una estrategia para abordar este problema es utilizar una ranura de retardo , que se refiere a la ranura de instrucción posterior a cualquier instrucción que necesite más tiempo para completarse. En los ejemplos anteriores, la instrucción que requiere más tiempo es la bifurcación, que es, con diferencia, el tipo más común de ranura de retardo, y a estas se las suele denominar ranura de retardo de bifurcación .
En las primeras implementaciones, la instrucción que seguía a la bifurcación se rellenaba con una operación nula, o NOP, simplemente para completar la tubería y asegurar la sincronización correcta, de modo que cuando se NOPcargara desde la memoria, la bifurcación estuviera completa y el contador de programa pudiera actualizarse con el valor correcto. Esta solución simple desperdicia el tiempo de procesamiento disponible. Las soluciones más avanzadas, en cambio, intentarían identificar otra instrucción, normalmente cercana en el código, para colocarla en el espacio de retardo y así realizar el trabajo útil.
En los ejemplos anteriores, la lwinstrucción (de carga) al final es completamente independiente, no depende de ninguna otra información y puede ejecutarse en cualquier momento. Esto la hace adecuada para su ubicación en la ranura de retardo de bifurcación. Normalmente, esto lo gestionaría automáticamente el programa ensamblador o el compilador , que reordenaría las instrucciones.
arriba: lw t0 , 0 ( a0 ) ; carga el valor de la dirección de memoria en el registro a0 en el registro t0 lw t1 , 4 ( a0 ) ; carga el valor de la dirección de memoria [valor del registro a0 más 4] en el registro t1 add t2 , t0 , t1 ; suma los registros t0, t1 en el registro t2 li t3 , 1000 ; establece el registro t3 en 1000 bgt t2 , t3 , arriba ; si t2 > t3, vuelve a "arriba:" ; ranura de retardo (se ejecutará incondicionalmente) lw t4 , 8 ( a0 ) ; carga el valor de la dirección de memoria [valor del registro a0 más 8] en el registro t4 ; de lo contrario: sw t2 , 0 ( a1 ) ; almacena el valor en el registro t2 en la dirección de memoria en el registro a1Cuando se ejecuta la rama, se procede a realizar la siguiente instrucción. Para cuando el procesador lee dicha instrucción y comienza a decodificarla, el resultado de la comparación ya está listo y el procesador puede decidir qué instrucción leer a continuación: la lwde arriba o la swde abajo. Esto evita la pérdida de tiempo y mantiene la tubería de procesamiento siempre llena.
Encontrar una instrucción para completar el espacio puede ser difícil. Los compiladores generalmente tienen un margen de análisis limitado y es posible que no encuentren una instrucción adecuada dentro de ese rango de código. Además, la instrucción no puede depender de ningún dato dentro de la bifurcación; si una addinstrucción toma un cálculo previo como una de sus entradas, esa entrada no puede formar parte del código en una bifurcación que podría ejecutarse. Decidir si esto es cierto puede ser muy complejo en presencia de renombramiento de registros , donde el procesador puede colocar datos en registros distintos a los especificados en el código sin que el compilador lo sepa.
Otro efecto secundario es que se requiere un manejo especial al administrar puntos de interrupción en instrucciones, así como al avanzar paso a paso durante la depuración dentro de la ranura de retardo de bifurcación. No se puede producir una interrupción durante una ranura de retardo de bifurcación y se pospone hasta después de dicha ranura. [ 5 ] [ 6 ] Está prohibido o desaconsejado colocar instrucciones de bifurcación en la ranura de retardo de bifurcación. [ 7 ] [ 8 ] [ 9 ]
El número ideal de ranuras de retardo de bifurcación en una implementación de pipeline específica viene determinado por el número de etapas del pipeline, la presencia de reenvío de registros , la etapa del pipeline donde se calculan las condiciones de bifurcación, el uso o no de un búfer de destino de bifurcación (BTB) y muchos otros factores. Los requisitos de compatibilidad de software exigen que una arquitectura no modifique el número de ranuras de retardo de una generación a otra. Esto implica que las implementaciones de hardware más recientes requieren hardware adicional para garantizar que se mantenga el comportamiento arquitectónico, aunque este ya no sea relevante.
Implementaciones
Las ranuras de retardo de rama se encuentran principalmente en arquitecturas DSP y arquitecturas RISC más antiguas. MIPS , PA-RISC (se puede especificar una rama retardada o no retardada), [ 10 ] ETRAX CRIS , SuperH (las instrucciones de rama incondicional tienen una ranura de retardo), [ 11 ] Am29000 , [ 12 ] Intel i860 (las instrucciones de rama incondicional tienen una ranura de retardo), [ 13 ] MC88000 (se puede especificar una rama retardada o no retardada), [ 14 ] y SPARC son arquitecturas RISC que tienen cada una una única ranura de retardo de rama; PowerPC , ARM , Alpha , V850 y RISC-V no tienen ninguna. Las arquitecturas DSP que tienen cada una una única ranura de retardo de rama incluyen μPD77230 [ 15 ] y el VS DSP . El SHARC DSP y MIPS-X utilizan una ranura de retardo de rama doble; [ 16 ] Dicho procesador ejecutará un par de instrucciones después de una instrucción de salto antes de que el salto surta efecto. Tanto el TMS320C3x [ 17 ] como el TMS320C4x [ 8 ] utilizan una ranura de retardo de salto triple. El TMS320C4x tiene saltos tanto sin retardo como con retardo. [ 8 ]
El siguiente ejemplo muestra saltos retardados en lenguaje ensamblador para el DSP SHARC, incluyendo un par después de la instrucción RTS. Los registros R0 a R9 se ponen a cero en orden numérico (el registro que se pone a cero después de R6 es R7, no R9). Ninguna instrucción se ejecuta más de una vez.
R0 = 0 ; CALL fn ( DB ); /* llama a una función, más abajo en la etiqueta "fn" */ R1 = 0 ; /* primer intervalo de retardo */ R2 = 0 ; /* segundo intervalo de retardo */ /***** discontinuidad aquí (la llamada surte efecto) *****/R6 = 0 ; /* la llamada/RTS regresa aquí, no en "R1 = 0" */ JUMP end ( DB ); R7 = 0 ; /* primera ranura de retardo */ R8 = 0 ; /* segunda ranura de retardo */ /***** discontinuidad aquí (el JUMP surte efecto) *****//* Las siguientes 4 instrucciones se llaman desde arriba, como la función "fn" */ fn : R3 = 0 ; RTS ( DB ); /* Regresar al llamador, pasando las ranuras de retardo del llamador */ R4 = 0 ; /* Primera ranura de retardo */ R5 = 0 ; /* Segunda ranura de retardo */ /***** Discontinuidad aquí (el RTS entra en efecto) *****/fin : R9 = 0 ;Ranura de retardo de carga
Una ranura de retardo de carga es una instrucción que se ejecuta inmediatamente después de una carga (de un registro desde la memoria), pero no recibe ni necesita esperar el resultado de la carga. Las ranuras de retardo de carga son muy poco comunes debido a que los retardos de carga son altamente impredecibles en el hardware moderno. Una carga puede satisfacerse desde la RAM o desde una caché, y puede verse ralentizada por la contención de recursos. Los retardos de carga se observaban generalmente en los primeros diseños de procesadores RISC. La arquitectura MIPS I (implementada en los microprocesadores R2000 y R3000 ) cuenta con una ranura de este tipo.
El siguiente ejemplo es código ensamblador MIPS I, que muestra tanto una ranura de retardo de carga como una ranura de retardo de bifurcación.
lw v0 , 4 ( v1 ) # carga la palabra desde la dirección v1+4 en v0 nop # ranura de retardo de carga desperdiciada jr v0 # salta a la dirección especificada por v0 nop # ranura de retardo de bifurcación desperdiciadaVéase también
Referencias
- ↑ A. Patterson, David; L. Hennessy, John (1990). Arquitectura de computadoras: Un enfoque cuantitativo . Morgan Kaufmann Publishers. pág. 275. ISBN 1-55860-069-8.
- ↑ "Página de ensamblador MSX" .
- ↑ "CMSC 411 Lección 19, Procesamiento en paralelo de datos" . Departamento de Ciencias de la Computación e Ingeniería Eléctrica de la Universidad de Maryland, Condado de Baltimore . Consultado el 22 de enero de 2020 .
- ↑ Cox, Russ (3 de enero de 2011). "El MOS 6502 y el mejor diseñador de maquetas del mundo" .
- ↑ "Procesador de señal avanzado μPD77230" (PDF) . págs. 38(3-39), 70(3-41) . Consultado el 17 de noviembre de 2023 .
- ↑ "Guía del usuario de TMS320C4x" (PDF) . pág. 75(3-15) . Consultado el 2 de diciembre de 2023 .
- ↑ "Procesador de señal avanzado μPD77230" (PDF) . pág. 191(4-76) . Consultado el 28 de octubre de 2023 .
- 1 2 3 "Guía del usuario de TMS320C4x" (PDF) . pág. 171(7-9) . Consultado el 29/10/2023 .
- ↑ "Manual del usuario del microprocesador RISC MC88100" (PDF) . pág. 88(3-33) . Consultado el 30/12/2023 .
- ↑ DeRosa, John A.; Levy, Henry M. "Una evaluación de las arquitecturas de ramificación" . pág. 1. Consultado el 27 de enero de 2024 .
- ↑ "Manual de hardware SH7020 y SH7021 Motor RISC SuperH™" . pág. 42,70 . Consultado el 17 de diciembre de 2023 .
- ↑ "Evaluación y programación de la familia RISC 29K, tercera edición – BORRADOR" (PDF) . pág. 54. Consultado el 20 de diciembre de 2023 .
- ↑ "Manual de referencia del programador del microprocesador i860™ de 64 bits" (PDF) . pág. 70(5-11) . Consultado el 21/12/2023 .
- ↑ "Manual del usuario del microprocesador RISC MC88100" (PDF) . pág. 81(3-26) . Consultado el 21/12/2023 .
- ↑ "Procesador de señal avanzado μPD77230" (PDF) . pág. 191(4-76) . Consultado el 5 de noviembre de 2023 .
- ↑ "Conjunto de instrucciones MIPS-X y manual del programador" (PDF) . pág. 18. Consultado el 3 de diciembre de 2023 .
- ↑ "El procesador de señal digital de punto flotante TMS320C30" (PDF) . ti.com. pág. 14. Consultado el 4 de noviembre de 2023 .
Enlaces externos
- DeRosa, JA; Levy, HM (1987). "Una evaluación de arquitecturas de ramificación §2 Ramificaciones retardadas" . Actas del 14.º simposio internacional anual sobre arquitectura de computadoras (ISCA '87) . Association for Computing Machinery. pp. 10–16 . doi : 10.1145/30350.30352 . ISBN 978-0-8186-0776-9. S2CID 1870852 .
- Prabhu, Gurpur M. "Esquemas de predicción de ramificaciones" . Tutorial de arquitectura de computadoras . Universidad Estatal de Iowa. Archivado del original el 7 de agosto de 2020.
- Procesamiento de instrucciones