Mandelbrot Set - SPMD Version
By Cedric Bail
This is the SPMD (Single Program Multiple Data) implementation of the Mandelbrot set computation, using go for loops to process 4 pixels simultaneously via WASM SIMD128.
Complete Source Code
// Mandelbrot Set - SPMD Version for Browser WASM Demo
// Uses go for SIMD loop for ~3x speedup over serial.
// Exports computeMandelbrot/getBufferPtr/getBufferLen for JavaScript interop.
package main
import (
"lanes"
"unsafe"
)
const bufSize = 256 * 256
var buf [bufSize]int32
func mandelSPMD(cRe, cIm lanes.Varying[float32], maxIter int) lanes.Varying[int32] {
var zRe lanes.Varying[float32] = cRe
var zIm lanes.Varying[float32] = cIm
var iterations lanes.Varying[int32] = int32(maxIter)
for iter := range maxIter {
magSquared := zRe*zRe + zIm*zIm
diverged := magSquared > 4.0
if diverged {
iterations = int32(iter)
break
}
newRe := zRe*zRe - zIm*zIm
newIm := 2.0 * zRe * zIm
zRe = cRe + newRe
zIm = cIm + newIm
}
return iterations
}
func mandelbrotSPMD(x0, y0, x1, y1 float32, width, height, maxIter int, output []int32) {
dx := (x1 - x0) / float32(width)
dy := (y1 - y0) / float32(height)
for j := 0; j < height; j++ {
y := y0 + float32(j)*dy
go for i := range width {
x := x0 + lanes.Varying[float32](i)*dx
iterations := mandelSPMD(x, y, maxIter)
index := j*width + i
output[index] = iterations
}
}
}
//go:export computeMandelbrot
func computeMandelbrot(width, height, maxIter int32) {
mandelbrotSPMD(-2.5, -1.25, 1.5, 1.25, int(width), int(height), int(maxIter), buf[:])
}
//go:export getBufferPtr
func getBufferPtr() int32 {
return int32(uintptr(unsafe.Pointer(&buf[0])))
}
//go:export getBufferLen
func getBufferLen() int32 { return bufSize }
func main() {}
How It Works
The SPMD version processes each row with a go for i := range width loop, which the compiler vectorizes into 4-wide SIMD operations. Each iteration computes 4 adjacent pixels in parallel:
lanes.Varying[float32](i)creates a vector of x-coordinates[i, i+1, i+2, i+3]mandelSPMDreceives varying complex coordinates and returns varying iteration counts- Per-lane
breakinside the iteration loop allows lanes that diverge early to exit independently - The compiler generates 53 WASM SIMD instructions (
v128.*) for the inner loop
The result is approximately 3x faster than the serial version, with identical output.
Key SPMD Patterns
- Varying break:
if diverged { break }exits individual lanes, not the entire loop - Scatter store:
output[index] = iterationswith varyingindexwrites 4 results simultaneously - Uniform broadcast: scalar
yandmaxIterare automatically broadcast to all lanes
Related
- Mandelbrot Set - Serial Version - Scalar baseline for comparison