De lento para SIMD: uma história de otimização em Go

A otimização manual com SIMD em Go para operações como produto escalar evidencia tanto o potencial de desempenho quanto as limitações atuais da linguagem para computação numérica de alto desempenho. Os comentaristas contrastam a falta de autovetorização e de abstrações ergonômicas de SIMD no Go com ecossistemas como C++, Rust, C# e Java, debatendo se intrinsics explícitas, assembly ou FFI para bibliotecas no estilo C/BLAS são o caminho mais prático. O debate também traz à tona compensações mais amplas entre escalar verticalmente com otimização de baixo nível, escalar horizontalmente com mais máquinas e os custos de manutenção e portabilidade de depender de assembly ou cgo.

Compilador Go, SIMD e modelo de otimização

  • Vários comentaristas observam que o Go ainda carece de autovetorização e, em geral, tem otimizações de laço fracas (por exemplo, unrolling limitado), o que o torna uma má escolha para trabalho numérico de alto nível em comparação com C/C++/Rust/C#/Java.
  • Alguns argumentam que isso pode ser uma “bênção oculta” porque vetorizadores são difíceis de acertar e podem ser frágeis ou bugados.
  • Preferência de vários participantes por abstrações explícitas de SIMD (à la ISPC, intrinsics, tipos SIMD portáveis) em vez de autovetorização “mágica”.

Comparações com outras linguagens e bibliotecas

  • C/C++: compiladores modernos com -O3 e -march muitas vezes conseguem autovetorizar produtos escalares, especialmente com flags de fast-math. SIMD escrito à mão ainda pode superar a autovetorização ao explorar detalhes microarquiteturais.
  • Rust: iteradores podem otimizar bem; laços com inteiros se vetorizam, mas reduções em ponto flutuante são bloqueadas por regras de associatividade, a menos que se usem intrinsics especiais ou recursos noturnos. SIMD portável é promissor, mas ainda não está totalmente estável.
  • C#: tem intrinsics estáveis e SIMD portável, além de bibliotecas auxiliares. Os vetores de Panama do Java estão em prévia, mas são criticados por operações lentas ou ausentes.
  • BLAS/Gonum: um produto escalar float32 de uma BLAS em Go supera as versões em float do blog, mas a falta de um dot int8 o torna inútil para vetores quantizados.
  • Outras ferramentas mencionadas: Halide (agendamento desacoplado), Google Highway, SimSIMD para outras linguagens e avo para geração de assembly em Go.

Fast-math e semântica numérica

  • Debate animado sobre flags no estilo -ffast-math: necessárias para vetorizar reduções em ponto flutuante, mas podem alterar sutilmente os resultados e historicamente tinham efeitos “contagiosos” entre unidades de tradução.
  • Alternativas sugeridas: flags mais estreitas (-fassociative-math, -fno-signed-zeros), atributos por função, tipos dedicados de “fast float” ou intrinsics estáveis com semântica explícita.

Slices do Go, capacidade e microdetalhes

  • Discussão sobre a sintaxe completa de slices do Go a[i:j:k]: ela limita a capacidade, pode evitar alguns cálculos extras de capacidade, mas não remove de forma confiável as checagens de limites.
  • A reutilização da capacidade de slices pode levar a aliasing surpreendente ao fazer append, o que alguns acham pouco intuitivo ou inseguro; outros veem slices como uma encapsulação limpa do idioma de C de ponteiro+len(+cap).

FFI, assembly e considerações de escala

  • Alguns preferem usar C via cgo (ou BLAS/Halide) em vez de asm escrito à mão; outros evitam cgo por complicações de toolchain, binário estático, portabilidade e depuração.
  • A paralelização entre núcleos é sugerida como outro caminho de ganho de desempenho, simples, ao lado do SIMD.
  • Ponto mais amplo: otimização vertical (velocidade em uma única máquina) pode reduzir materialmente os custos de infraestrutura, especialmente para SaaS com unit economics problemáticas.