Si los registros son tan increíblemente rápidos, ¿por qué no tenemos más?

88

En 32 bits, teníamos 8 registros de "propósito general". Con 64 bits, la cantidad se duplica, pero parece independiente del cambio de 64 bits en sí.
Ahora, si los registros son tan rápidos (sin acceso a la memoria), ¿por qué no hay más de ellos naturalmente? ¿No deberían los constructores de CPU trabajar con tantos registros como sea posible en la CPU? ¿Cuál es la restricción lógica de por qué solo tenemos la cantidad que tenemos?

Xeo
fuente
Las CPU y las GPU ocultan la latencia principalmente mediante cachés y subprocesos múltiples masivos, respectivamente. Entonces, las CPU tienen (o necesitan) pocos registros, mientras que las GPU tienen decenas de miles de registros. Consulte mi documento de encuesta sobre el archivo de registro de GPU que analiza todas estas compensaciones y factores.
user984260

Respuestas:

119

Hay muchas razones por las que no solo tiene una gran cantidad de registros:

  • Están muy vinculados a la mayoría de las etapas de la canalización. Para empezar, debe realizar un seguimiento de su vida útil y reenviar los resultados a las etapas anteriores. La complejidad se vuelve intratable muy rápidamente y la cantidad de cables (literalmente) involucrados crece al mismo ritmo. Es caro en área, lo que en última instancia significa que es caro en potencia, precio y rendimiento después de cierto punto.
  • Ocupa espacio de codificación de instrucciones. 16 registros ocupan 4 bits para origen y destino, y otros 4 si tiene instrucciones de 3 operandos (por ejemplo, ARM). Eso es una gran cantidad de espacio de codificación de conjuntos de instrucciones ocupado solo para especificar el registro. Esto eventualmente afecta la decodificación, el tamaño del código y nuevamente la complejidad.
  • Hay mejores formas de lograr el mismo resultado ...

En estos días realmente tenemos muchos registros, simplemente no están programados explícitamente. Tenemos "registro de cambio de nombre". Si bien solo accede a un conjunto pequeño (8-32 registros), en realidad están respaldados por un conjunto mucho más grande (por ejemplo, 64-256). Luego, la CPU rastrea la visibilidad de cada registro y los asigna al conjunto renombrado. Por ejemplo, puede cargar, modificar y luego almacenar en un registro muchas veces seguidas y hacer que cada una de estas operaciones se realice de forma independiente dependiendo de las fallas de caché, etc.

ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]

Los núcleos Cortex A9 registran el cambio de nombre, por lo que la primera carga a "r0" en realidad va a un registro virtual renombrado, llamémoslo "v0". La carga, el incremento y el almacenamiento ocurren en "v0". Mientras tanto, también realizamos una carga / modificación / almacenamiento en r0 nuevamente, pero se le cambiará el nombre a "v1" porque esta es una secuencia completamente independiente que usa r0. Digamos que la carga del puntero en "r4" se detuvo debido a un error de caché. Está bien, no necesitamos esperar a que "r0" esté listo. Debido a que se le cambió el nombre, podemos ejecutar la siguiente secuencia con "v1" (también asignada a r0), y quizás eso sea un éxito de caché y acabamos de tener una gran ganancia de rendimiento.

ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]

Creo que x86 tiene una cantidad gigantesca de registros renombrados en estos días (estadio de béisbol 256). Eso significaría tener 8 bits multiplicado por 2 para cada instrucción solo para decir cuál es el origen y el destino. Aumentaría enormemente la cantidad de cables necesarios a través del núcleo y su tamaño. Así que hay un punto óptimo alrededor de 16-32 registros con el que se han conformado la mayoría de los diseñadores, y para los diseños de CPU fuera de orden, el cambio de nombre de registros es la forma de mitigarlo.

Editar : La importancia de la ejecución fuera de orden y el cambio de nombre del registro en esto. Una vez que tiene OOO, el número de registros no importa tanto, porque son simplemente "etiquetas temporales" y se les cambia el nombre al conjunto de registros virtuales mucho más grande. No desea que el número sea demasiado pequeño, porque se vuelve difícil escribir secuencias de código pequeñas. Este es un problema para x86-32, porque los 8 registros limitados significan que muchos temporales terminan pasando por la pila y el núcleo necesita lógica adicional para reenviar las lecturas / escrituras a la memoria. Si no tiene OOO, por lo general se refiere a un núcleo pequeño, en cuyo caso un conjunto de registros grande es un beneficio de bajo costo / rendimiento.

Por lo tanto, existe un punto óptimo natural para el tamaño del banco de registros que alcanza un máximo de aproximadamente 32 registros diseñados para la mayoría de las clases de CPU. x86-32 tiene 8 registros y definitivamente es demasiado pequeño. ARM fue con 16 registros y es un buen compromiso. 32 registros son un poco demasiado, si acaso, terminas sin necesitar los últimos 10 más o menos.

Nada de esto afecta a los registros adicionales que obtiene para SSE y otros coprocesadores de coma flotante vectorial. Esos tienen sentido como un conjunto adicional porque se ejecutan independientemente del núcleo entero y no aumentan la complejidad de la CPU de manera exponencial.

John Ripley
fuente
12
Excelente respuesta: me gustaría agregar otra razón a la mezcla: cuantos más registros tenga uno, más tiempo se necesita para lanzarlos / sacarlos de la pila cuando se cambia de contexto. Definitivamente no es el problema principal, pero sí una consideración.
Will A
7
@WillUn buen punto. Sin embargo, las arquitecturas con muchos registros tienen formas de mitigar este costo. La ABI normalmente guardará llamadas para la mayoría de los registros, por lo que solo tendrá que guardar un conjunto básico. El cambio de contexto suele ser lo suficientemente caro como para que el guardado / restauración adicional no cueste mucho en comparación con el resto de la burocracia. SPARC en realidad trabaja en torno a esto haciendo que el banco de registros sea una "ventana" en un área de memoria, por lo que se escala con esto un poco (como si se agitara con la mano).
John Ripley
4
Considere mi mente asombrada por una respuesta tan completa que seguramente no esperaba. Además, gracias por esa explicación de por qué no necesitamos tantos registros con nombre, ¡eso es muy interesante! Realmente disfruté leyendo tu respuesta, porque estoy totalmente interesado en lo que sucede "bajo el capó". :) Voy a esperar un poco más antes de aceptar una respuesta, porque nunca se sabe, pero mi +1 es seguro.
Xeo
1
Independientemente de dónde radique la responsabilidad de guardar los registros, el tiempo que lleva es una sobrecarga administrativa. De acuerdo, el cambio de contexto puede no ser el caso más frecuente, pero las interrupciones sí lo son. Las rutinas codificadas a mano pueden economizar registros, pero si los controladores están escritos en C, es probable que la función declarada de interrupción guarde todos los registros, llame al isr y luego restaure todos los registros guardados. IA-32 tenía una ventaja de interrupción con sus 15-20 registros en comparación con 32 + algo de registros de arquitecturas RISC.
Olof Forshell
1
Excelente respuesta, pero no estaré de acuerdo con la comparación directa de registros "renombrados" con registros direccionables "reales". En x86-32, incluso con 256 registros internos, no puede usar más de 8 valores temporales almacenados en registros en un solo punto de ejecución. Básicamente, el cambio de nombre de registros es solo un subproducto curioso de OOE, nada más.
2012
12

Nos hacemos tienen más de Ellos

Debido a que casi todas las instrucciones deben seleccionar 1, 2 o 3 registros arquitectónicamente visibles, expandir el número de ellos aumentaría el tamaño del código en varios bits en cada instrucción y reduciría la densidad del código. También aumenta la cantidad de contexto que debe guardarse como estado del hilo y guardarse parcialmente en el registro de activación de una función . Estas operaciones ocurren con frecuencia. Los enclavamientos de tuberías deben verificar un cuadro de indicadores para cada registro y esto tiene una complejidad cuadrática de tiempo y espacio. Y quizás la razón más importante es simplemente la compatibilidad con el conjunto de instrucciones ya definido.

Pero resulta que, gracias al cambio de nombre de los registros , realmente tenemos muchos registros disponibles y ni siquiera necesitamos guardarlos. La CPU en realidad tiene muchos conjuntos de registros y automáticamente cambia entre ellos a medida que se ejecuta su código. Hace esto simplemente para obtener más registros.

Ejemplo:

load  r1, a  # x = a
store r1, x
load  r1, b  # y = b
store r1, y

En una arquitectura que solo tiene r0-r7, el siguiente código puede ser reescrito automáticamente por la CPU como algo como:

load  r1, a
store r1, x
load  r10, b
store r10, y

En este caso, r10 es un registro oculto que se sustituye temporalmente por r1. La CPU puede decir que el valor de r1 nunca se vuelve a utilizar después del primer almacenamiento. Esto permite retrasar la primera carga (incluso un acierto de caché en el chip suele tardar varios ciclos) sin requerir el retraso de la segunda carga o de la segunda tienda.

DigitalRoss
fuente
2

Agregan registros todo el tiempo, pero a menudo están vinculados a instrucciones de propósito especial (por ejemplo, SIMD, SSE2, etc.) o requieren compilarse en una arquitectura de CPU específica, lo que reduce la portabilidad. Las instrucciones existentes a menudo funcionan en registros específicos y no podrían aprovechar otros registros si estuvieran disponibles. Conjunto de instrucciones heredado y todo.

Seth Robertson
fuente
1

Para agregar un poco de información interesante aquí, notará que tener 8 registros del mismo tamaño permite que los códigos de operación mantengan la coherencia con la notación hexadecimal. Por ejemplo la instrucciónpush ax es el código de operación 0x50 en x86 y sube a 0x57 para el último registro di. Luego, la instrucción pop axcomienza en 0x58 y sube hasta 0x5F pop dipara completar la primera base-16. La consistencia hexadecimal se mantiene con 8 registros por tamaño.


fuente
2
En x86 / 64, los prefijos de instrucción REX amplían los índices de registro con más bits.
Alexey Frunze