Skip to content

Instantly share code, notes, and snippets.

@zpbappi
Created August 28, 2016 10:23
Show Gist options
  • Save zpbappi/af3c6adf987c311a179c22ad96675963 to your computer and use it in GitHub Desktop.
Save zpbappi/af3c6adf987c311a179c22ad96675963 to your computer and use it in GitHub Desktop.
Comparison of XorShift128+, XorShiRo128+ and existing .NET Random class
using System;
using System.IO;
namespace RandomTest
{
class Program
{
private static readonly string BasePath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "random-files");
static void Main(string[] args)
{
if (!Directory.Exists(BasePath))
Directory.CreateDirectory(BasePath);
const int runs = 10000000;
var seed = Environment.TickCount;
var buffer = new byte[runs];
// dotNET Random
GenerateNumbersWithRandom(seed, runs, "dn-next.txt", (w, v) => w.Write(v), g => g.Next());
GenerateNumbersWithRandom(seed, runs, "dn-nextdouble.txt", (w, v) => w.Write(v), g => g.NextDouble());
GenerateNumbersWithRandom(seed, 4, "dn-bytes.txt", (w, v) => w.Write(v), g =>
{
g.NextBytes(buffer);
return buffer;
});
// XorShift128+
GenerateNumbersWithXorShift(seed, runs, "xs-next.txt", (w, v) => w.Write(v), g => g.Next());
GenerateNumbersWithXorShift(seed, runs, "xs-nextdouble.txt", (w, v) => w.Write(v), g => g.NextDouble());
GenerateNumbersWithXorShift(seed, 4, "xs-bytes.txt", (w, v) => w.Write(v), g =>
{
g.NextBytes(buffer);
return buffer;
});
// XorShiRo128+
GenerateNumbersWithXorShiRo(seed, runs, "xsr-next.txt", (w, v) => w.Write(v), g => g.Next());
GenerateNumbersWithXorShiRo(seed, runs, "xsr-nextdouble.txt", (w, v) => w.Write(v), g => g.NextDouble());
GenerateNumbersWithXorShiRo(seed, 4, "xsr-bytes.txt", (w, v) => w.Write(v), g =>
{
g.NextBytes(buffer);
return buffer;
});
}
private static void GenerateNumbersWithRandom<T>(
int seed,
int runs,
string fileName,
Action<BinaryWriter, T> write,
Func<Random, T> method)
{
var filePath = Path.Combine(BasePath, fileName);
var gen = new Random(seed);
using (var writer = new BinaryWriter(File.Create(filePath)))
{
for (var i = 0; i < runs; ++i)
{
write(writer, method(gen));
}
}
}
private static void GenerateNumbersWithXorShift<T>(
int seed,
int runs,
string fileName,
Action<BinaryWriter, T> write,
Func<XorShiftRandom, T> method)
{
var filePath = Path.Combine(BasePath, fileName);
var gen = new XorShiftRandom(seed);
using (var writer = new BinaryWriter(File.Create(filePath)))
{
for (var i = 0; i < runs; ++i)
{
write(writer, method(gen));
}
}
}
private static void GenerateNumbersWithXorShiRo<T>(
int seed,
int runs,
string fileName,
Action<BinaryWriter, T> write,
Func<XorShiRoRandom, T> method)
{
var filePath = Path.Combine(BasePath, fileName);
var gen = new XorShiRoRandom(seed);
using (var writer = new BinaryWriter(File.Create(filePath)))
{
for (var i = 0; i < runs; ++i)
{
write(writer, method(gen));
}
}
}
}
}

This analysis was done as a supporting evidence for the discussion in this issue.

Files create with randomly generated bytes (using NextBytes(byte[]) method) were submitted to CAcert Research Lab. Here is the result, with some unnecessary columns removed:

|Generator |Speed |Upload Size |Entropy (->8) |Birthday Spacing |Matrix Ranks |6x8 Matrix Ranks |Minimum Distance Test |Random Spheres Test |The Sqeeze Test |Overlapping Sums Test |Submission YYYY-MM ---|---|---|---|---|---|---|---|---|---|---|---|--- |dotNET Framework Random class |3000 B/s |30 MiB |7.999994 |0.110870 |0.650 |0.827 |0.511151 |0.507239 |0.939770 |0.229187 |2013-07 XorShift128+ v2 issue#5974 |214147895 B/s |38 MiB |7.999996 |0.012782 |0.669 |0.379 |0.954850 |0.405336 |0.899568 |0.293652 |2016-08 |XorShiRo128+ v2 issue#5974 |192939942 B/s |38 MiB |7.999995 |0.253301 |0.363 |0.345 |0.831364 |0.446184 |0.140312 |0.176870 |2016-08

using System;
namespace RandomTest
{
public class XorShiftRandom
{
private const double NormalizationFactor = 1.0/int.MaxValue;
private readonly ulong[] _state;
private ulong _seed;
private bool _takeMsb = true;
private ulong _currentPartialResult;
public XorShiftRandom(int seed)
{
_seed = (ulong)seed;
_state = new[] {SplitMix64(), SplitMix64()};
}
public virtual int Next()
{
var sample = InternalSample() & int.MaxValue;
return sample == int.MaxValue
? --sample
: sample;
}
public virtual int Next(int minValue, int maxValue)
{
if (minValue > maxValue)
throw new ArgumentOutOfRangeException(nameof(minValue));
var range = (long)maxValue - minValue;
return minValue + (int)(range * NextDouble());
}
public virtual int Next(int maxValue)
{
if (maxValue < 0)
throw new ArgumentOutOfRangeException(nameof(maxValue));
return (int) (NextDouble()*maxValue);
}
public virtual double NextDouble()
{
var sample = Next();
return sample * NormalizationFactor;
}
public virtual void NextBytes(byte[] buffer)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
var tmp = BitConverter.GetBytes(InternalSample());
short index = 0;
for (var i = 0; i < buffer.Length; ++i)
{
if (index == 4)
{
index = 0;
tmp = BitConverter.GetBytes(InternalSample());
}
buffer[i] = tmp[index++];
}
}
private int InternalSample()
{
int sample;
if (_takeMsb)
{
_currentPartialResult = XorShift128Plus();
sample = unchecked((int)(_currentPartialResult >> 32));
}
else
{
sample = unchecked((int)_currentPartialResult);
}
_takeMsb = !_takeMsb;
return sample;
}
private ulong SplitMix64()
{
var z = unchecked(_seed += 0x9E3779B97F4A7C15);
z = unchecked((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9);
z = unchecked((z ^ (z >> 27)) * 0x94D049BB133111EB);
return z ^ (z >> 31);
}
private ulong XorShift128Plus()
{
var s1 = _state[0];
var s0 = _state[1];
var result = s0 + s1;
_state[0] = s0;
s1 ^= s1 << 23;
_state[1] = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5);
return result;
}
}
}
using System;
namespace RandomTest
{
public class XorShiRoRandom
{
private readonly ulong[] _state;
private const double NormalizationFactor = 1.0 / int.MaxValue;
private ulong _seed;
private bool _takeMsb = true;
private ulong _currentPartialResult;
public XorShiRoRandom(int seed)
{
_seed = (ulong)seed;
_state = new[] { SplitMix64(), SplitMix64() };
}
public virtual int Next()
{
var sample = InternalSample() & int.MaxValue;
return sample == int.MaxValue
? --sample
: sample;
}
public virtual int Next(int minValue, int maxValue)
{
if (minValue > maxValue)
throw new ArgumentOutOfRangeException(nameof(minValue));
var range = (long)maxValue - minValue;
return minValue + (int)(range * NextDouble());
}
public virtual int Next(int maxValue)
{
if (maxValue < 0)
throw new ArgumentOutOfRangeException(nameof(maxValue));
return (int)(NextDouble() * maxValue);
}
public virtual double NextDouble()
{
var sample = Next();
return sample * NormalizationFactor;
}
public virtual void NextBytes(byte[] buffer)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
var tmp = BitConverter.GetBytes(InternalSample());
short index = 0;
for (var i = 0; i < buffer.Length; ++i)
{
if (index == 4)
{
index = 0;
tmp = BitConverter.GetBytes(InternalSample());
}
buffer[i] = tmp[index++];
}
}
private int InternalSample()
{
int sample;
if (_takeMsb)
{
_currentPartialResult = XorShiRo128Plus();
sample = unchecked((int)(_currentPartialResult >> 32));
}
else
{
sample = unchecked((int)_currentPartialResult);
}
_takeMsb = !_takeMsb;
return sample;
}
private ulong SplitMix64()
{
var z = unchecked(_seed += 0x9E3779B97F4A7C15);
z = unchecked((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9);
z = unchecked((z ^ (z >> 27)) * 0x94D049BB133111EB);
return z ^ (z >> 31);
}
private static ulong RotateLeft(ulong x, int k)
{
return (x << k) | (x >> (64 - k));
}
private ulong XorShiRo128Plus()
{
var s0 = _state[0];
var s1 = _state[1];
var result = s0 + s1;
s1 ^= s0;
_state[0] = RotateLeft(s0, 55) ^ s1 ^ (s1 << 14);
_state[1] = RotateLeft(s1, 36);
return result;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment