From slow to SIMD: A Go optimization story

Manual SIMD optimization in Go for operations like dot products highlights both the performance potential and the language’s current limitations for high-performance numerical computing. Commenters contrast Go’s lack of autovectorization and ergonomic SIMD abstractions with ecosystems like C++, Rust, C#, and Java, debating whether explicit intrinsics, assembly, or FFI to C/BLAS-style libraries are the most practical path. The thread also surfaces broader trade-offs between vertical scaling via low-level optimization, horizontal scaling across more machines, and the maintenance and portability costs of relying on assembly or cgo.

Go compiler, SIMD, and optimization model

  • Several commenters note Go still lacks autovectorization and generally weak loop optimizations (e.g., limited unrolling), making it a poor fit for high‑end numeric work compared to C/C++/Rust/C#/Java.
  • Some argue this may be a “hidden blessing” because vectorizers are hard to get right and can be brittle or buggy.
  • Preference from multiple participants for explicit SIMD abstractions (ISPC‑like, intrinsics, portable SIMD types) over “magic” auto‑vectorization.

Comparisons with other languages and libraries

  • C/C++: Modern compilers with -O3 and -march can often auto‑vectorize dot products, especially with fast‑math flags. Handwritten SIMD can still beat auto‑vectorization by exploiting microarchitectural details.
  • Rust: Iterators can optimize well; integer loops vectorize, but floating‑point reductions are blocked by associativity rules unless using special intrinsics or nightly features. Portable SIMD is promising but not yet fully stable.
  • C#: Has stable intrinsics and portable SIMD, plus helper libraries. Java’s Panama vectors are in preview but criticized for slow or missing operations.
  • BLAS/Gonum: A Go BLAS float32 dot product outperforms the blog’s float versions, but lack of int8 dot makes it unusable for quantized vectors.
  • Other tooling mentioned: Halide (decoupled scheduling), Google Highway, SimSIMD for other languages, and avo for Go assembly generation.

Fast‑math and numerical semantics

  • Lively debate on -ffast-math‑style flags: needed for vectorizing FP reductions, but can subtly change results and historically had “infectious” effects across translation units.
  • Suggested alternatives: narrower flags (-fassociative-math, -fno-signed-zeros), per‑function attributes, dedicated “fast float” types, or stable intrinsics with explicit semantics.

Go slices, capacity, and micro‑details

  • Discussion of Go’s full slice syntax a[i:j:k]: it caps capacity, may avoid some extra cap computations, but does not reliably remove bounds checks.
  • Slice capacity reuse can lead to surprising aliasing when appending, which some find unintuitive or unsafe; others see slices as a clean encapsulation of C’s pointer+len(+cap) idiom.

FFI, assembly, and scaling considerations

  • Some prefer using C via cgo (or BLAS/Halide) over handwritten asm; others avoid cgo due to toolchain, static binary, portability, and debugging complications.
  • Parallelization across cores is suggested as an additional, straightforward speedup path alongside SIMD.
  • Broader point: vertical optimization (single‑node speed) can materially reduce infrastructure costs, especially for SaaS with problematic unit economics.