Platform-Independent SIMD in Go
Platform-Independent SIMD in Go
By David Chase and Junyang Shao Originally published: 24 September 2026
With the release of Go 1.26 and Go 1.27, the language introduces experimental APIs designed for Single Instruction Multiple Data (SIMD) operations.
What is SIMD?
SIMD is a hardware capability found in most modern CPUs that enables a single instruction to perform the same operation across a vector of data simultaneously. For instance, instead of adding two numbers, a CPU can add 8 pairs of float64 values in one go.
Mathematically, if we have two vectors and , SIMD allows us to compute: where each element is processed in parallel.
This provides a massive performance boost for computationally heavy workloads, including:
- AI and Machine Learning
- Data Processing
- Cryptography
Interestingly, Go's own Green Tea garbage collector utilizes SIMD to speed up the process of scanning memory for live objects.
The Evolution of SIMD in Go
Historically, accessing SIMD power required writing Go assembly. While effective, this was only feasible for the most critical performance kernels. Consequently, many applications that could have benefited from vectorization simply left CPU resources untapped.
The rollout of new APIs happened in stages:
- Go 1.26: Introduced the
archsimdAPI foramd64. - Go 1.27: Expanded
archsimdto includearm64(NEON) andwasm.
The Architecture Gap
The primary hurdle for a universal SIMD API is the extreme diversity of hardware. Platforms differ not just in what they can do, but in how they represent data.
To provide raw access to these differences, Go uses the architecture-specific archsimd package. However, Go 1.27 introduces a higher-level, platform-agnostic simd interface (inspired by the Highway library for C++).
The Goal: Enable "write-once" code that achieves near-assembly performance on supported hardware, while providing a functional emulation for platforms that lack SIMD support.
Currently, the simd package supports:
- amd64: AVX, AVX2, and AVX512
- arm64: NEON
- wasm: SIMD instructions
The Challenge: Hardware Fragmentation
SIMD architectures vary across several critical dimensions:
1. Vector Size
Vector lengths are not standardized across the industry.
| Architecture | Vector Size Characteristics | Examples |
|---|---|---|
| Fixed (Single) | Always 128 bits | wasm, PowerPC, s390x |
| Fixed (Multiple) | Multiple options (128, 256, 512) | amd64, loong64 |
| Variable | Unspecified (128 to 65536 bits) | riscv64 |
| Hybrid | Fixed (128) AND Variable (128-2048) | arm64 (NEON & SVE) |
2. Vector Masking
Masking allows "if-then-else" logic within a vector. If a mask is "true" for an element, the operation is applied; otherwise, it is ignored.
- Bitmasking: No special registers; uses boolean vector operations (
wasm, AVX, AVX2, NEON). - Dedicated Registers: Specific mask registers where one bit controls one element (AVX512, RVV).
- Byte-based: One bit per byte, with the LSB controlling the operation (SVE).
- Hybrid: AVX2 uses standard vectors as masks for loads/stores (using the MSB).
3. Other Discrepancies
- Rearrangement: Primitives for shuffling elements vary between constant and variable inputs.
- Crypto: Support for specialized cryptographic instructions differs by chip.
- Basic Math: Some platforms lack basic features (e.g.,
wasmcannot compare 64-bit integer vectors).
Because of these "quirks," the archsimd package—while uniform—still requires developers to handle platform-specific edge cases, making multiplatform development tedious.
The simd Package Overview
The new simd package solves these issues by:
- Removing fixed-size vectors from the type system.
- Supporting only the intersection of features available across all platforms.
- Using efficient emulation to fill the gaps where a platform lacks a specific instruction.
This ensures that code is easy to read, maintainable, and potentially writable by LLMs, without sacrificing the efficiency of the underlying hardware.
Usage and Implementation
To enable this experimental feature, build your program with:
GOEXPERIMENT=simd
Vector types are represented as capitalized, pluralized versions of primitive types (e.g., simd.Uint8s, simd.Float32s).
Example: Calculating an Inner Product
// innerProduct computes the dot product of two float32 slices.
func innerProduct(x, y []float32) float32 {
var a simd.Float32s
var i int
// Process main chunks of the slices
for i = 0; i <= len(x)-a.Len(); i += a.Len() {
u := simd.LoadFloat32s(x[i : i+a.Len()])
v := simd.LoadFloat32s(y[i : i+a.Len()])
a = u.MulAdd(v, a)
}
// Handle remaining elements (the tail)
if i < len(x) {
u, _ := simd.LoadFloat32sPart(x[i:])
v, _ := simd.LoadFloat32sPart(y[i:])
a = u.MulAdd(v, a)
}
return sum(a)
}
Conceptual representation of vector loading and multiplication.