Vector SIMD em Zig — O que é e Como Usar
Definição
Vetores SIMD (Single Instruction, Multiple Data) em Zig são tipos que permitem executar a mesma operação em múltiplos dados simultaneamente, aproveitando instruções vetoriais da CPU como SSE, AVX (x86) ou NEON (ARM). O tipo @Vector(N, T) cria um vetor de N elementos do tipo T, e operações sobre ele são mapeadas diretamente para instruções SIMD do hardware.
Diferente de bibliotecas SIMD externas, vetores SIMD são cidadãos de primeira classe em Zig — suportam operadores aritméticos, comparações e indexação nativamente.
Por que Vetores SIMD Importam
- Performance: Processar 4, 8, 16 ou mais elementos em uma única instrução de CPU.
- Portabilidade: O compilador traduz para as instruções SIMD disponíveis na arquitetura-alvo.
- Sem assembly: Acesso a SIMD com a mesma sintaxe de Zig normal.
- Auto-vetorização: O LLVM backend pode otimizar ainda mais o código vetorial gerado.
Exemplo Prático
Operações Básicas com Vetores
const std = @import("std");
pub fn main() void {
const a: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };
const b: @Vector(4, f32) = .{ 5.0, 6.0, 7.0, 8.0 };
// Operações elemento a elemento
const soma = a + b; // { 6.0, 8.0, 10.0, 12.0 }
const produto = a * b; // { 5.0, 12.0, 21.0, 32.0 }
// Escalar broadcast
const dobro = a * @as(@Vector(4, f32), @splat(2.0));
std.debug.print("Soma: {d}\n", .{soma});
std.debug.print("Produto: {d}\n", .{produto});
std.debug.print("Dobro: {d}\n", .{dobro});
}
Soma de Array com SIMD
const std = @import("std");
fn somaSimd(dados: []const f32) f32 {
const VEC_LEN = 4;
var acumulador: @Vector(VEC_LEN, f32) = @splat(0.0);
var i: usize = 0;
while (i + VEC_LEN <= dados.len) : (i += VEC_LEN) {
const chunk: @Vector(VEC_LEN, f32) = dados[i..][0..VEC_LEN].*;
acumulador += chunk;
}
// Reduzir vetor para escalar
var total: f32 = @reduce(.Add, acumulador);
// Processar elementos restantes
while (i < dados.len) : (i += 1) {
total += dados[i];
}
return total;
}
pub fn main() void {
const dados = [_]f32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
const resultado = somaSimd(&dados);
std.debug.print("Soma SIMD: {d}\n", .{resultado}); // 55.0
}
Comparação Vetorial
const std = @import("std");
pub fn main() void {
const a: @Vector(4, i32) = .{ 10, 20, 30, 40 };
const b: @Vector(4, i32) = .{ 15, 15, 35, 35 };
// Comparação retorna vetor de bool
const maior = a > b; // { false, true, false, true }
// Selecionar baseado na comparação
const resultado = @select(i32, maior, a, b);
// { 15, 20, 35, 40 } — o maior de cada par
std.debug.print("Resultado: {}\n", .{resultado});
}
Operações Disponíveis
| Operação | Descrição |
|---|---|
+, -, *, / | Aritmética elemento a elemento |
>, <, ==, != | Comparação (retorna vetor de bool) |
@reduce(.Add, v) | Reduz vetor a escalar com soma |
@splat(valor) | Cria vetor com todos elementos iguais |
@select(T, mask, a, b) | Seleciona elementos por máscara |
@shuffle | Reorganiza elementos entre vetores |
Armadilhas Comuns
- Tamanho deve ser potência de 2: Vetores com tamanhos como 3 ou 5 podem não mapear para instruções SIMD reais.
- Alinhamento: Vetores SIMD exigem alinhamento específico. O compilador gerencia isso automaticamente em variáveis locais.
- Fallback escalar: Se a CPU não suportar SIMD para o tamanho escolhido, o compilador gera código escalar equivalente (mais lento).
- Elementos restantes: Quando o array não é múltiplo do tamanho do vetor, trate os elementos restantes em um loop escalar.
Termos Relacionados
- Comptime — Tamanho do vetor é definido em comptime
- Type Coercion — Conversão entre vetores e arrays
- Release Modes — Otimizações SIMD dependem do modo de build
- Cross-Compilation — Instruções SIMD variam por arquitetura