Comparing generators: xoshiro variants vs MRG32k3a

RandomDataStreams.jl lets you pick between two very different generator designs. This page helps you choose.

Side-by-side

PropertyMRG32k3aXoroshiro128* (p/ss/pp)Xoshiro256* (p/ss/pp)Xoshiro512* (p/ss/pp)
State (bits)192 (6 × Int64)128256512
Period≈ 2^1912^128 − 12^256 − 12^512 − 1
Native outputFloat64 [0,1), ~32-bit resolutionUInt64UInt64UInt64
Throughput (this package, single core)≈ 200 M/s≈ 1200 M/s≈ 1340 M/s≈ 1150 M/s
Stream mechanismmatrix power A^2^127 on the seedlong_jump! polynomial (2^96)long_jump! (2^192)long_jump! (2^384)
Substream mechanismmatrix power A^2^76short_jump! (2^64)short_jump! (2^128)short_jump! (2^256)
Backward jumpsyes (advance_state!)yes (advance_state!, GF(2) polynomials)yes (advance_state!)yes (advance_state!)
Arbitrary-jump costO(e) matrix productsO(deg²) GF(2) ops (~10–500 ms)samesame
Equidistributionwell analysed theory1-dim (**/++) / 0-dim (+)3-dim (**/++) / 2-dim (+)7-dim (**/++) / 6-dim (+)
BigCrush (Vigna's shootout)passespassespassespasses
Parallelism scale advisedlargemild (≈ 2^32 streams)very largeextreme

(* means any of the +, **, ++ scramblers.)

When to choose MRG32k3a

  • You need provably non-overlapping streams with published, peer-reviewed parameters (L'Ecuyer et al. 2002), e.g. to stay compatible with simulation libraries built on that model (SSJ, Simio, Arena-style stream ids...).
  • Your application consumes floats only: MRG32k3a emits them natively.
  • You can afford ~6x slower generation.

Note: backward jumping is no longer a differentiator — every RandomDataStreams generator supports advance_state!(rng, e, c) with negative distances.

When to choose a xoshiro/xoroshiro variant

  • You want maximum throughput (all are ≈ 6–7x faster than MRG32k3a here) or raw 64-bit integers.
  • Choose your state size by parallelism needs:
    • Xoroshiro128*: embedded/GPU or few workers only — its 2^64 substreams per 2^32 streams are enough for mild parallelism. Note Xoroshiro128p has a mild Hamming-weight dependency after ~5 TB of output; prefer ss/pp.
    • Xoshiro256*: the sweet spot for general use (Julia itself uses xoshiro256++ as its default RNG). Use + if you generate only floating-point numbers from the high bits (~15% faster historically); otherwise prefer ++/**, whose outputs are fully equidistributed.
    • Xoshiro512*: essentially never needed for period reasons — any period ≥ 2^256 is beyond every imaginable need — but handy when you want many more independent substreams than 2^128 without changing design.

Scrambler choice within a family

ScramblerOutputBest for
+sum of two state wordsfloat-only workloads using the top bits; lowest 3 bits have low linear complexity
**rotl(s2·5, 7)·9all-purpose; maximal equidistribution (e.g. .NET/Lua default)
++rotl(s1+s4, k)+s1all-purpose; Rust's SmallRng, Julia's default

All three share the same transition and therefore the same jump constants: streams/substreams behave identically across scramblers of one family.

Practical notes

  • Seeding: seed states must not be all-zero. For hash-like seeds, initialize with splitmix64 outputs (see Vigna's recommendation).
  • The package's jumps are anchored at stream boundaries (Cg ← Bg ← Ig), matching the L'Ecuyer stream model: next_substream! always lands at the same place regardless of how far you consumed in the current substream, which makes scenario replay reproducible.