deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (hnrss.org)

Go 1.26 and 1.27 ship experimental SIMD APIs with a portable vector layer

Go's experimental archsimd and simd packages bring near-assembly vector performance to amd64, arm64 and wasm, with emulation everywhere else.

Go 1.26 and 1.27 ship experimental SIMD APIs with a portable vector layer

The Go project has added experimental support for Single Instruction, Multiple Data (SIMD) programming across its two most recent releases. According to a post on the official Go blog, Go 1.26 introduced a SIMD API for amd64, while Go 1.27 extended coverage to arm64 (via NEON) and wasm, and layered on top a new portable interface that aims to hide platform differences altogether.

SIMD is a hardware capability present in most modern CPUs that applies one instruction across a whole vector of values at once — adding eight pairs of float64 numbers in a single operation, for example. The blog points out that this can substantially speed up compute-bound work in cryptography, data processing and AI, and that Go's Green Tea garbage collector already exploits SIMD internally to scan memory for live objects more quickly.

Two layers of API

The release actually contains two related pieces. The first, an architecture-dependent package called archsimd, exposes the full breadth of each platform's SIMD capabilities. It exists as a separate, per-architecture package because SIMD hardware varies so much between platforms — not only in supported operations but even in how vectors themselves are represented. Some architectures offer fixed-size vectors while others leave the size unknown until the program starts running.

The second piece, new in Go 1.27, is an experimental, fully portable and size-agnostic package named simd, loosely modelled on Highway for C++. Its stated goal is write-once code that performs close to hand-written assembly on platforms with SIMD support, and falls back to a capable emulation layer on platforms without it. The blog says the simd package currently targets AVX, AVX2 and AVX512 on amd64, NEON on arm64 and wasm's SIMD instructions, and emulates everything on other platforms so that code written against it always runs.

Why SIMD resists portability

The blog spends considerable space explaining why a uniform API is hard. Vector widths differ wildly: wasm, PowerPC and s390x offer a single fixed 128-bit width; amd64 supports 128, 256 and 512 bits; loong64 supports 128 and 256; riscv64 allows any power-of-two width between 128 and 65,536 bits that is only discoverable at runtime; and arm64 pairs the fixed 128-bit NEON with the variable-width SVE, which itself ranges from 128 to 2,048 bits.

Masking — selectively applying operations to some elements of a vector — is equally fragmented. Some architectures have no masks at all and fake them with bitmasks and boolean operations; AVX512 and RVV use dedicated mask registers with one bit per element; SVE uses one bit per byte; and AVX2 supports masked loads and stores with a plain vector as the mask.

Even basic operations are inconsistent. Wasm, for instance, lacks comparisons for vectors of 64-bit integers, and rearrangement and crypto primitives vary per architecture. On any given machine, code may also need to check which feature variant is actually present — AVX versus AVX2 versus AVX512, or NEON versus SVE, SVE2 or SVE2.1.

How the portable package works

The simd package handles this diversity through three deliberate choices described in the blog: it removes fixed-size vectors from the type system, it supports only the operations common to all platforms, and it fills gaps with efficient emulation built from other SIMD instructions. The team's stated criteria are that the operations suit data-processing algorithms not tied to a particular vector width, run at assembly-level speed when they map onto hardware, degrade gracefully when emulated, and stay easy to read — including, the blog notes, for code an LLM might generate.

Vector types are simply capitalized plurals of primitive types, such as simd.Uint8s or simd.Float32s, loaded from and stored to ordinary slices. A worked inner-product example in the blog shows vector loads, fused multiply-add via MulAdd, and a partial-load helper for tail elements. Comparisons yield typed mask values like Mask8s that can select and filter vectors. One limitation of this first release: there is no common way to sum across a vector's elements, so the example reduces to a scalar by hand; a ReduceSum function is planned for the next release.

Both packages are opt-in and enabled with the GOEXPERIMENT=simd environment variable.

Why it matters

Before these APIs, the only way to reach SIMD from Go was writing assembly, which the blog says only paid off for genuinely performance-critical kernels — meaning much ordinary software simply left most of the CPU idle. A portable, near-assembly-speed vector interface in a mainstream language changes that calculus for the whole ecosystem, from parsers and codecs to cryptography and machine-learning code. The design is also explicitly shaped for LLM-generated code, a telling signal of where the Go team expects maintainable SIMD to be written in the future. As with anything behind GOEXPERIMENT, the APIs are experimental and may still change.

  • #go
  • #simd
  • #performance
  • #programming-languages