SIMD and Go
Walks through using SIMD in Go for a simple float32 add loop and the speedup over scalar code.
Extract
Take a really boring loop: walk two []float32 slices and add them together, element by element. Written the normal way in Go, the CPU handles one pair per step. The funny thing is that the same CPU has hardware that could handle eight pairs at once, and a plain Go loop never uses it.That hardware is SIMD. Until recently, the only way to use it from Go was hand-written assembly. Now Go has an experimental simd/archsimd package (Go 1.26) and a portable simd package on top of it (Go 1.27), so you can write normal Go that turns into SIMD instructions.We’ll start with how SIMD works inside the CPU and meet Go’s two SIMD packages. Then we’ll go through the most common SIMD operations, with Go code for each one, peek at how the compiler makes it all work, and finally put everything together in a small, real example.📌 A note on scopeEverything here is based on Go 1.27.0 with GOEXPERIMENT=simd. The package is experimental, so names may change between releases. I’ll stick to amd64 (Intel and AMD CPUs) and skip a lot of the instruction set to keep the core ideas clear.Before we write any Go, though, let’s see what that “eight pairs at once” hardware actually does.What SIMD Actually MeansSIMD stands for Single Instruction, Multiple Data. But what does that actually mean? Let’s put the same operation side by side, once without SIMD and once with it.The scalar version is a normal loop: one instruction adds one pair of numbers, then the next instruction adds the next pair, eight times in a row. The SIMD version works differently: instead of adding the pairs one at a time, a single CPU instruction takes all eight numbers from a and all eight from b and adds them in one go. One instruction instead of eight, so the whole thing finishes much faster.There’s a catch, though: all eight additions have to be the same operation. You can’t add in one slot and multiply in the next. SIMD shines when you have lots of independent data that all needs the same treatment, which is very common in things like image processing, audio or machine learning.But notice that in the SIMD version, all eight numbers had to sit together somewhere before the add. That somewhere is a special kind of register.A normal register holds a single 64-bit value, so eight floats don’t fit. SIMD has its own, much wider vector registers: 128, 256 or 512 bits, depending on your CPU. In this post we’ll mostly use 256-bit ones, which hold exactly eight float32 values.But 256 bits of what? Floats? Bytes? Let’s look …