# SIMD e vetores

Paralelismo explícito de dados, registos vetoriais, um exemplo de soma e os limites do speedup.

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

O pipeline e a predição exploram o paralelismo **implícito**: o hardware descobre sozinho que instruções pode sobrepor. O SIMD (_single instruction, multiple data_) é paralelismo **explícito**: uma só instrução declara que a mesma operação se aplica a vários dados ao mesmo tempo, e o programador (ou o compilador) organiza o trabalho para isso. É a técnica por trás do desempenho em multimédia, gráficos e computação científica.

## A ideia em dez segundos

Somar dois vetores de 4 elementos sem SIMD demora 4 instruções de soma. Com registos vetoriais de 4 elementos, demora 1 instrução de soma vetorial mais as cargas e guardas vetoriais:

```
vle32.v  v0, (a0)    # carrega 4 palavras de a0 para o registo vetorial v0
vle32.v  v1, (a1)    # carrega 4 palavras de a1 para v1
vadd.vv  v0, v0, v1  # v0[i] = v0[i] + v1[i], os 4 em paralelo
vse32.v  v0, (a2)    # guarda os 4 resultados em a2
```

Este trecho é ilustrativo: o qtrvsim e o RARS base não implementam a extensão vetorial, por isso lê-o como o padrão (carregar, operar, guardar) e corre a versão escalar. Cada registo vetorial guarda vários elementos; a unidade funcional tem várias ALUs em paralelo (ou uma em pipeline profundo) e produz todos os resultados juntos. O ganho ideal acompanha a largura: 4 elementos por instrução, 4 vezes menos instruções de soma.

![Quatro vias em paralelo: cada via soma v0[i] com v1[i] ao mesmo tempo, com setas de carga vetorial à entrada e de guarda vetorial à saída.](https://resumos.rgo.pt/cadeiras/ac/simd/figura-1.svg)

A versão escalar faz o mesmo cálculo elemento a elemento; a versão desenrolada escreve as 4 somas à mão, como a vetorial faria em hardware. Ambas imprimem os mesmos 4 resultados:

```c
#include <stdio.h>
int main(void) {
    int a[4] = {1, 2, 3, 4}, b[4] = {10, 20, 30, 40}, c[4], d[4];
    for (int i = 0; i < 4; i++) c[i] = a[i] + b[i];
    d[0] = a[0] + b[0]; d[1] = a[1] + b[1];
    d[2] = a[2] + b[2]; d[3] = a[3] + b[3];
    printf("%d %d %d %d\n", c[0], c[1], c[2], c[3]);
    printf("%d %d %d %d\n", d[0], d[1], d[2], d[3]);
    return 0;
}
```

Isto é diferente do pipeline: o pipeline sobrepõe instruções **diferentes** no tempo, o SIMD executa a **mesma** operação sobre dados diferentes no mesmo instante. E é diferente do multicore: há um só fluxo de instruções, sem threads nem sincronização.

## Quando rende e quando não rende

O SIMD rende quando o mesmo cálculo se repete sobre muitos dados independentes: somar vetores, ajustar o brilho de uma imagem, aplicar um filtro, multiplicar matrizes. O padrão é sempre o mesmo: um ciclo cujas iterações não comunicam entre si.

Não rende quando há dependências entre elementos (cada iteração precisa do resultado da anterior), quando o controlo diverge (metade dos elementos segue um ramo e a outra metade outro) ou quando os acessos são irregulares (cada elemento está num endereço imprevisível). Nesses casos o código vetorial passa o tempo a mascarar elementos e a reorganizar dados, e o escalar simples ganha.

```
for (int i = 0; i < n; i++)
    c[i] = a[i] + b[i];
```

Cada iteração só lê `a[i]` e `b[i]`: as 4 vias trabalham sem comunicar.

```
for (int i = 1; i < n; i++)
    a[i] = a[i] + a[i - 1];
```

Cada iteração precisa do resultado da anterior: não há vias independentes e o vetorial não rende.

## A conta do speedup

Volta à [lei de Amdahl](https://resumos.rgo.pt/cadeiras/ac/simd/desempenho/): se 60% do tempo está num ciclo vetorizável e a unidade SIMD acelera essa parte 4 vezes, o speedup total é $1 / (0{,}4 + 0{,}6/4) \approx 1{,}82$, como já calculaste. Duplica a largura para 8 elementos e o speedup sobe só para $1 / (0{,}4 + 0{,}6/8) \approx 2{,}11$. A fração não vetorizável manda, e cada duplicação de hardware rende menos que a anterior.

Há ainda o custo escondido: carregar os dados para os registos vetoriais e voltar a guardá-los. Se o vetor for curto, o prólogo e o epílogo comem o ganho. A regra prática é que o SIMD compensa em ciclos longos sobre dados contíguos, exatamente o caso dos vetores e das imagens.

O erro mais comum

Multiplicar o speedup pela largura sem aplicar Amdahl. “8 elementos em paralelo” não significa programa 8 vezes mais rápido: significa a parte vetorizável 8 vezes mais rápida, e o total decide-se na fórmula com a fração real.

## SIMD na prática da cadeira

Nos resultados de aprendizagem, o SIMD aparece com uma exigência concreta: aplicar instruções vetoriais em sub-rotinas de elevado desempenho. O padrão pedido é o desta página: identifica o ciclo paralelizável, carrega em bloco, opera em vetor, guarda em bloco. Nos testes, a pergunta típica dá-te um ciclo escalar e pede o equivalente vetorial, ou dá-te tempos com e sem vetorização e pede o speedup pela lei de Amdahl. Em ambos os casos, o método é o mesmo: fração primeiro, contas depois.

## Para saber mais

*   [Especificação da extensão vetorial RISC-V, versão 1.0](https://github.com/riscv/riscv-v-spec/blob/v1.0/v-spec.adoc): o documento oficial com `vsetvli` e agrupamento de registos.
