# Superescalares

Emissão múltipla, dependências que limitam, execução fora de ordem e commit em ordem.

Página: https://resumos.rgo.pt/cadeiras/ac/superescalar/

O pipeline escalar completa no máximo uma instrução por ciclo. O processador **superescalar** replica hardware para buscar, descodificar e executar **várias instruções por ciclo**: CPI abaixo de 1, ou, na linguagem do mercado, IPC (_instructions per cycle_) acima de 1. O preço é decidir em hardware, a cada ciclo, que instruções podem avançar juntas sem violar o programa.

## Emissão múltipla

Um superescalar de 2 vias busca 2 instruções por ciclo e tem (pelo menos) 2 unidades funcionais para as executar. Se o programa fosse uma fila de instruções independentes, o débito duplicava. Mas as instruções dependem umas das outras, e as dependências mandam:

```
add  t0, t1, t2
sub  t3, t0, t4
```

O `sub` precisa de `t0`, que o `add` ainda está a calcular. Não há paralelismo aqui: o `sub` espera, e a segunda via fica vazia nesse ciclo. O débito real fica ditado pelo paralelismo do programa, não pela largura da máquina. Duplicar as vias sem instruções independentes para as encher é hardware parado.

## O que limita: três dependências

Entre duas instruções há três relações possíveis, comparando o que cada uma lê e escreve:

*   **RAW** (_read after write_): a segunda lê o que a primeira escreve. É dependência a sério: o valor tem de fluir da primeira para a segunda, por forwarding ou espera.
*   **WAR** (_write after read_): a segunda escreve num registo que a primeira lê. Não há fluxo de valor nenhum; o problema é só o nome do registo estar reutilizado. Se a escrita adiantada corromper a leitura atrasada, o programa parte, mas nada impede a ordem certa com registos diferentes.
*   **WAW** (_write after write_): ambas escrevem no mesmo registo. Outra vez só o nome colide: a ordem das escritas é que tem de ser preservada.

RAW limita de verdade; WAR e WAW são **dependências falsas**, artefactos da reutilização de nomes. E é aqui que entra a técnica central desta página.

![Quatro instruções com as três dependências: RAW a cheio de 1 para 2 e de 3 para 4 em t0, WAR a tracejado de 2 para 3 em t3 e WAW a tracejado de 1 para 4 em t0.](https://resumos.rgo.pt/cadeiras/ac/superescalar/figura-1.svg)

## Execução fora de ordem

O processador **fora de ordem** não espera pela instrução bloqueada: olha para a frente (a **janela de instruções**), encontra trabalho independente mais à frente e executa-o já. Toma esta sequência:

```
lw   t0, 0(t1)     # 1: falta a cache, demora
add  t3, t0, t4    # 2: depende de 1, bloqueia
sub  t5, t6, t7    # 3: independente, pode avancar
```

Um pipeline em ordem parava na 2 e a 3 esperava também. O fora de ordem executa a 3 enquanto a 1 espera pela memória, e faz a 2 quando `t0` chegar. O programa observa exatamente a mesma sequência de resultados, mas o hardware preenche os vazios com trabalho útil.

![Emissão em duas vias: a instrução 2 bloqueada ocupa a via B enquanto a instrução 3, independente, passa à frente pela via livre.](https://resumos.rgo.pt/cadeiras/ac/superescalar/figura-2.svg)

Para isto funcionar, o processador resolve as dependências falsas com **renomeação de registos**: dá a cada escrita um registo físico novo e separado, por isso WAR e WAW desaparecem (nomes diferentes nunca colidem) e só as RAW verdadeiras impõem ordem. O registo arquitetural `t0` do programa passa a ser uma etiqueta; por baixo há um ficheiro de registos físicos maior que o programador nunca vê.

Segue três escritas em `t0` a receberem físicos distintos, com `p` para os registos físicos:

| Instrução | Lê físicos | Escreve físico novo |
| --- | --- | --- |
| `add t0, t1, t2` | `t1`, `t2` | `p40` para `t0` |
| `sub t3, t0, t4` | `p40` (`t0`) | `p42` para `t3` |
| `add t0, t5, t6` | `t5`, `t6` | `p41` para `t0` |

A segunda escrita em `t0` recebe `p41` em vez de reutilizar `p40`: a WAW entre a primeira e a terceira instrução desaparece, e um leitor atrasado de `p40` continua a ver o valor certo. É assim que nomes diferentes eliminam as dependências falsas sem mudar o programa.

## Commit em ordem

Executar fora de ordem não significa terminar fora de ordem para o mundo exterior. As instruções **reformam-se** (_commit_) pela ordem original do programa: só quando todas as anteriores terminaram sem exceção é que o resultado se torna visível (escrita no registo, loja na memória). Se uma instrução levantar uma exceção, o processador descarta tudo o que veio depois dela como se nunca tivesse corrido, e o sistema operativo vê o estado exato da instrução que falhou. A execução é especulativa e desordenada por dentro; o comportamento observável é sequencial e preciso.

## O limite do paralelismo implícito

Junta as peças e vês o teto: a largura de emissão, o tamanho da janela, a precisão da [predição de saltos](https://resumos.rgo.pt/cadeiras/ac/superescalar/predicao-saltos/) (cada erro deita fora a janela inteira) e, acima de tudo, o paralelismo real do programa. Como regra prática, passar de 2 para 4 vias duplica o hardware de emissão e de forwarding, mas o IPC típico sobe muito menos, porque os programas comuns têm paralelismo limitado.[1](https://resumos.rgo.pt/cadeiras/ac/superescalar/#user-content-fn-vias) É este rendimento decrescente, somado ao muro da energia, que empurrou a indústria para vários núcleos em vez de núcleos cada vez mais largos. E isso é a próxima página.

## Notas de rodapé

1.  A quantificação exata depende do programa e da microarquitetura; o _Computer Organization and Design_ de Patterson e Hennessy, livro de referência da cadeira, mede estes rendimentos com benchmarks SPEC. [Voltar](https://resumos.rgo.pt/cadeiras/ac/superescalar/#user-content-fnref-vias)
