Este artículo tiene varios problemas. Ayúdenos a mejorarlo o a discutir estos problemas en la página de discusión . ( Aprenda cómo y cuándo eliminar estos mensajes )
|
En la arquitectura informática , el cambio de nombre de registros es una técnica que abstrae los registros lógicos de los registros físicos. Cada registro lógico tiene un conjunto de registros físicos asociados. Cuando una instrucción en lenguaje de máquina hace referencia a un registro lógico en particular, el procesador transpone este nombre a un registro físico específico sobre la marcha. Los registros físicos son opacos y no se puede hacer referencia a ellos directamente, sino solo a través de los nombres canónicos.
Esta técnica se utiliza para eliminar las dependencias de datos falsas que surgen de la reutilización de registros por parte de instrucciones sucesivas que no tienen ninguna dependencia de datos real entre ellas. La eliminación de estas dependencias de datos falsas revela un mayor paralelismo a nivel de instrucción en un flujo de instrucciones, que puede aprovecharse mediante diversas técnicas complementarias, como la ejecución superescalar y fuera de orden, para lograr un mejor rendimiento .
Los programas se componen de instrucciones que operan sobre valores. Las instrucciones deben nombrar estos valores para distinguirlos entre sí. Una instrucción típica podría decir: sumar y y poner el resultado en . En esta instrucción, , y son los nombres de las ubicaciones de almacenamiento.
Es común que los valores que se manipulan se utilicen varias veces seguidas. Las máquinas de registros aprovechan esta situación introduciendo una serie de registros de procesador , que son ubicaciones de memoria de alta velocidad que almacenan estos valores. Las instrucciones que utilizan los registros como valores intermedios se ejecutarán mucho más rápido que las que acceden a la memoria principal . Este aumento del rendimiento es un elemento de diseño clave del diseño del procesador RISC , que utiliza registros para todas sus instrucciones matemáticas y lógicas primarias. La colección de registros en un diseño particular se conoce como su archivo de registros .
Los registros individuales del archivo se referencian por número en el código de máquina . Codificar un número en el código de máquina requiere varios bits. Por ejemplo, en el Zilog Z80 había ocho registros de propósito general en el archivo. Para seleccionar uno de los ocho valores se requieren tres bits, ya que 2 3 = 8. Más registros en el archivo darán como resultado un mejor rendimiento, ya que se pueden mantener más valores temporales en el archivo y así evitar las costosas operaciones de guardar o cargar desde la memoria. Generalmente, los procesadores más modernos y aquellos con palabras de instrucción más grandes usarán más registros cuando sea posible. Por ejemplo, la arquitectura del conjunto de instrucciones IA-32 tiene 8 registros de propósito general, x86-64 tiene 16, muchos RISC tienen 32 y IA-64 tiene 128.
Las ventajas de un archivo de registros más grande se ven contrarrestadas por la necesidad de utilizar más bits para codificar el número de registro. Por ejemplo, en un sistema que utiliza instrucciones de 32 bits, es posible que desee tener tres registros, de modo que pueda realizar operaciones del tipo = . Si el archivo de registros contiene 32 entradas, cada una de las referencias requerirá 5 bits, y el conjunto de tres registros ocupará 15 bits, dejando 17 para codificar la operación y otra información. Ampliar el archivo de registros a 64 entradas requeriría 6 bits, un total de 18 bits. Si bien esto puede dar como resultado un rendimiento más rápido, también significa que quedan menos bits para codificar la instrucción. Esto conduce a un esfuerzo por equilibrar el tamaño del archivo con la cantidad de instrucciones posibles.
Los primeros ordenadores solían trabajar en sincronía con su memoria principal, lo que reducía las ventajas de los archivos de registro de gran tamaño. Una nota de diseño habitual en el mercado de miniordenadores de la década de 1960 era que los registros se implementaran físicamente en la memoria principal, en cuyo caso la ventaja de rendimiento era simplemente que la instrucción podía hacer referencia directamente a la ubicación en lugar de tener que utilizar un segundo byte o dos para especificar una dirección de memoria completa. Esto hacía que las instrucciones fueran más pequeñas y, por tanto, más rápidas de leer. Este tipo de diseño, que maximizaba el rendimiento ajustando cuidadosamente el conjunto de instrucciones para un tamaño mínimo, era común hasta la década de 1980. Un ejemplo de este enfoque es el MOS 6502 , que tenía un solo registro, en cuyo caso se lo denomina acumulador , y un modo de direccionamiento especial de "página cero" que trataba los primeros 256 bytes de memoria como si fueran registros. Colocar el código y los datos en la página cero significaba que la instrucción tenía solo dos bytes de longitud en lugar de tres, lo que mejoraba enormemente el rendimiento al evitar lecturas.
La introducción generalizada de la memoria RAM dinámica en la década de 1970 cambió este enfoque. Con el tiempo, el rendimiento de las unidades centrales de procesamiento (CPU) aumentó en relación con la memoria a la que estaban conectadas, por lo que ya no era razonable utilizar la memoria principal como registros. Esto llevó a que los archivos de registros internos de la CPU fueran cada vez más grandes para evitar hacer referencia a la memoria siempre que fuera posible. Sin embargo, en la práctica no es posible evitar por completo el acceso a la memoria y, a medida que aumentaba la diferencia de velocidad, cada acceso de este tipo se volvía cada vez más costoso en términos de la cantidad de instrucciones que podrían realizarse si el valor estuviera en un registro.
Diferentes instrucciones pueden requerir distintas cantidades de tiempo; por ejemplo, un procesador puede ejecutar cientos de instrucciones registro a registro mientras se realiza una única carga desde la memoria principal. Un avance clave para mejorar el rendimiento es permitir que esas instrucciones rápidas se ejecuten mientras las demás esperan datos. Esto significa que las instrucciones ya no se completan en el orden en que se especifican en el código de la máquina, sino que se ejecutan fuera de orden .
Considere este fragmento de código ejecutándose en una CPU fuera de servicio:
r1 ≔ m [ 1024 ] ;lee el valor en la posición de memoria 1024 r1 ≔ r1 + 2 ;suma dos al valor m [ 1032 ] ≔ r1 ;guarda el resultado en la ubicación 1032 r1 ≔ m [ 2048 ] ;valor de lectura en 2048 r1 ≔ r1 + 4 ;suma 4 m [ 2056 ] ≔ r1 ;guárdelo en 2056
Las instrucciones en las últimas tres líneas son independientes de las primeras tres instrucciones, pero el procesador no puede terminar hasta que se complete la anterior , ya que hacerlo sumaría cuatro al valor de 1024, no 2048.r1 ≔ m[2048]
m[1032] ≔ r1
Si hay otro registro disponible, esta restricción se puede eliminar eligiendo registros diferentes para las primeras tres y las segundas tres instrucciones:
r1 ≔ m [ 1024 ] r1 ≔ r1 + 2 m [ 1032 ] ≔ r1 r2 ≔ m [ 2048 ] r2 ≔ r2 + 4 m [ 2056 ] ≔ r2
Ahora las últimas tres instrucciones se pueden ejecutar en paralelo con las primeras tres. El programa se ejecutará más rápido que antes al eliminar la dependencia de datos causada por el uso innecesario del mismo registro en ambas secuencias. Un compilador puede detectar secuencias de instrucciones independientes y, si hay registros disponibles para su uso, elegir registros diferentes durante la asignación de registros en el proceso de generación de código .
Sin embargo, para acelerar el código generado por compiladores que no realizan esa optimización, o código para el cual no había suficientes registros para realizar esa optimización, muchas CPU de alto rendimiento proporcionan un archivo de registros con más registros que los especificados en el conjunto de instrucciones y, en hardware, renombran las referencias en los registros definidos por el conjunto de instrucciones para hacer referencia a registros en el archivo de registros, de modo que la secuencia de instrucciones original, usando solo r1, se comporta como si fuera:
rA ≔ m [ 1024 ] rA ≔ rA + 2 m [ 1032 ] ≔ rA rB ≔ m [ 2048 ] rB ≔ rB + 4 m [ 2056 ] ≔ rB
con el registro r1 "renombrado" como registro interno rA para las primeras tres instrucciones y como registro interno rB para las segundas tres instrucciones. Esto elimina la falsa dependencia de datos, lo que permite que las primeras tres instrucciones se ejecuten en paralelo con las segundas tres instrucciones.
Cuando más de una instrucción hace referencia a una ubicación particular como operando, ya sea leyéndola (como entrada) o escribiéndola (como salida), ejecutar esas instrucciones en un orden diferente del orden del programa original puede generar tres tipos de riesgos de datos:
En lugar de retrasar la escritura hasta que se completen todas las lecturas, se pueden mantener dos copias de la ubicación, el valor anterior y el valor nuevo. Las lecturas que preceden, en el orden del programa, a la escritura del nuevo valor se pueden proporcionar con el valor anterior, incluso mientras que otras lecturas que siguen a la escritura se proporcionan con el valor nuevo. La falsa dependencia se rompe y se crean oportunidades adicionales para la ejecución fuera de orden. Cuando se han satisfecho todas las lecturas que necesitan el valor anterior, se puede descartar. Este es el concepto esencial detrás del cambio de nombre de registros.
Todo lo que se lee y se escribe se puede renombrar. Si bien los registros de propósito general y de punto flotante son los más discutidos, los registros de estado y de indicadores o incluso los bits de estado individuales también se renombran comúnmente.
Las ubicaciones de memoria también se pueden renombrar, aunque no se hace tan comúnmente como en el caso del cambio de nombre de registros. El búfer de almacenamiento controlado del procesador Transmeta Crusoe es una forma de cambio de nombre de memoria.
Si los programas se abstuvieran de reutilizar registros inmediatamente, no habría necesidad de renombrarlos. Algunos conjuntos de instrucciones (por ejemplo, IA-64 ) especifican una gran cantidad de registros específicamente por este motivo. Sin embargo, este enfoque tiene limitaciones:
Los aumentos en el tamaño del código son importantes porque cuando el código del programa es más grande, el caché de instrucciones falla con mayor frecuencia y el procesador se detiene esperando nuevas instrucciones.
Los programas en lenguaje de máquina especifican operaciones de lectura y escritura en un conjunto limitado de registros especificados por la arquitectura del conjunto de instrucciones (ISA). Por ejemplo, la ISA Alpha especifica 32 registros enteros, cada uno de 64 bits de ancho, y 32 registros de punto flotante, cada uno de 64 bits de ancho. Estos son los registros de la arquitectura . Los programas escritos para procesadores que ejecutan el conjunto de instrucciones Alpha especificarán operaciones de lectura y escritura en esos 64 registros. Si un programador detiene el programa en un depurador, puede observar el contenido de estos 64 registros (y algunos registros de estado) para determinar el progreso de la máquina.
Un procesador en particular que implementa esta ISA, el Alpha 21264 , tiene 80 registros físicos de números enteros y 72 de punto flotante . En un chip Alpha 21264 hay 80 ubicaciones físicamente separadas que pueden almacenar los resultados de operaciones de números enteros y 72 ubicaciones que pueden almacenar los resultados de operaciones de punto flotante (de hecho, hay incluso más ubicaciones que esas, pero esas ubicaciones adicionales no son pertinentes para la operación de cambio de nombre de registros).
El siguiente texto describe dos estilos de cambio de nombre de registros, que se distinguen por el circuito que mantiene los datos listos para una unidad de ejecución.
En todos los esquemas de cambio de nombre, la máquina convierte los registros arquitectónicos a los que se hace referencia en el flujo de instrucciones en etiquetas. Si bien los registros arquitectónicos pueden especificarse con 3 a 5 bits, las etiquetas suelen ser un número de 6 a 8 bits. El archivo de cambio de nombre debe tener un puerto de lectura para cada entrada de cada instrucción renombrada en cada ciclo, y un puerto de escritura para cada salida de cada instrucción renombrada en cada ciclo. Debido a que el tamaño de un archivo de registros generalmente crece con el cuadrado del número de puertos, el archivo de cambio de nombre suele ser físicamente grande y consume una cantidad significativa de energía.
En el estilo de archivo de registro indexado por etiquetas , hay un archivo de registro grande para valores de datos, que contiene un registro para cada etiqueta. Por ejemplo, si la máquina tiene 80 registros físicos, entonces utilizaría etiquetas de 7 bits. 48 de los posibles valores de etiqueta en este caso no se utilizan. En este estilo, cuando se emite una instrucción a una unidad de ejecución, las etiquetas de los registros de origen se envían al archivo de registro físico, donde se leen los valores correspondientes a esas etiquetas y se envían a la unidad de ejecución.
En el estilo de estación de reserva , hay muchos archivos de registro asociativos pequeños, generalmente uno en las entradas de cada unidad de ejecución. Cada operando de cada instrucción en una cola de emisión tiene un lugar para un valor en uno de estos archivos de registro. En este estilo, cuando se emite una instrucción a una unidad de ejecución, las entradas del archivo de registro correspondientes a la entrada de la cola de emisión se leen y se reenvían a la unidad de ejecución.
Este es el estilo de cambio de nombre utilizado en el MIPS R10000 , el Alpha 21264 y en la sección FP del AMD Athlon .
En la etapa de cambio de nombre, cada registro arquitectónico referenciado (para lectura o escritura) se busca en un archivo de reasignación indexado arquitectónicamente . Este archivo devuelve una etiqueta y un bit de listo. La etiqueta no está lista si hay una instrucción en cola que escribirá en ella y que aún no se ha ejecutado. Para los operandos de lectura, esta etiqueta reemplaza al registro arquitectónico en la instrucción. Para cada escritura de registro, se extrae una nueva etiqueta de un FIFO de etiquetas libre y se escribe una nueva asignación en el archivo de reasignación, de modo que las futuras instrucciones que lean el registro arquitectónico hagan referencia a esta nueva etiqueta. La etiqueta se marca como no lista, porque la instrucción aún no se ha ejecutado. El registro físico anterior asignado para ese registro arquitectónico se guarda con la instrucción en el búfer de reordenación , que es un FIFO que mantiene las instrucciones en orden de programa entre las etapas de decodificación y graduación.
Las instrucciones se colocan entonces en varias colas de emisión . A medida que se ejecutan las instrucciones, se transmiten las etiquetas de sus resultados y las colas de emisión hacen coincidir estas etiquetas con las etiquetas de sus operandos de origen no listos. Una coincidencia significa que el operando está listo. El archivo de reasignación también hace coincidir estas etiquetas, de modo que puede marcar los registros físicos correspondientes como listos. Cuando todos los operandos de una instrucción en una cola de emisión están listos, esa instrucción está lista para emitirse. Las colas de emisión seleccionan instrucciones listas para enviar a las diversas unidades funcionales en cada ciclo. Las instrucciones no listas permanecen en las colas de emisión. Esta eliminación desordenada de instrucciones de las colas de emisión puede hacer que sean grandes y consuman mucha energía.
Las instrucciones emitidas se leen desde un archivo de registro físico indexado por etiquetas (omitiendo los operandos recién transmitidos) y luego se ejecutan. Los resultados de la ejecución se escriben en el archivo de registro físico indexado por etiquetas y se transmiten a la red de omisión que precede a cada unidad funcional. La graduación coloca la etiqueta anterior del registro arquitectónico escrito en la cola libre para que pueda reutilizarse para una instrucción recién decodificada.
Una excepción o una predicción errónea de una bifurcación hace que el archivo de reasignación vuelva al estado de reasignación de la última instrucción válida mediante una combinación de instantáneas de estado y un ciclo a través de las etiquetas anteriores en la cola de pregraduación en orden. Dado que este mecanismo es necesario y que puede recuperar cualquier estado de reasignación (no solo el estado anterior a la instrucción que se está graduando actualmente), las predicciones erróneas de bifurcación se pueden manejar antes de que la bifurcación alcance la graduación, lo que podría ocultar la latencia de predicción errónea de bifurcación.
Este es el estilo utilizado en la sección de enteros de los diseños AMD K7 y K8.
En la etapa de cambio de nombre, cada registro arquitectónico referenciado para lecturas se busca tanto en el archivo futuro indexado arquitectónicamente como en el archivo de cambio de nombre. La lectura del archivo futuro proporciona el valor de ese registro, si aún no hay instrucciones pendientes para escribir en él (es decir, está listo). Cuando la instrucción se coloca en una cola de emisión, los valores leídos del archivo futuro se escriben en las entradas correspondientes en las estaciones de reserva. Las escrituras de registros en la instrucción hacen que se escriba una nueva etiqueta no lista en el archivo de cambio de nombre. El número de etiqueta generalmente se asigna en serie en el orden de instrucción; no es necesario un FIFO de etiqueta libre.
Al igual que con el esquema indexado por etiquetas, las colas de emisión esperan a que los operandos que no están listos vean las transmisiones de etiquetas coincidentes. A diferencia del esquema indexado por etiquetas, las etiquetas coincidentes hacen que el valor de transmisión correspondiente se escriba en la estación de reserva de la entrada de la cola de emisión.
Las instrucciones emitidas leen sus argumentos desde la estación de reserva, pasan por alto los operandos recién emitidos y luego se ejecutan. Como se mencionó anteriormente, los archivos de registro de la estación de reserva suelen ser pequeños, con quizás ocho entradas.
Los resultados de la ejecución se escriben en el búfer de reordenamiento , en las estaciones de reserva (si la entrada de la cola de emisión tiene una etiqueta coincidente) y en el archivo futuro si esta es la última instrucción destinada a ese registro arquitectónico (en cuyo caso el registro se marca como listo).
Graduation copia el valor del búfer de reordenamiento en el archivo de registro arquitectónico. El único uso del archivo de registro arquitectónico es recuperarse de excepciones y predicciones erróneas de bifurcaciones.
Las excepciones y los errores de predicción de bifurcaciones, reconocidos en la graduación, hacen que el archivo de arquitectura se copie en el archivo futuro y que todos los registros se marquen como listos en el archivo de cambio de nombre. Normalmente, no hay forma de reconstruir el estado del archivo futuro para alguna instrucción intermedia entre la decodificación y la graduación, por lo que normalmente no hay forma de realizar una recuperación temprana de los errores de predicción de bifurcaciones.
En ambos esquemas, las instrucciones se insertan en orden en las colas de emisión, pero se eliminan desordenadas. Si las colas no colapsan los espacios vacíos, tendrán muchas entradas sin usar o requerirán algún tipo de codificación de prioridad variable para cuando haya varias instrucciones listas para ser enviadas simultáneamente. Las colas que colapsan los espacios vacíos tienen una codificación de prioridad más simple, pero requieren circuitos simples pero grandes para hacer avanzar las instrucciones a través de la cola.
Las estaciones de reserva tienen una mejor latencia desde el cambio de nombre hasta la ejecución, porque la etapa de cambio de nombre encuentra los valores de registro directamente, en lugar de buscar el número de registro físico y luego usarlo para encontrar el valor. Esta latencia se muestra como un componente de la latencia de predicción errónea de la bifurcación.
Las estaciones de reserva también tienen una mejor latencia desde la emisión de la instrucción hasta su ejecución, porque cada archivo de registro local es más pequeño que el archivo central grande del esquema indexado por etiquetas. La generación de etiquetas y el procesamiento de excepciones también son más simples en el esquema de estación de reserva, como se analiza a continuación.
Los archivos de registro físicos utilizados por las estaciones de reserva suelen colapsar las entradas no utilizadas en paralelo con la cola de emisión a la que sirven, lo que hace que estos archivos de registro sean más grandes en conjunto, consuman más energía y sean más complicados que los archivos de registro más simples utilizados en un esquema de indexación de etiquetas. Peor aún, cada entrada en cada estación de reserva puede ser escrita por cada bus de resultados, de modo que una máquina de estación de reserva con, por ejemplo, 8 entradas de cola de emisión por unidad funcional tendrá típicamente 9 veces más redes de derivación que una máquina equivalente de indexación de etiquetas. En consecuencia, el reenvío de resultados consume mucha más energía y área que en un diseño de indexación de etiquetas.
Además, el esquema de estación de reserva tiene cuatro lugares (Archivo futuro, Estación de reserva, Buffer de reordenamiento y Archivo arquitectónico) donde se puede almacenar un valor de resultado, mientras que el esquema indexado por etiquetas tiene solo uno (el archivo de registro físico). Debido a que los resultados de las unidades funcionales, transmitidos a todas estas ubicaciones de almacenamiento, deben alcanzar una cantidad mucho mayor de ubicaciones en la máquina que en el esquema indexado por etiquetas, esta función consume más energía, área y tiempo. Aún así, en máquinas equipadas con esquemas de predicción de bifurcaciones muy precisos y si las latencias de ejecución son una preocupación importante, las estaciones de reserva pueden funcionar notablemente bien.
El IBM System/360 Modelo 91 fue una de las primeras máquinas que admitía la ejecución fuera de orden de instrucciones; utilizaba el algoritmo Tomasulo , que utiliza cambio de nombre de registros.
El POWER1 de 1990 es el primer microprocesador que utilizó el cambio de nombre de registros y la ejecución fuera de orden. Este procesador implementó el cambio de nombre de registros solo para cargas de punto flotante. El POWER1 tenía solo una FPU, por lo que no era necesario utilizar el cambio de nombre para instrucciones de punto flotante que no fueran operaciones de memoria. El POWER2 tenía varias FPU, por lo que se utilizó el cambio de nombre para todas las instrucciones de punto flotante. [1]
El diseño original del R10000 no tenía colas de emisión colapsables ni codificación de prioridad variable y, como resultado, sufría problemas de inanición: la instrucción más antigua de la cola a veces no se emitía hasta que ambas decodificaciones de instrucciones se detenían por completo por falta de registros de cambio de nombre y se habían emitido todas las demás instrucciones. Las revisiones posteriores del diseño, a partir del R12000, utilizaron un codificador de prioridad parcialmente variable para mitigar este problema.
Las primeras máquinas fuera de servicio no separaban las funciones de cambio de nombre y almacenamiento ROB/PRF. De hecho, algunas de las primeras, como RUU de Sohi o DCAF de Metaflow, combinaban programación, cambio de nombre y almacenamiento en la misma estructura.
La mayoría de las máquinas modernas renombran mediante la indexación de la RAM en una tabla de mapas con el número de registro lógico. Por ejemplo, P6 lo hizo; los archivos futuros lo harán y tendrán almacenamiento de datos en la misma estructura.
Sin embargo, las máquinas anteriores utilizaban memoria direccionable por contenido (CAM) en el renombrador. Por ejemplo, la HPSM RAT, o tabla de alias de registro, utilizaba esencialmente una CAM en el número de registro lógico en combinación con diferentes versiones del registro.
En muchos sentidos, la historia de la microarquitectura desordenada ha sido la forma en que estos CAM se han ido eliminando progresivamente. Los CAM pequeños son útiles; los CAM grandes son poco prácticos. [ cita requerida ]
La microarquitectura P6 fue la primera microarquitectura de Intel en implementar tanto la ejecución fuera de orden como el cambio de nombre de registros. La microarquitectura P6 se utilizó en los microprocesadores Pentium Pro, Pentium II, Pentium III, Pentium M, Core y Core 2. El Cyrix M1 , lanzado el 2 de octubre de 1995, [2] fue el primer procesador x86 en utilizar el cambio de nombre de registros y la ejecución fuera de orden. Otros procesadores x86 (como NexGen Nx686 y AMD K5 ) lanzados en 1996 también presentaban cambio de nombre de registros y ejecución fuera de orden de operaciones μ RISC (en lugar de instrucciones x86 nativas). [3] [4]