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
-O3and-marchcan 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
float32dot product outperforms the blog’s float versions, but lack ofint8dot 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.