Há muitos motivos pelos quais você não tem apenas um grande número de registros:
- Eles estão altamente ligados à maioria dos estágios do pipeline. Para começar, você precisa rastrear sua vida útil e encaminhar os resultados de volta aos estágios anteriores. A complexidade se torna intratável muito rapidamente e o número de fios (literalmente) envolvidos cresce na mesma proporção. É caro na área, o que significa que é caro em energia, preço e desempenho depois de um certo ponto.
- Ele ocupa espaço de codificação de instruções. 16 registradores ocupam 4 bits para origem e destino, e outros 4 se você tiver instruções de 3 operandos (por exemplo, ARM). É uma quantidade enorme de espaço de codificação de conjunto de instruções, ocupada apenas para especificar o registro. Isso eventualmente afeta a decodificação, o tamanho do código e novamente a complexidade.
- Existem melhores maneiras de obter o mesmo resultado ...
Atualmente, temos muitos registradores - eles apenas não estão programados explicitamente. Temos "renomeação de registro". Enquanto você acessa apenas um pequeno conjunto (8-32 registradores), eles na verdade são apoiados por um conjunto muito maior (por exemplo, 64-256). A CPU então rastreia a visibilidade de cada registro e os aloca para o conjunto renomeado. Por exemplo, você pode carregar, modificar e, em seguida, armazenar em um registro muitas vezes seguidas e ter cada uma dessas operações realmente executada de forma independente, dependendo das falhas de cache etc. No ARM:
ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]
Os núcleos do Cortex A9 registram a renomeação, então a primeira carga para "r0" na verdade vai para um registro virtual renomeado - vamos chamá-lo de "v0". O carregamento, incremento e armazenamento acontecem na "v0". Enquanto isso, também executamos um carregamento / modificação / armazenamento em r0 novamente, mas isso será renomeado para "v1" porque esta é uma sequência totalmente independente usando r0. Digamos que a carga do ponteiro em "r4" parou devido a uma falha no cache. Tudo bem - não precisamos esperar que "r0" esteja pronto. Por ser renomeado, podemos executar a próxima sequência com "v1" (também mapeado para r0) - e talvez seja um acerto de cache e acabamos de ter uma grande vitória de desempenho.
ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]
Eu acho que o x86 é até um número gigantesco de registros renomeados atualmente (estimativa 256). Isso significaria ter 8 bits vezes 2 para cada instrução apenas para dizer qual é a origem e o destino. Isso aumentaria enormemente o número de fios necessários ao longo do núcleo e seu tamanho. Portanto, há um ponto ideal em torno de 16-32 registradores que a maioria dos designers se conformaram, e para designs de CPU fora de ordem, a renomeação de registradores é a maneira de mitigar isso.
Editar : A importância da execução fora de ordem e renomeação de registro neste. Depois de ter OOO, o número de registros não importa muito, porque eles são apenas "marcas temporárias" e são renomeados para o conjunto de registros virtuais muito maior. Você não quer que o número seja muito pequeno, porque fica difícil escrever pequenas sequências de código. Este é um problema para x86-32, porque os 8 registros limitados significam que muitos temporários acabam passando pela pilha, e o núcleo precisa de lógica extra para encaminhar leituras / gravações para a memória. Se você não tem OOO, geralmente está falando de um núcleo pequeno; nesse caso, um grande conjunto de registros é um benefício de baixo custo / desempenho.
Portanto, há um ponto ideal natural para o tamanho do banco de registradores que atinge o máximo em cerca de 32 registradores arquitetados para a maioria das classes de CPU. x86-32 tem 8 registros e é definitivamente muito pequeno. ARM foi com 16 registros e é um bom compromisso. 32 registros é um pouco demais - você acaba não precisando dos últimos 10 ou mais.
Nada disso afeta os registros extras que você obtém para SSE e outros coprocessadores de ponto flutuante vetorial. Eles fazem sentido como um conjunto extra porque são executados independentemente do núcleo inteiro e não aumentam a complexidade da CPU exponencialmente.