Mi sono sempre chiesto perché i processori si sono fermati a 32 registri. È di gran lunga il pezzo più veloce della macchina, perché non creare processori più grandi con più registri? Non significherebbe meno andare nella RAM?
Mi sono sempre chiesto perché i processori si sono fermati a 32 registri. È di gran lunga il pezzo più veloce della macchina, perché non creare processori più grandi con più registri? Non significherebbe meno andare nella RAM?
Risposte:
Innanzitutto, non tutte le architetture di processori si sono fermate a 32 registri. Quasi tutte le architetture RISC che hanno 32 registri esposti nel set di istruzioni in realtà hanno 32 registri interi e altri 32 registri a virgola mobile (quindi 64). (Il virgola mobile "add" utilizza registri diversi rispetto all'intero "add".) L'architettura SPARC ha finestre di registro. Su SPARC puoi accedere solo a 32 registri interi alla volta, ma i registri si comportano come uno stack e puoi spingere e pop i nuovi registri 16 alla volta. L'architettura Itanium di HP / Intel aveva 128 interi e 128 registri a virgola mobile esposti nel set di istruzioni. Le moderne GPU di NVidia, AMD, Intel, ARM e Imagination Technologies, espongono tutti un numero enorme di registri nei loro file di registro. (So che questo vale per le architetture NVidia e Intel, non ho molta familiarità con i set di istruzioni AMD, ARM e Imagination, ma penso che anche i file di registro siano grandi lì.)
In secondo luogo, la maggior parte dei microprocessori moderni implementa la ridenominazione dei registri per eliminare la serializzazione non necessaria causata dalla necessità di riutilizzare le risorse, quindi i file dei registri fisici sottostanti possono essere più grandi (96, 128 o 192 registri su alcune macchine.) Questo (e la programmazione dinamica) elimina alcuni dei è necessario che il compilatore generi così tanti nomi di registro univoci, pur fornendo allo scheduler un file di registro più grande.
Vi sono due motivi per cui potrebbe essere difficile aumentare ulteriormente il numero di registri esposti nel set di istruzioni. Innanzitutto, devi essere in grado di specificare gli identificatori di registro in ciascuna istruzione. 32 registri richiedono un identificatore di registro a 5 bit, quindi le istruzioni a 3 indirizzi (comuni nelle architetture RISC) impiegano 15 dei 32 bit di istruzione solo per specificare i registri. Se lo aumentassi a 6 o 7 bit, allora avresti meno spazio per specificare i codici operativi e le costanti. Le GPU e Itanium hanno istruzioni molto più grandi. Le istruzioni più grandi hanno un costo: è necessario utilizzare più memoria delle istruzioni, quindi il comportamento della cache delle istruzioni è meno ideale.
Il secondo motivo è il tempo di accesso. Più grande è la memoria, più lenta è l'accesso ai dati da essa. (Solo in termini di fisica di base: i dati sono memorizzati in uno spazio bidimensionale, quindi se si memorizzano bit, la distanza media da un bit specifico è .) Un file di registro è solo un piccola memoria multi-portata e uno dei vincoli per ingrandirla è che alla fine dovresti iniziare a rallentare la macchina per contenere il file di registro più grande. Di solito, in termini di prestazioni totali, questa è una perdita.
Solo altri due motivi per limitare il numero di registri:
Un sacco di codice ha molti accessi alla memoria (il 30% è una cifra tipica). Di conseguenza, in genere circa 2/3 sono accessi in lettura e 1/3 sono accessi in scrittura. Ciò non è dovuto all'esaurimento dei registri quanto all'accesso alle matrici, all'accesso alle variabili dei membri dell'oggetto, ecc.
Questo deve essere fatto in memoria (o cache di dati) a causa di come viene creato C / C ++ (tutto ciò a cui è possibile ottenere un puntatore deve avere un indirizzo a cui deve essere potenzialmente memorizzato in memoria). Se il compilatore può indovinare che non scriverai su variabili volenti o nolenti usando trucchi puntatori indiretti pazzi, li metterà nei registri e questo funziona alla grande per le variabili di funzione ma non per quelle accessibili a livello globale (in genere, tutto ciò che viene fuori da malloc ()) perché è sostanzialmente impossibile indovinare come cambierà lo stato globale.
Per questo motivo, non è comune che il compilatore sarà in grado di fare comunque qualcosa con più di circa 16 registri di utilizzo generale. Ecco perché tutti gli architetti popolari ne hanno così tanti (ARM ne ha 16).
MIPS e altri RISC tendono ad avere 32 perché non è molto difficile avere così tanti registri - il costo è abbastanza basso, quindi è un po 'un "perché no?". Più di 32 sono per lo più inutili e hanno il rovescio della medaglia di rendere più lungo l'accesso al file di registro (ogni raddoppio del numero di registri potenzialmente aggiunge un ulteriore livello di multiplexer che aggiunge un po 'più di ritardo ...). Inoltre, rende le istruzioni leggermente più lunghe in media, il che significa che quando si esegue il tipo di programmi che dipendono dalla larghezza di banda della memoria delle istruzioni, i registri extra in realtà ti rallentano!
Se la tua cpu è in ordine e non rinomina i registri e stai provando a fare molte operazioni per ciclo (più di 3), in teoria hai bisogno di più registri man mano che aumenta il numero di operazioni per ciclo. Questo è il motivo per cui Itanium ha così tanti registri! Ma in pratica, a parte il codice a virgola mobile o orientato al SIMD (in cui Itanium era davvero bravo), la maggior parte del codice avrà molte letture / scritture e salti di memoria che rendono impossibile questo sogno di più di 3 operazioni per ciclo (specialmente nei software orientati al server come database, compilatori, esecuzione di linguaggio di alto livello come javascript, emulazione ecc ...). Questo è ciò che affondò Itanium.
Tutto dipende dalla differenza tra calcolo ed esecuzione!
Chi ti dice che il processore ha sempre 32 registri? x86 ha 8, ARM 32-bit e x86_64 ne hanno 16, IA-64 ne ha 128 e molti altri numeri. Puoi dare un'occhiata qui . Anche MIPS, PPC o qualsiasi altra architettura che abbia 32 registri di uso generale nel set di istruzioni, il numero è molto più grande di 32 poiché ci sono sempre registri flag (se presenti), registri di controllo ... esclusi registri rinominati e registri hardware
Tutto ha il suo prezzo. Maggiore è il numero di registri, maggiore è il lavoro svolto durante la commutazione delle attività, maggiore è lo spazio necessario nella codifica delle istruzioni. Se si dispone di meno registri, non è necessario archiviare e ripristinare molto quando si chiama e si ritorna da funzioni o si cambia attività con il compromesso della mancanza di registri in alcuni codici di calcolo
Inoltre, più grande è il file di registro, più costoso e complesso sarà. SRAM è la RAM più veloce e costosa, quindi viene utilizzata solo nella cache della CPU. Ma è ancora molto più economico e occupa meno area di un file di registro con la stessa capacità.
Ad esempio, un tipico processore Intel ha "ufficialmente" 16 numeri interi e 16 registri vettoriali. Ma in realtà ce ne sono molti altri: il processore usa "rinomina registro". Se hai un'istruzione reg3 = reg1 + reg2 avresti un problema se un'altra istruzione che utilizza reg3 non fosse ancora terminata - non potresti eseguire la nuova istruzione nel caso in cui sovrascriva reg3 prima che sia stata letta dall'istruzione precedente.
Pertanto ci sono circa 160 registri reali . Quindi la semplice istruzione sopra è cambiata in "regX = reg1 + reg2, e ricorda che regX contiene reg3". Senza i registri di rinomina, l'esecuzione fuori servizio sarebbe assolutamente morta nell'acqua.
Non sono un ingegnere elettrico, ma penso che un'altra possibilità per il motivo di limitare il numero di registri sia il routing. Esistono un numero limitato di unità aritmetiche e devono essere in grado di prendere input da ogni registro e output ad ogni registro. Ciò è particolarmente vero quando si dispone di programmi in pipeline che possono eseguire molte istruzioni per ciclo.
Una versione semplice di questo avrebbe complessità, rendendo non scalabile il numero di registri o altrimenti richiederebbe una riprogettazione del routing a qualcosa di molto più complicato per instradare tutto con una complessità migliore.
Ho avuto l'idea di questa risposta guardando alcuni dei discorsi di Ivan Godard sulla CPU del mulino. Parte dell'innovazione della CPU Mill è che non è possibile eseguire l'output su registri arbitrari: le uscite sono tutte inserite in uno stack di registro o "cinghia", il che riduce così i problemi di routing, perché si sa sempre dove andrà l'output. Si noti che hanno ancora il problema di routing per ottenere i registri di input per le unità aritmetiche.
Vedere The Mill CPU Architecture - the Belt (2 di 9) per la descrizione del problema e la soluzione di Mill.