tandem rng tandem-numpy GitHub

Performance

pixi run bench produces the figures.

CPU

Apple M4, one thread, pixi run bench, 2^22 elements per call, GiB/s of output, the median of three passes of the minimum of five runs. Every call allocates its array.

Tandem fill TandemGenerator(42) Generator(Tandem(42)) Generator(PCG64(42)) Generator(Philox(42))
random float64 16.5 16.5 5.0 2.4 2.8
random float32 16.6 16.4 3.0 2.3 1.9
integers(0, 1000) int32 7.7 7.7 3.1 2.3 1.9
integers(0, 1000) int64 7.5 12.7 5.8 4.5 3.8
standard_normal float64 7.6 7.7 2.2 2.0 2.0
standard_normal float32 5.5 5.5 1.1 1.5 1.0
standard_exponential float64 6.1 6.1 3.9 2.1 2.5
standard_exponential float32 6.7 6.6 1.2 1.3 1.0
raw uint64 words 19.1 - 4.9 2.4 3.1

The Tandem column calls the C fills of the bit generator with the GIL released: random, below(1000, n, dtype) at the dtype's width, normal, exponential, and raw. TandemGenerator routes the sampler names through the same fills. The plain Generator columns are NumPy's own samplers, over Tandem, over the default PCG64, and over Philox4x32-10, the counter-based generator of the GPU libraries: Generator calls the bit generator one value at a time, through next_double, next_uint32, and next_uint64, and runs its own ziggurat for normals and its own Lemire method for integers. Its speed is bounded by that call, and its normals and large-range integers are NumPy's values, not Tandem's. The hooks fill a buffer of 1024 words at a time, with the stream's alignment rules kept for mixed widths.