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.
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.