Skip to content

Commit 4a7ab18

Browse files
committed
Updates
1 parent 7e8d87b commit 4a7ab18

File tree

9 files changed

+224
-58
lines changed

9 files changed

+224
-58
lines changed

benchmark/Fast.PRNGs.Benchmarks/PRNGs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class PRNGs
88
private Xoroshiro128Plus _xoroshiro128plus;
99
private Xoshiro256Plus _xoshiro256plus;
1010

11-
[Params(100_000, 1_000_000)]
11+
[Params(100_000)]
1212
public int Iterations { get; set; }
1313

1414
[GlobalSetup]

src/Fast.PRNGs/Shishua.cs

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Fast.PRNGs;
1616
/// This uses vectorization from the AVX2 instructions
1717
/// </summary>
1818
[StructLayout(LayoutKind.Sequential)]
19-
unsafe public struct Shishua : IDisposable
19+
public readonly struct Shishua : IDisposable
2020
{
2121
private static Span<ulong> Phi => new ulong[]
2222
{
@@ -25,39 +25,65 @@ unsafe public struct Shishua : IDisposable
2525
0xF06AD7AE9717877E, 0x85839D6EFFBD7DC6, 0x64D325D1C5371682, 0xCADD0CCCFDFFBBE1,
2626
0x626E33B8D04B4331, 0xBBF73C790D94F79D, 0x471C4AB3ED3D82A5, 0xFEC507705E4AE6E5,
2727
};
28+
2829
private const int BufferSize = 1 << 17;
2930

30-
private readonly void* _state;
31+
private readonly nuint _state;
32+
33+
public static bool IsSupported => Avx2.IsSupported;
3134

32-
private ref BufferedState State
35+
unsafe private ref BufferedState State
3336
{
3437
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3538
get
3639
{
37-
return ref Unsafe.AsRef<BufferedState>(_state);
40+
return ref Unsafe.AsRef<BufferedState>(_state.ToPointer());
3841
}
3942
}
4043

41-
private Shishua(Random? seedGenerator = null)
44+
private Shishua(ref Seed seed)
45+
{
46+
_state = AllocateState(ref seed);
47+
}
48+
49+
public static Shishua Create()
50+
{
51+
ThrowIfNotSupported();
52+
53+
Seed seed = default;
54+
var seedGenerator = Random.Shared;
55+
seedGenerator.NextBytes(MemoryMarshal.AsBytes(seed.Span));
56+
return new Shishua(ref seed);
57+
}
58+
59+
public static Shishua Create(Random seedGenerator)
4260
{
61+
ThrowIfNotSupported();
62+
4363
Seed seed = default;
44-
seedGenerator ??= Random.Shared;
4564
seedGenerator.NextBytes(MemoryMarshal.AsBytes(seed.Span));
65+
return new Shishua(ref seed);
66+
}
67+
68+
public static Shishua Create(ReadOnlySpan<byte> seedBytes)
69+
{
70+
ThrowIfNotSupported();
71+
72+
if (seedBytes.Length != 32)
73+
throw new ArgumentException("Seed bytes should be of length 32, got: " + seedBytes.Length);
4674

47-
_state = AllocateState(seed);
75+
Seed seed = default;
76+
seedBytes.CopyTo(MemoryMarshal.AsBytes(seed.Span));
77+
return new Shishua(ref seed);
4878
}
4979

50-
static Shishua()
80+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
81+
private static void ThrowIfNotSupported()
5182
{
5283
if (!Avx2.IsSupported)
53-
{
54-
throw new InvalidProgramException("Need access to AVX2 instruction to run this module");
55-
}
84+
throw new InvalidOperationException("Need access to AVX2 instruction to run the Shishua PRNG");
5685
}
5786

58-
public static Shishua Create(Random? seedGenerator = null) =>
59-
new Shishua(seedGenerator);
60-
6187
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6288
private ulong NextInternal()
6389
{
@@ -97,17 +123,17 @@ public void Dispose()
97123
FreeState();
98124
}
99125

100-
private void* AllocateState(Seed seed)
126+
unsafe private nuint AllocateState(ref Seed seed)
101127
{
102-
var ptr = NativeMemory.AlignedAlloc((nuint)Marshal.SizeOf<BufferedState>(), 128);
128+
void* ptr = NativeMemory.AlignedAlloc((nuint)Marshal.SizeOf<BufferedState>(), 128);
103129
ref BufferedState bufferedState = ref Unsafe.AsRef<BufferedState>(ptr);
104130

105-
InitState(ref bufferedState.State, seed);
131+
InitState(ref bufferedState.State, ref seed);
106132
FillBuffer(ref bufferedState);
107-
return ptr;
133+
return (nuint)ptr;
108134
}
109135

110-
private void InitState(ref RawState state, Seed seed)
136+
private void InitState(ref RawState state, ref Seed seed)
111137
{
112138
state = default;
113139

@@ -123,20 +149,22 @@ private void InitState(ref RawState state, Seed seed)
123149

124150
for (int i = 0; i < rounds; i++)
125151
{
126-
PrngGen(ref state, buf, 128 * steps);
152+
PrngGen(ref state, buf);
127153
state.State[0] = state.Output[3]; state.State[1] = state.Output[2];
128154
state.State[2] = state.Output[1]; state.State[3] = state.Output[0];
129155
}
130156
}
131157

132158
private void FillBuffer(ref BufferedState bufferedState)
133159
{
134-
PrngGen(ref bufferedState.State, bufferedState.Buffer, BufferSize);
160+
PrngGen(ref bufferedState.State, bufferedState.Buffer);
135161
bufferedState.BufferIndex = 0;
136162
}
137163

138-
private void PrngGen(ref RawState state, Span<byte> buffer, nint size)
164+
unsafe private void PrngGen(ref RawState state, Span<byte> buffer)
139165
{
166+
var size = buffer.Length;
167+
140168
__m256i
141169
o0 = state.Output[0], o1 = state.Output[1],
142170
o2 = state.Output[2], o3 = state.Output[3],
@@ -184,13 +212,13 @@ private void PrngGen(ref RawState state, Span<byte> buffer, nint size)
184212
state.Counter = counter;
185213
}
186214

187-
private void FreeState()
215+
unsafe private void FreeState()
188216
{
189-
NativeMemory.AlignedFree(_state);
217+
NativeMemory.AlignedFree(_state.ToPointer());
190218
}
191219

192220
[StructLayout(LayoutKind.Sequential)]
193-
private struct BufferedState
221+
unsafe private struct BufferedState
194222
{
195223
public RawState State;
196224
private fixed byte _buffer[BufferSize];
@@ -219,7 +247,7 @@ private struct RawState
219247
}
220248

221249
[StructLayout(LayoutKind.Sequential)]
222-
private struct Seed
250+
unsafe private struct Seed
223251
{
224252
private fixed ulong _value[4];
225253

src/Fast.PRNGs/Splitmix64.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Runtime.CompilerServices;
2+
using static Fast.PRNGs.Common;
23

34
namespace Fast.PRNGs;
45

@@ -25,4 +26,16 @@ public ulong Next()
2526
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
2627
return z ^ (z >> 31);
2728
}
29+
30+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
31+
public double NextDouble()
32+
{
33+
return (Next() & DoubleMask) * Norm53;
34+
}
35+
36+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
37+
public float NextFloat()
38+
{
39+
return (Next() & FloatMask) * Norm24;
40+
}
2841
}

src/Fast.PRNGs/Xoroshiro128Plus.cs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,11 @@ public struct Xoroshiro128Plus
2121

2222
private Xoroshiro128Plus(Random? seedGenerator = null)
2323
{
24-
if (seedGenerator is not null)
25-
{
26-
const int min = int.MinValue;
27-
const int max = int.MaxValue;
28-
_state0 = (ulong)seedGenerator.Next(min, max) << 32 | (uint)seedGenerator.Next(min, max);
29-
_state1 = (ulong)seedGenerator.Next(min, max) << 32 | (uint)seedGenerator.Next(min, max);
30-
}
31-
else
32-
{
33-
Unsafe.SkipInit(out ulong rng1);
34-
Unsafe.SkipInit(out uint rng2);
35-
Unsafe.SkipInit(out ulong rng3);
36-
Unsafe.SkipInit(out uint rng4);
37-
38-
_state0 = rng1 << 32 | rng2;
39-
_state1 = rng3 << 32 | rng4;
40-
}
24+
seedGenerator ??= Random.Shared;
25+
const int min = int.MinValue;
26+
const int max = int.MaxValue;
27+
_state0 = (ulong)seedGenerator.Next(min, max) << 32 | (uint)seedGenerator.Next(min, max);
28+
_state1 = (ulong)seedGenerator.Next(min, max) << 32 | (uint)seedGenerator.Next(min, max);
4129
}
4230

4331
public static Xoroshiro128Plus Create(Random? seedGenerator = null) =>

src/Fast.PRNGs/Xoshiro256Plus.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,35 @@ private Xoshiro256Plus(ulong state0, ulong state1, ulong state2, ulong state3)
2323
public static Xoshiro256Plus Create()
2424
{
2525
var seedGenerator = Splitmix64.Create();
26-
return new Xoshiro256Plus(seedGenerator.Next(), seedGenerator.Next(), seedGenerator.Next(), seedGenerator.Next());
26+
return new Xoshiro256Plus(
27+
seedGenerator.Next(),
28+
seedGenerator.Next(),
29+
seedGenerator.Next(),
30+
seedGenerator.Next()
31+
);
32+
}
33+
34+
public static Xoshiro256Plus Create(Random seedGenerator)
35+
{
36+
return new Xoshiro256Plus(
37+
seedGenerator.NextUInt(),
38+
seedGenerator.NextUInt(),
39+
seedGenerator.NextUInt(),
40+
seedGenerator.NextUInt()
41+
);
42+
}
43+
44+
public static Xoshiro256Plus Create(ReadOnlySpan<byte> seedBytes)
45+
{
46+
if (seedBytes.Length != 32)
47+
throw new ArgumentException("Seed bytes should be of length 32, got: " + seedBytes.Length);
48+
49+
return new Xoshiro256Plus(
50+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 0),
51+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 1),
52+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 2),
53+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 3)
54+
);
2755
}
2856

2957
[MethodImpl(MethodImplOptions.AggressiveInlining)]

test/Fast.PRNGs.Tests/ShishuaTests.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using MathNet.Numerics.Distributions;
2-
using Plotly.NET.CSharp;
1+
using Plotly.NET.CSharp;
32
using System.Runtime.CompilerServices;
4-
using Config = Plotly.NET.Config;
5-
using GenericChartExtensions = Plotly.NET.GenericChartExtensions;
63

74
namespace Fast.PRNGs.Tests;
85

@@ -45,6 +42,31 @@ public void DoubleDistributionTest()
4542
chart.SaveHtml("shishua.html");
4643
}
4744

45+
public void InitFromNothing()
46+
{
47+
using var _ = Shishua.Create();
48+
}
49+
50+
public void InitFromNew()
51+
{
52+
using var _ = Shishua.Create(new Random());
53+
}
54+
55+
public void InitFromBytes()
56+
{
57+
Span<byte> seedBytes = stackalloc byte[32];
58+
Random.Shared.NextBytes(seedBytes);
59+
using var _ = Shishua.Create(seedBytes);
60+
}
61+
62+
public void FailsWhenGivenWrongSizeSeed()
63+
{
64+
Assert.Throws<ArgumentException>(() => {
65+
Span<byte> seedBytes = stackalloc byte[33];
66+
using var _ = Shishua.Create(seedBytes);
67+
});
68+
}
69+
4870
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
4971
private static void AssertInRange(double value)
5072
{
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Plotly.NET.CSharp;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Fast.PRNGs.Tests;
5+
6+
public sealed class Splitmix64Tests
7+
{
8+
public void DoubleDistributionTest()
9+
{
10+
var baselinePrng = new Random();
11+
var prng = Splitmix64.Create();
12+
13+
const int iterations = 10_000_000;
14+
var baselineValues = new double[iterations];
15+
var values = new double[iterations];
16+
for (int i = 0; i < values.Length; i++)
17+
{
18+
var baselineValue = baselinePrng.NextDouble();
19+
var value = prng.NextDouble();
20+
21+
AssertInRange(baselineValue);
22+
AssertInRange(value);
23+
24+
baselineValues[i] = baselineValue;
25+
values[i] = value;
26+
}
27+
28+
var baselineLabel = "Baseline (System.Random)";
29+
var prngLabel = "Splitmix64+";
30+
31+
var baselineChart = Chart.Histogram<double, double, string>(
32+
X: baselineValues,
33+
Name: baselineLabel,
34+
Text: baselineLabel
35+
);
36+
var prngChart = Chart.Histogram<double, double, string>(
37+
X: values,
38+
Name: prngLabel,
39+
Text: prngLabel
40+
);
41+
var chart = Chart.Combine(new []{ baselineChart, prngChart });
42+
chart.SaveHtml("splitmix64+.html");
43+
}
44+
45+
46+
public void InitFromNothing()
47+
{
48+
var _ = Splitmix64.Create();
49+
}
50+
51+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
52+
private static void AssertInRange(double value)
53+
{
54+
Assert.True(value >= 0d);
55+
Assert.True(value < 1.0d);
56+
}
57+
}

test/Fast.PRNGs.Tests/Xoroshiro128PlusTests.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using MathNet.Numerics.Distributions;
2-
using Plotly.NET.CSharp;
1+
using Plotly.NET.CSharp;
32
using System.Runtime.CompilerServices;
4-
using Config = Plotly.NET.Config;
5-
using GenericChartExtensions = Plotly.NET.GenericChartExtensions;
63

74
namespace Fast.PRNGs.Tests;
85

@@ -45,6 +42,16 @@ public void DoubleDistributionTest()
4542
chart.SaveHtml("xoroshiro128+.html");
4643
}
4744

45+
public void InitFromNothing()
46+
{
47+
var _ = Xoroshiro128Plus.Create();
48+
}
49+
50+
public void InitFromNew()
51+
{
52+
var _ = Xoroshiro128Plus.Create(new Random());
53+
}
54+
4855
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
4956
private static void AssertInRange(double value)
5057
{

0 commit comments

Comments
 (0)