Skip to content

Commit a43e89d

Browse files
committed
Add MWC256, tests, benchmarks
1 parent a538376 commit a43e89d

File tree

15 files changed

+334
-58
lines changed

15 files changed

+334
-58
lines changed

README.md

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,54 +17,24 @@ Sources:
1717
* No abstraction - interfaces etc, makes it easier to not invalidate the above
1818
* Vectorization where possible - beneficial if PRNG is on your hotpath
1919

20+
### Running tests
21+
22+
```pwsh
23+
dotnet test -c Release --logger:"console;verbosity=detailed"
24+
```
25+
2026
### Initial benchmarks
2127

2228
The benchmarks measure generation of `double`s.
2329
Iterations = `double`s per op.
2430

2531
#### With hardware counters
2632

27-
``` ini
28-
29-
BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1555/22H2/2022Update/SunValley2)
30-
AMD Ryzen 5 5600X, 1 CPU, 12 logical and 6 physical cores
31-
.NET SDK=7.0.300-preview.23122.5
32-
[Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
33-
DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
34-
35-
36-
```
37-
| Method | Iterations | Mean | Error | StdDev | Ratio | RatioSD | Rank | BranchInstructions/Op | CacheMisses/Op | TotalCycles/Op | BranchMispredictions/Op | Allocated | Alloc Ratio |
38-
|-------------------- |----------- |---------:|--------:|--------:|-------------:|--------:|-----:|----------------------:|---------------:|---------------:|------------------------:|----------:|------------:|
39-
| SystemRandomGen | 100000 | 423.5 μs | 7.62 μs | 7.13 μs | baseline | | 4 | 603,262 | 482 | 1,293,860 | 716 | - | NA |
40-
| Xoroshiro128PlusGen | 100000 | 305.8 μs | 2.99 μs | 2.80 μs | 1.38x faster | 0.03x | 3 | 107,806 | 261 | 999,642 | 392 | - | NA |
41-
| Xoshiro256PlusGen | 100000 | 231.2 μs | 2.85 μs | 2.67 μs | 1.83x faster | 0.03x | 2 | 105,846 | 219 | 750,974 | 308 | - | NA |
42-
| ShishuaGen | 100000 | 185.7 μs | 1.32 μs | 1.24 μs | 2.28x faster | 0.05x | 1 | 437,648 | 640 | 529,914 | 346 | - | NA |
43-
33+
![With hardware counters](/img/perf-hardwarecounters.png "With hardware counters")
4434

4535
#### Scaling iterations
4636

4737
There is likely overhead in capturing hardware counters, so these should be more "correct"
4838

49-
``` ini
50-
51-
BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1555/22H2/2022Update/SunValley2)
52-
AMD Ryzen 5 5600X, 1 CPU, 12 logical and 6 physical cores
53-
.NET SDK=7.0.300-preview.23122.5
54-
[Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
55-
DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
56-
57-
58-
```
59-
| Method | Iterations | Mean | Error | StdDev | Ratio | RatioSD | Rank |
60-
|-------------------- |----------- |-----------:|---------:|---------:|-------------:|--------:|-----:|
61-
| SystemRandomGen | 100000 | 282.1 μs | 2.04 μs | 1.70 μs | baseline | | 4 |
62-
| Xoroshiro128PlusGen | 100000 | 213.2 μs | 0.45 μs | 0.37 μs | 1.32x faster | 0.01x | 3 |
63-
| Xoshiro256PlusGen | 100000 | 158.7 μs | 1.18 μs | 1.11 μs | 1.78x faster | 0.02x | 2 |
64-
| ShishuaGen | 100000 | 103.5 μs | 0.54 μs | 0.45 μs | 2.73x faster | 0.02x | 1 |
65-
| | | | | | | | |
66-
| SystemRandomGen | 1000000 | 2,825.6 μs | 18.07 μs | 16.90 μs | baseline | | 4 |
67-
| Xoroshiro128PlusGen | 1000000 | 2,141.1 μs | 15.47 μs | 14.47 μs | 1.32x faster | 0.01x | 3 |
68-
| Xoshiro256PlusGen | 1000000 | 1,583.3 μs | 7.22 μs | 6.40 μs | 1.79x faster | 0.01x | 2 |
69-
| ShishuaGen | 1000000 | 1,030.8 μs | 8.83 μs | 8.26 μs | 2.74x faster | 0.03x | 1 |
39+
![Scaling iterations](/img/perf-scaling.png "Scaling iterations")
7040

benchmark/Fast.PRNGs.Benchmarks/PRNGs.cs renamed to benchmark/Fast.PRNGs.Benchmarks/PRNGsScaling.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
namespace Fast.PRNGs.Benchmarks;
22

33
[Config(typeof(Config))]
4-
public class PRNGs
4+
public class PRNGsScaling
55
{
66
private Random _random;
77
private Shishua _shishua;
88
private Xoroshiro128Plus _xoroshiro128plus;
99
private Xoshiro256Plus _xoshiro256plus;
10+
private MWC256 _mwc256;
1011

1112
[Params(100_000, 1_000_000)]
1213
public int Iterations { get; set; }
@@ -18,6 +19,7 @@ public void Setup()
1819
_shishua = Shishua.Create();
1920
_xoroshiro128plus = Xoroshiro128Plus.Create();
2021
_xoshiro256plus = Xoshiro256Plus.Create();
22+
_mwc256 = MWC256.Create();
2123
}
2224

2325
[GlobalCleanup]
@@ -62,20 +64,22 @@ public double Xoshiro256PlusGen()
6264
return default;
6365
}
6466

67+
[Benchmark]
68+
public double MWC256Gen()
69+
{
70+
for (int i = 0; i < Iterations; i++)
71+
_ = _mwc256.NextDouble();
72+
73+
return default;
74+
}
75+
6576
private sealed class Config : ManualConfig
6677
{
6778
public Config()
6879
{
6980
this.SummaryStyle = SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
70-
//this.AddDiagnoser(MemoryDiagnoser.Default);
7181
this.AddColumn(RankColumn.Arabic);
7282
this.Orderer = new DefaultOrderer(SummaryOrderPolicy.SlowestToFastest, MethodOrderPolicy.Declared);
73-
//this.AddHardwareCounters(
74-
// HardwareCounter.BranchInstructions,
75-
// HardwareCounter.BranchMispredictions,
76-
// HardwareCounter.CacheMisses,
77-
// HardwareCounter.TotalCycles
78-
//);
7983
}
8084
}
8185
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
namespace Fast.PRNGs.Benchmarks;
2+
3+
[Config(typeof(Config))]
4+
public class PRNGsWithCounters
5+
{
6+
private Random _random;
7+
private Shishua _shishua;
8+
private Xoroshiro128Plus _xoroshiro128plus;
9+
private Xoshiro256Plus _xoshiro256plus;
10+
private MWC256 _mwc256;
11+
12+
[Params(100_000)]
13+
public int Iterations { get; set; }
14+
15+
[GlobalSetup]
16+
public void Setup()
17+
{
18+
_random = new Random();
19+
_shishua = Shishua.Create();
20+
_xoroshiro128plus = Xoroshiro128Plus.Create();
21+
_xoshiro256plus = Xoshiro256Plus.Create();
22+
_mwc256 = MWC256.Create();
23+
}
24+
25+
[GlobalCleanup]
26+
public void Cleanup()
27+
{
28+
_shishua.Dispose();
29+
}
30+
31+
[Benchmark(Baseline = true)]
32+
public double SystemRandomGen()
33+
{
34+
for (int i = 0; i < Iterations; i++)
35+
_ = _random.NextDouble();
36+
37+
return default;
38+
}
39+
40+
[Benchmark]
41+
public double ShishuaGen()
42+
{
43+
for (int i = 0; i < Iterations; i++)
44+
_ = _shishua.NextDouble();
45+
46+
return default;
47+
}
48+
49+
[Benchmark]
50+
public double Xoroshiro128PlusGen()
51+
{
52+
for (int i = 0; i < Iterations; i++)
53+
_ = _xoroshiro128plus.NextDouble();
54+
55+
return default;
56+
}
57+
58+
[Benchmark]
59+
public double Xoshiro256PlusGen()
60+
{
61+
for (int i = 0; i < Iterations; i++)
62+
_ = _xoshiro256plus.NextDouble();
63+
64+
return default;
65+
}
66+
67+
[Benchmark]
68+
public double MWC256Gen()
69+
{
70+
for (int i = 0; i < Iterations; i++)
71+
_ = _mwc256.NextDouble();
72+
73+
return default;
74+
}
75+
76+
private sealed class Config : ManualConfig
77+
{
78+
public Config()
79+
{
80+
this.SummaryStyle = SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
81+
this.AddDiagnoser(MemoryDiagnoser.Default);
82+
this.AddColumn(RankColumn.Arabic);
83+
this.Orderer = new DefaultOrderer(SummaryOrderPolicy.SlowestToFastest, MethodOrderPolicy.Declared);
84+
this.AddHardwareCounters(
85+
HardwareCounter.BranchInstructions,
86+
HardwareCounter.BranchMispredictions,
87+
HardwareCounter.CacheMisses,
88+
HardwareCounter.TotalCycles
89+
);
90+
}
91+
}
92+
}

img/perf-hardwarecounters.png

81.3 KB
Loading

img/perf-scaling.png

86.1 KB
Loading

src/Fast.PRNGs/Common.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ internal static ulong Rotl(ulong x, int k)
1616
}
1717

1818
[MethodImpl(MethodImplOptions.AggressiveInlining)]
19-
internal static uint NextUInt(this Random random)
19+
internal static ulong NextULong(this Random random)
2020
{
21-
return (uint)random.Next();
21+
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
22+
return random.NextULong(bytes);
2223
}
2324

2425
[MethodImpl(MethodImplOptions.AggressiveInlining)]
25-
internal static ulong NextULong(this Random random)
26+
internal static ulong NextULong(this Random random, Span<byte> bytes)
2627
{
27-
return ((((ulong)random.NextUInt()) << 32) + random.NextUInt());
28+
random.NextBytes(bytes);
29+
return Unsafe.ReadUnaligned<ulong>(ref bytes[0]);
2830
}
2931
}

src/Fast.PRNGs/Fast.PRNGs.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
</Description>
1919
</PropertyGroup>
2020

21+
<ItemGroup>
22+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
23+
<_Parameter1>Fast.PRNGs.Tests</_Parameter1>
24+
</AssemblyAttribute>
25+
</ItemGroup>
26+
2127
<ItemGroup>
2228
<PackageReference Include="Fody" Version="6.6.4">
2329
<PrivateAssets>all</PrivateAssets>

src/Fast.PRNGs/MWC256.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Runtime.CompilerServices;
2+
using System.Runtime.InteropServices;
3+
using static Fast.PRNGs.Common;
4+
5+
namespace Fast.PRNGs;
6+
7+
/// <summary>
8+
/// Implementation of the MWC 256 PRNG
9+
/// Ported from: https://prng.di.unimi.it/MWC256.c
10+
/// </summary>
11+
[StructLayout(LayoutKind.Sequential)]
12+
public struct MWC256
13+
{
14+
private const ulong MWC_A3 = 0xfff62cf2ccc0cdaf;
15+
16+
private ulong _x, _y, _z, _c;
17+
18+
private MWC256(ulong x, ulong y, ulong z, ulong c)
19+
{
20+
_x = x;
21+
_y = y;
22+
_z = z;
23+
_c = c;
24+
}
25+
26+
public static MWC256 Create()
27+
{
28+
var seedGenerator = Random.Shared;
29+
return new MWC256(
30+
seedGenerator.NextULong(),
31+
seedGenerator.NextULong(),
32+
seedGenerator.NextULong(),
33+
seedGenerator.NextULong() % (MWC_A3 - 1)
34+
);
35+
}
36+
37+
public static MWC256 Create(Random seedGenerator)
38+
{
39+
return new MWC256(
40+
seedGenerator.NextULong(),
41+
seedGenerator.NextULong(),
42+
seedGenerator.NextULong(),
43+
seedGenerator.NextULong() % (MWC_A3 - 1)
44+
);
45+
}
46+
47+
public static MWC256 Create(ReadOnlySpan<byte> seedBytes)
48+
{
49+
if (seedBytes.Length != 32)
50+
throw new ArgumentException("Seed bytes should be of length 32, got: " + seedBytes.Length);
51+
52+
return new MWC256(
53+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 0),
54+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 1),
55+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 2),
56+
Unsafe.Add(ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(seedBytes)), 3) % (MWC_A3 - 1)
57+
);
58+
}
59+
60+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61+
private ulong NextInternal()
62+
{
63+
var result = _z;
64+
UInt128 t = MWC_A3 * (UInt128)_x + _c;
65+
_x = _y;
66+
_y = _z;
67+
_z = (ulong)t;
68+
_c = (ulong)(t >> 64);
69+
return result;
70+
}
71+
72+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
73+
public int Next()
74+
{
75+
return (int)(NextInternal() >> 32);
76+
}
77+
78+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
79+
public double NextDouble()
80+
{
81+
return (NextInternal() & DoubleMask) * Norm53;
82+
}
83+
84+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
85+
public float NextFloat()
86+
{
87+
return (NextInternal() & FloatMask) * Norm24;
88+
}
89+
}

src/Fast.PRNGs/Xoshiro256Plus.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ public static Xoshiro256Plus Create()
3434
public static Xoshiro256Plus Create(Random seedGenerator)
3535
{
3636
return new Xoshiro256Plus(
37-
seedGenerator.NextUInt(),
38-
seedGenerator.NextUInt(),
39-
seedGenerator.NextUInt(),
40-
seedGenerator.NextUInt()
37+
seedGenerator.NextULong(),
38+
seedGenerator.NextULong(),
39+
seedGenerator.NextULong(),
40+
seedGenerator.NextULong()
4141
);
4242
}
4343

test/Fast.PRNGs.Tests/Fast.PRNGs.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
1616
<PackageReference Include="Plotly.NET" Version="4.0.0" />
1717
<PackageReference Include="Plotly.NET.CSharp" Version="0.10.0" />
18+
<PackageReference Include="Accord.Statistics" Version="3.8.0" />
1819
</ItemGroup>
1920

2021
<ItemGroup>

0 commit comments

Comments
 (0)