Portable, seekable random streams for parallel CPU simulation

vphilox is a header-only C++20 library built on Philox4x32-10, a counter-based generator whose whole state is a key and a position. A checkpoint is six 32-bit integers and a word offset. It survives a move between standard libraries, instruction sets, and thread counts. std::mt19937 survives none of the three.

Counter interleaving across SIMD lanesTwo passes drawn on the same time axis. The first carries a single counter through ten rounds of Philox and writes one 128-bit block. The second loads eight consecutive counters into the eight lanes of one 256-bit register and carries all of them through the same ten rounds, writing eight blocks in the same span. A bar chart below compares cost per byte against std::mt19937: one times the baseline against a measured range of 0.22 to 0.75.scalar · one counter per passr₁r₂r₃r₄r₅r₆r₇r₈r₉r₁₀c₀+016 BAVX2 · eight counters per __m256ic₀+0c₀+1c₀+2c₀+3c₀+4c₀+5c₀+6c₀+716 B16 B16 B16 B16 B16 B16 B16 B8 × 16 B = 128 Bno 32-lane widening multiply: each round pays two, even lanes then oddcost per byte, relative to std::mt19937std::mt199371.00×vphilox0.22 – 0.75×five processors · the tenfold penalty reproduced on none of them
One at a time.

The objection was always speed

An earlier attempt to put Philox into a production machine-learning library was rejected on the grounds that scalar Philox runs at one tenth the speed of the Mersenne Twister. That tenfold penalty did not reproduce on any of the five processors measured here. Interleaving independent counters across SIMD lanes puts the generator between 0.22× and 0.75× the cost per byte ofstd::mt19937, so on the bulk path it is faster than the engine it replaces. Aggregate cost per byte is flat across thread counts, and the first parallel limit the paper finds is hyperthread co-location, not the generator.

0.22–0.75×
cost per byte against std::mt19937, bulk path, five processors
7 integers
the entire serialized state, version tag aside
O(1)
discard(N) costs the same for N = 1 and N = 240

What actually gets written

The C++ standard fixes the text format of an engine’s stream operators only partially, and the three major standard libraries disagree. libstdc++ writes the 624 state words of std::mt19937 in raw internal order followed by a position index; libc++ and the Microsoft standard library write the same words rotated into canonical order and write no position at all. The word counts agree, which is what makes the failure dangerous: a reader handed 624 integers cannot tell that they are in the wrong order, so the restore succeeds, at a position nobody chose, without a diagnostic.

vphilox writes a position instead.

vphilox1 <k0> <k1> <c0> <c1> <c2> <c3> <word>

Seven decimal integers and a version tag. Nothing about the engine’s refill buffer is in there, which is why states written before the AVX-512 kernel raised the refill size from 8 blocks to 16 still load after it. Digits are formatted and parsed by hand, so an imbued grouping locale cannot corrupt a checkpoint, and unrecognised input is refused rather than interpreted.