-
Notifications
You must be signed in to change notification settings - Fork 11
Performance Random Performance
State repair for a generator restored from JSON or protobuf happens in its constructor or the shared after-deserialization callback. This includes each generator's state, the shared bit/byte reservoirs and GUID scratch buffer. Repair is not repeated in a draw method, so malformed/default serialized state is repaired before the first draw without adding a guard to every later draw. PhotonSpin's one-time warmup priming also happens there, leaving only its block-boundary check in the draw path.
The ops/s columns are each generator measured on its own, one after another. That answers "how fast is this generator here" and it is the wrong instrument for "which generator is faster": the roster takes minutes to walk, and anything that changed on the machine in between lands on whichever generator was being measured at the time.
The Speed column is measured differently. Every generator is compared against IllusionFlow
(what PRNG.Instance returns) in a fixed ABBABAAB batch, so each of the four readings that make
up a ratio sits next to the reading it is divided by, and both generators occupy the same mean
position in the batch. A drift that is linear across the batch cancels rather than being attributed
to one side. The four raw readings per side are kept, and their spread is what decides whether the
ratio is worth publishing: if the machine moved more than 3% between adjacent cycles, that
generator falls back to the un-paired number and the run says so.
So a Speed bucket is a claim about the generators; an ops/s figure is a claim about this machine on that day.
PractRand streams until something fails and reports the depth: a generator is "clean through 8GB" or it is not. TestU01 SmallCrush runs fifteen fixed statistics and reports a p-value for each. A generator can be clean at 8GB of PractRand and still land a decisive p-value here, so neither stands in for the other.
Both run against the same byte stream, from the same host, so a difference between them is a difference between the batteries rather than between two ways of producing bytes.
Reading a SmallCrush result takes one piece of context: with fifteen statistics, a perfectly good
generator lands one p-value outside [0.001, 0.9990] roughly one run in seven. The threshold that
separates noise from signal is not close: the recorded weak control reports eps (below 1e-300),
while noise sits around 1e-4. Anything below 1e-10 is treated as a failure; anything above it is
reported and ignored. IllusionFlow produced a single 7.2e-4 at the manifest seed and was clean on
two other seeds, which is what that rule is for.
Whole-inventory SmallCrush, 2026-08-26, seed 00010203-0405-0607-0809-0a0b0c0d0e0f, 908 MB and
6.3 s of CPU per generator:
- Every generator rated
Goodor better passed. - Four recorded-weak generators failed decisively, most of their statistics at
eps:LinearCongruentialGenerator,WDoomRandom,WaveSplatRandom,XorShiftRandom. - Three recorded-weak generators passed:
DotNetRandom,SquirrelRandom,SystemRandom. SmallCrush is the shallower instrument, so that is inconclusive rather than a contradiction of their rating.
For statistical batteries, the repository's
Generator~/WallstopStudios.UnityHelpers.RandomQuality host emits a reproducible little-endian byte
stream from an explicit generator, GUID seed and byte count. Long PractRand/TestU01 runs belong in
scheduled reporting with pinned tools and expected weak-generator failures; they are not suitable as
nondeterministic pull-request gates.
The
NextUlong,NextLongandNextDoublecolumns below predate the one-advance-per-64-bit-draw change toBlastCircuitRandom,RomuDuo,SplitMix64,WyRandomandXoshiro256StarStar, which is measured at 2.49x on Unity 6000.4.6f1 (Mono). They refresh the next timeRandomPerformanceTests.Benchmarkruns.
| Random | NextUint (ops/s) | Speed | Quality | Notes |
|---|---|---|---|---|
| LinearCongruentialGenerator | 1,323,700,000 | Fastest | Poor | Numerical Recipes 'quick and dirty' LCG (a=1664525, c=1013904223, m=2^32) returning the raw state, so bit k has period only 2^(k+1); measured linear complexity of bit k is 2^k+1, and bit 0 simply alternates. Cosmetic use only. |
| WaveSplatRandom | 1,283,400,000 | Fastest | Experimental | Single-word chaotic generator; author notes period 2^64 but provides no formal test results, so treat it as experimental. |
| BlastCircuitRandom | 1,054,900,000 | Very Fast | Good | Chaotic ARX mixer rather than a proven statistically optimal generator. Verified here: PractRand 0.95 clean through 8GB, the depth at which SystemRandom fails. |
| SplitMix64 | 1,052,300,000 | Very Fast | Very Good | Well-known SplitMix64 mixer; passes TestU01 BigCrush and PractRand up to large data sizes in literature. Vigna 2014 |
| FlurryBurstRandom | 923,200,000 | Fast | Excellent | Six-word ARX-style generator tuned for all-around use. Verified here: PractRand 0.95 clean through 8GB, the depth at which SystemRandom fails. The author reports TestU01 BigCrush passes; that run cannot be checked because the upstream repository is offline. |
| PcgRandom | 897,900,000 | Fast | Excellent | PCG XSH RR 64/32 variant; passes TestU01 BigCrush and PractRand in published results. O'Neill 2014 |
| XoroShiroRandom | 754,700,000 | Fast | Good | xoroshiro128+, returning the high 32 bits, the half its authors recommend. The discarded low half is linear (bit 0 has linear complexity exactly 128); no output bit of the returned half is. A 64-bit draw costs two state advances, because a + scrambler has no strong 64-bit word to return. Blackman & Vigna 2018 |
| IllusionFlow | 754,500,000 | Fast | Excellent | Five-word rotate/xor/add generator driven by a 32-bit Weyl counter, and the generator PRNG.Instance returns. Verified here: PractRand 0.95 clean through 8GB, the depth at which SystemRandom fails. The author reports 64GB; that run cannot be checked because the upstream repository is offline. |
| RomuDuo | 750,100,000 | Fast | Good | Published romuDuo: the ROMU multiplier with the ROTL(y,36) + ROTL(y,15) - x update. NextUint returns the low 32 bits of the 64-bit word. Throughput figures in this table predate that update rule and have not been re-measured. Overton 2020 |
| StormDropRandom | 705,200,000 | Moderate | Excellent | Large-state ARX generator over a 1024-word (4 KB) ring buffer with two 32-bit control words. Verified here: PractRand 0.95 clean through 8GB, the depth at which SystemRandom fails. The author's own results cannot be checked because the upstream repository is offline. |
| XorShiftRandom | 602,100,000 | Moderate | Fair | Classic 32-bit xorshift; known to fail portions of TestU01 and PractRand, acceptable for lightweight effects only. Marsaglia 2003 |
| WyRandom | 440,500,000 | Slow | Very Good | Wyhash-based generator; published testing shows it clears BigCrush/PractRand with wide seed coverage. Wang Yi 2019 |
| SquirrelRandom | 414,000,000 | Slow | Fair | Hash-based generator built on Squirrel3. Measured: fails PractRand 0.95 FPF-14+6/16 at 1GB, reproducibly across four seeds. Good equidistribution for the table lookups it was designed for; not a general-purpose stream. Squirrel Eiserloh |
| PhotonSpinRandom | 261,100,000 | Very Slow | Excellent | SHISHUA-inspired generator. Verified here: PractRand 0.95 clean through 8GB, the depth at which SystemRandom fails. The author reports 128GB; that run cannot be checked because the upstream repository is offline. |
| UnityRandom | 87,600,000 | Very Slow | Fair | Mirrors UnityEngine.Random, documented by Unity as Xorshift 128; suitable for legacy compatibility but not high-stakes simulation. UnityEngine.Random |
| SystemRandom | 64,700,000 | Very Slow | Poor | Reimplements the classic .NET System.Random algorithm (Knuth subtractive lagged-Fibonacci, mod 2^31-1) so its sequence is serializable and stable across runtimes. Fails modern statistical batteries. System.Random considered harmful |
| DotNetRandom | 55,900,000 | Very Slow | Poor | Wraps System.Random, which on Mono and for seeded .NET Core is Knuth's subtractive lagged-Fibonacci generator (mod 2^31-1), not an LCG. The sequence is runtime-dependent, so do not rely on it for cross-platform determinism. System.Random considered harmful |
| Random | NextBool | Next | NextUint | NextFloat | NextDouble | NextUint (Range) | NextInt (Range) |
|---|---|---|---|---|---|---|---|
| LinearCongruentialGenerator | 785,200,000 | 538,800,000 | 1,323,700,000 | 184,300,000 | 296,300,000 | 582,900,000 | 498,700,000 |
| WaveSplatRandom | 787,300,000 | 528,500,000 | 1,283,400,000 | 184,300,000 | 297,900,000 | 529,000,000 | 458,200,000 |
| BlastCircuitRandom | 788,200,000 | 537,400,000 | 1,054,900,000 | 183,800,000 | 293,200,000 | 479,700,000 | 421,200,000 |
| SplitMix64 | 795,900,000 | 537,200,000 | 1,052,300,000 | 184,400,000 | 297,700,000 | 482,500,000 | 439,100,000 |
| FlurryBurstRandom | 767,800,000 | 526,500,000 | 923,200,000 | 182,300,000 | 293,100,000 | 449,800,000 | 404,100,000 |
| PcgRandom | 249,800,000 | 527,300,000 | 897,900,000 | 181,000,000 | 274,700,000 | 447,700,000 | 405,000,000 |
| XoroShiroRandom | 761,000,000 | 359,000,000 | 754,700,000 | 157,900,000 | 192,400,000 | 422,100,000 | 376,900,000 |
| IllusionFlow | 779,300,000 | 529,200,000 | 754,500,000 | 183,000,000 | 281,600,000 | 439,200,000 | 390,000,000 |
| RomuDuo | 784,600,000 | 359,400,000 | 750,100,000 | 156,100,000 | 188,700,000 | 437,700,000 | 384,600,000 |
| StormDropRandom | 776,600,000 | 523,600,000 | 705,200,000 | 180,600,000 | 252,400,000 | 393,500,000 | 356,700,000 |
| XorShiftRandom | 783,300,000 | 534,100,000 | 602,100,000 | 184,100,000 | 283,200,000 | 474,500,000 | 383,500,000 |
| WyRandom | 749,500,000 | 363,000,000 | 440,500,000 | 159,000,000 | 185,300,000 | 290,000,000 | 277,800,000 |
| SquirrelRandom | 756,300,000 | 382,700,000 | 414,000,000 | 158,800,000 | 197,800,000 | 355,500,000 | 309,500,000 |
| PhotonSpinRandom | 713,300,000 | 220,800,000 | 261,100,000 | 119,900,000 | 115,800,000 | 217,600,000 | 214,200,000 |
| UnityRandom | 628,900,000 | 76,700,000 | 87,600,000 | 59,700,000 | 38,800,000 | 81,800,000 | 81,900,000 |
| SystemRandom | 146,200,000 | 145,700,000 | 64,700,000 | 131,300,000 | 138,600,000 | 58,700,000 | 57,800,000 |
| DotNetRandom | 544,600,000 | 53,100,000 | 55,900,000 | 44,700,000 | 26,700,000 | 53,400,000 | 51,700,000 |
The tables above are rewritten only by a benchmark run, so a generator added since the last one is
absent until the .github/workflows/unity-benchmarks.yml workflow next runs.
Absence here says nothing about quality: statistical standing is measured separately, by the
bit-plane linearity gate on every pull request and by the scheduled PractRand battery. See
Random Generators for the current ratings.
The battery runs both stream widths. NextUlong is no longer NextUint rearranged: five
generators answer a 64-bit draw from one raw word, so half of it reaches a caller only through
NextDouble, NextLong and NextUlong(max) and appears in no 32-bit draw. Even the generators
that do build NextUlong from two NextUint draws pack them high-word-first and write
little-endian, so their 64-bit stream is the 32-bit one with each adjacent word pair swapped.
SystemRandom is the proof that this is not a redundant measurement: it fails the 32-bit battery at
exactly 8GB and is clean through 8GB at 64-bit. Every "clean through 8GB" above is the 32-bit
figure; the 64-bit outcomes are recorded per generator in
scripts/random-quality/expected-outcomes.json.
Run RandomPerformanceTests.Benchmark from Unity's Test Runner, or let the weekly Unity Benchmarks workflow do it. Both rewrite the tables in place.
📦 Unity Helpers | 📖 Documentation | 🐛 Issues | 📜 MIT License
- Inspector Button
- Inspector Conditional Display
- Inspector Grouping Attributes
- Inspector Inline Editor
- Inspector Overview
- Inspector Selection Attributes
- Inspector Settings
- Inspector Validation Attributes
- Utility Components
- Visual Components
- Data Structures
- Helper Utilities
- Math And Extensions
- Pooling Guide
- Random Generators
- Reflection Helpers
- Singletons