Se i registri sono così incredibilmente veloci, perché non ne abbiamo altri?


89

A 32 bit, avevamo 8 registri "general purpose". Con 64 bit, l'importo raddoppia, ma sembra indipendente dalla modifica a 64 bit stessa.
Ora, se i registri sono così veloci (nessun accesso alla memoria), perché non ce ne sono di più naturalmente? I costruttori di CPU non dovrebbero lavorare il maggior numero possibile di registri nella CPU? Qual è la logica restrizione al motivo per cui abbiamo solo l'importo che abbiamo?


CPU e GPU nascondono la latenza principalmente dalle cache e rispettivamente dal multithreading massiccio. Quindi, le CPU hanno (o necessitano) pochi registri, mentre le GPU hanno decine di migliaia di registri. Vedi il mio documento di indagine sul file di registro della GPU che discute tutti questi compromessi e fattori.
user984260

Risposte:


120

Ci sono molte ragioni per cui non hai solo un numero enorme di registri:

  • Sono altamente collegati alla maggior parte delle fasi della pipeline. Per cominciare, è necessario monitorare la loro durata e riportare i risultati alle fasi precedenti. La complessità diventa intrattabile molto rapidamente e il numero di fili (letteralmente) coinvolti cresce alla stessa velocità. È costoso in area, il che alla fine significa che è costoso in termini di potenza, prezzo e prestazioni dopo un certo punto.
  • Occupa spazio per la codifica delle istruzioni. 16 registri occupano 4 bit per sorgente e destinazione e altri 4 se si hanno istruzioni a 3 operandi (es. ARM). È una quantità enorme di spazio di codifica del set di istruzioni occupato solo per specificare il registro. Ciò alla fine influisce sulla decodifica, sulla dimensione del codice e ancora sulla complessità.
  • Esistono modi migliori per ottenere lo stesso risultato ...

Oggigiorno abbiamo davvero molti registri, semplicemente non sono programmati esplicitamente. Abbiamo "rinominare i registri". Anche se si accede solo a un set piccolo (8-32 registri), in realtà sono supportati da un set molto più grande (ad esempio 64-256). La CPU quindi tiene traccia della visibilità di ogni registro e li assegna al set rinominato. Ad esempio, è possibile caricare, modificare, quindi memorizzare in un registro molte volte di seguito e fare in modo che ciascuna di queste operazioni venga effettivamente eseguita in modo indipendente a seconda dei mancati riscontri nella cache, ecc. In ARM:

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

I core Cortex A9 registrano la ridenominazione, quindi il primo caricamento su "r0" va effettivamente a un registro virtuale rinominato - chiamiamolo "v0". Il caricamento, l'incremento e l'archiviazione avvengono su "v0". Nel frattempo, eseguiamo di nuovo un caricamento / modifica / memorizzazione in r0, ma verrà rinominato in "v1" perché questa è una sequenza completamente indipendente che utilizza r0. Diciamo che il carico dal puntatore in "r4" si è bloccato a causa di un errore nella cache. Va bene - non è necessario attendere che "r0" sia pronto. Poiché è stato rinominato, possiamo eseguire la sequenza successiva con "v1" (anch'esso mappato su r0) - e forse è un successo nella cache e abbiamo appena ottenuto un enorme successo in termini di prestazioni.

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

Penso che x86 sia fino a un numero enorme di registri rinominati in questi giorni (ballpark 256). Ciò significherebbe avere 8 bit per 2 per ogni istruzione solo per dire qual è l'origine e la destinazione. Aumenterebbe enormemente il numero di fili necessari attraverso il nucleo e le sue dimensioni. Quindi c'è un punto debole intorno ai registri 16-32 che la maggior parte dei progettisti ha scelto, e per i progetti di CPU fuori ordine, la ridenominazione dei registri è il modo per mitigarlo.

Modifica : l'importanza dell'esecuzione fuori ordine e la ridenominazione del registro su questo. Una volta che hai OOO, il numero di registri non ha molta importanza, perché sono solo "tag temporanei" e vengono rinominati in un insieme di registri virtuali molto più grande. Non vuoi che il numero sia troppo piccolo, perché diventa difficile scrivere piccole sequenze di codice. Questo è un problema per x86-32, perché gli 8 registri limitati significano che molti temporanei finiscono per passare attraverso lo stack e il core ha bisogno di logica aggiuntiva per inoltrare letture / scritture in memoria. Se non hai OOO, di solito parli di un core piccolo, nel qual caso un set di registri di grandi dimensioni è uno scarso vantaggio in termini di costi / prestazioni.

Quindi c'è un punto debole naturale per la dimensione del banco di registro che arriva al massimo a circa 32 registri progettati per la maggior parte delle classi di CPU. x86-32 ha 8 registri ed è decisamente troppo piccolo. ARM ha 16 registri ed è un buon compromesso. 32 registri sono un po 'troppi se non altro: finisci per non aver bisogno degli ultimi 10 o giù di lì.

Niente di tutto questo tocca i registri extra che ottieni per SSE e altri coprocessori a virgola mobile vettoriale. Quelli hanno senso come set aggiuntivo perché funzionano indipendentemente dal core intero e non aumentano la complessità della CPU in modo esponenziale.


12
Risposta eccellente - Vorrei aggiungere un altro motivo al mix: più registri si hanno, più tempo ci vuole per lanciarli / estrarli dallo stack quando si cambia contesto. Sicuramente non è il problema principale, ma una considerazione.
Will un

7
@ WillA buon punto. Tuttavia, le architetture con molti registri hanno modi per mitigare questo costo. L'ABI di solito prevede il salvataggio del chiamato della maggior parte dei registri, quindi devi solo salvare un set di base. Il cambio di contesto è solitamente abbastanza costoso che il salvataggio / ripristino extra non costa molto rispetto a tutte le altre burocrazie. SPARC in realtà aggira questo problema rendendo il banco di registri una "finestra" su un'area di memoria, quindi scala con questo un po '(una specie di agitazione manuale).
John Ripley,

4
Considera la mia mente sbalordita da una risposta così esauriente che di sicuro non mi aspettavo. Inoltre, grazie per quella spiegazione sul motivo per cui non abbiamo davvero bisogno di così tanti registri con nome, è molto interessante! Mi è piaciuto molto leggere la tua risposta, perché sono totalmente interessato a quello che succede "sotto il cofano". :) Aspetterò ancora un po 'prima di accettare una risposta, perché non si sa mai, ma il mio +1 è sicuro.
Xeo

1
indipendentemente da dove risieda la responsabilità per il salvataggio dei registri, il tempo necessario è un sovraccarico amministrativo. OK, quindi il cambio di contesto potrebbe non essere il caso più frequente, ma le interruzioni lo sono. Le routine codificate a mano possono risparmiare sui registri, ma se i driver sono scritti in C è probabile che la funzione dichiarata da interrupt salverà ogni singolo registro, chiamerà isr e quindi ripristinerà tutti i registri salvati. IA-32 aveva un vantaggio di interruzione con i suoi 15-20 reg rispetto ai 32 + qualcosa delle architetture RISC.
Olof Forshell

1
Ottima risposta, ma non sono d'accordo con il confronto diretto dei registri "rinominati" con quelli "reali" indirizzabili. Su x86-32, anche con 256 registri interni non è possibile utilizzare più di 8 valori temporanei memorizzati nei registri in un singolo punto di esecuzione. Fondamentalmente, la ridenominazione dei registri è solo un curioso sottoprodotto di OOE, niente di più.
noop

12

Noi facciamo Hanno più di loro

Poiché quasi tutte le istruzioni devono selezionare 1, 2 o 3 registri architettonicamente visibili, espandere il numero di essi aumenterebbe la dimensione del codice di diversi bit su ciascuna istruzione e quindi ridurrebbe la densità del codice. Aumenta anche la quantità di contesto che deve essere salvato come stato del thread e parzialmente salvato nel record di attivazione di una funzione . Queste operazioni si verificano frequentemente. Gli interblocchi della pipeline devono controllare un tabellone per ogni registro e questo ha una complessità temporale e spaziale quadratica. E forse il motivo principale è semplicemente la compatibilità con il set di istruzioni già definito.

Ma si scopre che, grazie alla ridenominazione dei registri , abbiamo davvero molti registri disponibili e non abbiamo nemmeno bisogno di salvarli. La CPU ha in realtà molti set di registri e passa automaticamente da uno all'altro durante l'esecuzione del codice. Lo fa esclusivamente per ottenere più registri.

Esempio:

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

In un'architettura che ha solo r0-r7, il seguente codice può essere riscritto automaticamente dalla CPU come qualcosa di simile:

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

In questo caso r10 è un registro nascosto che viene temporaneamente sostituito da r1. La CPU può dire che il valore di r1 non viene mai più utilizzato dopo la prima memorizzazione. Ciò consente di ritardare il primo caricamento (anche un accesso alla cache su chip di solito richiede diversi cicli) senza richiedere il ritardo del secondo caricamento o del secondo archivio.


2

Aggiungono registri tutto il tempo, ma sono spesso legati a istruzioni per scopi speciali (ad esempio SIMD, SSE2, ecc.) O richiedono la compilazione a una specifica architettura della CPU, che riduce la portabilità. Le istruzioni esistenti spesso funzionano su registri specifici e non potrebbero trarre vantaggio da altri registri se fossero disponibili. Set di istruzioni legacy e tutto il resto.


1

Per aggiungere alcune informazioni interessanti qui noterai che avere 8 registri della stessa dimensione consente agli opcode di mantenere la coerenza con la notazione esadecimale. Ad esempio, l'istruzione push axè il codice operativo 0x50 su x86 e sale a 0x57 per l'ultimo registro di. Quindi l'istruzione pop axinizia da 0x58 e sale a 0x5F pop diper completare la prima base 16. La consistenza esadecimale viene mantenuta con 8 registri per dimensione.


2
Su x86 / 64 i prefissi delle istruzioni REX estendono gli indici dei registri con più bit.
Alexey Frunze
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.