tandem rng tandem-metal GitHub

Performance

swift run -c release tandem-bench prints the GPU fills into device memory, no readback, 16 fills per command buffer timed on the GPU, best of 7, and the CPU fills on one core, best of 5. Apple M4 Pro, macOS 26, Swift 6.3.3, GiB/s of output, every figure from one session.

CPU

The baselines are the fastest generators Swift reaches: arc4random_buf for raw words, and a loop over libc's drand48 for the rest, with Box-Muller pairs for the normals and -log(1 - u) for the exponentials. drand48 runs four times or more faster than SystemRandomNumberGenerator and GameplayKit's sources.

CPU fill, 2^22 Tandem baseline
fillU32 8.89 4.49
fillU64 8.78 4.44
fillF32 7.77 2.96
fillF64 7.79 5.94
fillU32(_:below: 1000) 3.54 2.76
fillNormalF64 3.66 1.02
fillNormalF32 1.85 0.50
fillExponentialF64 3.19 1.38
fillExponentialF32 2.38 1.49

On the same machine tandem-c fills 18.7 GiB/s of u32, 7.1 of f64 normals and 6.1 of f64 exponentials, so the CPU fills run at 35% to 55% of tandem-c. LLVM folds the row transpose into the shuffles of the step, and Swift has no shuffle intrinsic to keep them apart.

GPU

The baseline is MPSMatrixRandomPhilox, Philox4x32-10 from Metal Performance Shaders, timed the same way into the same buffer. MPS has uint32 words, float32 uniforms and float32 normals. The rows it has no draw for compare with its uint32 words of the same byte count, or its float32 uniforms for the exponentials, marked *.

GPU fill 2^24 MPS Philox 2^26 MPS Philox
.u32 226 174 190 171
.u64 211 175* 182 159*
.f32 236 170 189 171
.u32Below(range: 1000) 224 173* 191 172*
.u32BelowWide(range: 1000) 211 175* 181 159*
.u64Below(range: 1000) 207 175* 180 156*
.u32Below(range: 2^31 + 1) 17.5 174* 18.0 173*
.normalF32 261 109 189 110
.normalF32, odd first draw 159 109 165 110
.exponentialF32 252 171* 151 147*

A range of 2^31 + 1 rejects half the draws, and each rejected draw computes a block of its fallback stream from the key.