Single Instruction Multiple Data - SIMD
Na taxonomia de Flynn, SIMD significa “Single Instruction, Multiple Data” (Instrução Única, Múltiplos Dados).
Nesse modelo, uma única instrução é aplicada simultaneamente a vários elementos de dados em paralelo. Isso é alcançado por meio de unidades de processamento que operam de forma sincronizada, geralmente em arquiteturas vetoriais ou com extensões específicas em CPUs modernas, como SSE (Streaming SIMD Extensions) ou AVX (Advanced Vector Extensions) no caso de processadores x86.
Principais características:
- Uma única instrução é executada por vez, mas ela opera em múltiplos dados ao mesmo tempo.
- Ideal para tarefas que envolvem operações repetitivas em grandes conjuntos de dados, como processamento de imagens, áudio ou cálculos científicos.
- Explora o paralelismo de dados, mas não o paralelismo de instruções.
O SIMD é amplamente utilizado em GPUs, processadores vetoriais e em extensões de CPUs modernas para acelerar operações como multiplicação de matrizes, transformações gráficas ou filtros digitais.
Exemplo
Imagine que você precisa aumentar o brilho de uma imagem digital. Cada pixel da imagem é representado por um valor de intensidade (digamos, de 0 a 255). Para aumentar o brilho, você adiciona um valor fixo (por exemplo, 10) a cada pixel.
Em uma abordagem sequencial (SISD), você processaria um pixel por vez. Já no modelo SIMD, uma única instrução “adicionar 10” é aplicada simultaneamente a vários pixels em paralelo.
Por exemplo:
- Dados:
[50, 100, 150, 200]
(valores de intensidade de 4 pixels). - Instrução: “Adicionar 10”.
- Resultado:
[60, 110, 160, 210]
, calculado em uma única operação vetorial.
Aqui, a mesma operação (adição) é executada em múltiplos dados (os pixels) ao mesmo tempo, exemplificando o paralelismo de dados do SIMD.
Aplicação em Código (C++)
O código usará registradores vetoriais para processar 4 valores de 32 bits em paralelo.
#include <iostream>
#include <emmintrin.h> // Biblioteca para SSE2
using namespace std;
int main() {
// Dados de entrada: 4 pixels com valores de intensidade (32 bits cada)
alignas(16) int pixels[4] = {50, 100, 150, 200}; // Alinhado para SSE
alignas(16) int resultado[4]; // Para armazenar o resultado
// Valor a ser adicionado (10) replicado em um vetor para SIMD
alignas(16) int incremento[4] = {10, 10, 10, 10};
// Carregar os dados nos registradores SSE (128 bits = 4 inteiros de 32 bits)
__m128i vec_pixels = _mm_load_si128((__m128i*)pixels);
__m128i vec_incremento = _mm_load_si128((__m128i*)incremento);
// Executar a instrução SIMD: adicionar os valores em paralelo
__m128i vec_resultado = _mm_add_epi32(vec_pixels, vec_incremento);
// Armazenar o resultado de volta na memória
_mm_store_si128((__m128i*)resultado, vec_resultado);
// Exibir os resultados
cout << "Pixels originais: ";
for (int i = 0; i < 4; i++) {
cout << pixels[i] << " ";
}
cout << endl;
cout << "Pixels ajustados: ";
for (int i = 0; i < 4; i++) {
cout << resultado[i] << " ";
}
cout << endl;
return 0;
}
-
Declaração de variáveis:
pixels
contém os valores iniciais dos 4 pixels:[50, 100, 150, 200]
.incremento
contém o valor 10 replicado 4 vezes para corresponder ao tamanho do vetor.- Os arrays são alinhados em 16 bytes (
alignas(16)
) para atender aos requisitos do SSE.
-
Carregamento nos registradores SSE:
_mm_load_si128
carrega 128 bits (4 inteiros de 32 bits) dos arrayspixels
eincremento
em registradores vetoriais (__m128i
).
O tamanho dos registradores depende da arquitetura do processar na máquina que vai rodar o código, embora os processadores mais modernos (a partir de 2011) já possuem registradores de 256 bits é recomendado utilizar 128 para garantir uma maior compatibilidade de hardware.
-
Instrução SIMD:
_mm_add_epi32
executa a adição em paralelo nos 4 elementos dos registradores. Essa é a essência do SIMD: uma única instrução (add
) opera em múltiplos dados simultaneamente.
-
Armazenamento e saída:
_mm_store_si128
salva o resultado de volta na memória.- O programa exibe os valores originais e ajustados:
[60, 110, 160, 210]
.
Requisitos para Executar
- Compile com suporte a SSE (ex.:
g++ -msse2 arquivo.cpp -o programa
). - Use uma CPU compatível com SSE2 (praticamente todas as CPUs modernas suportam).