FastRng/FastRng/Double/MultiThreadedRng.cs

192 lines
8.0 KiB
C#
Raw Normal View History

2020-09-23 19:10:55 +00:00
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
2020-09-26 09:40:01 +00:00
using FastRng.Double.Distributions;
2020-09-23 19:10:55 +00:00
2020-09-26 09:40:01 +00:00
namespace FastRng.Double
2020-09-23 19:10:55 +00:00
{
2020-09-25 22:05:41 +00:00
/// <summary>
/// This class uses the George Marsaglia's MWC algorithm. The algorithm's implementation based loosely on John D.
/// Cook's (johndcook.com) implementation (https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation).
/// Thanks John for your work.
/// </summary>
2020-09-23 19:10:55 +00:00
public sealed class MultiThreadedRng : IRandom
{
#if DEBUG
private const int CAPACITY_RANDOM_NUMBERS_4_SOURCE = 10_000;
#else
private const int CAPACITY_RANDOM_NUMBERS_4_SOURCE = 16_000_000;
#endif
private readonly CancellationTokenSource producerToken = new CancellationTokenSource();
2020-09-25 22:05:41 +00:00
private readonly object syncUintGenerators = new object();
private readonly object syncUniformDistributedDoubleGenerators = new object();
private readonly Thread[] producerRandomUint = new Thread[2];
private readonly Thread[] producerRandomUniformDistributedDouble = new Thread[2];
2020-09-23 19:10:55 +00:00
2020-09-25 22:05:41 +00:00
private uint mW;
private uint mZ;
2020-09-23 19:10:55 +00:00
2020-09-25 22:05:41 +00:00
private readonly Channel<uint> channelRandomUint = Channel.CreateBounded<uint>(new BoundedChannelOptions(CAPACITY_RANDOM_NUMBERS_4_SOURCE)
{
FullMode = BoundedChannelFullMode.Wait,
SingleReader = false,
SingleWriter = false,
});
private readonly Channel<double> channelRandomUniformDistributedDouble = Channel.CreateBounded<double>(new BoundedChannelOptions(CAPACITY_RANDOM_NUMBERS_4_SOURCE)
2020-09-23 19:10:55 +00:00
{
FullMode = BoundedChannelFullMode.Wait,
SingleReader = false,
SingleWriter = false,
});
#region Constructors
public MultiThreadedRng()
{
2020-09-25 22:05:41 +00:00
//
// Initialize the mW and mZ by using
// the system's time.
//
var now = DateTime.Now;
var ticks = now.Ticks;
this.mW = (uint) (ticks >> 16);
this.mZ = (uint) (ticks % 4294967296);
this.StartProducerThreads();
2020-09-23 19:10:55 +00:00
}
2020-09-25 22:05:41 +00:00
public MultiThreadedRng(uint seedU)
2020-09-23 19:10:55 +00:00
{
2020-09-25 22:05:41 +00:00
this.mW = seedU;
this.mZ = 362436069;
this.StartProducerThreads();
}
public MultiThreadedRng(uint seedU, uint seedV)
{
this.mW = seedU;
this.mZ = seedV;
this.StartProducerThreads();
}
private void StartProducerThreads()
{
this.producerRandomUint[0] = new Thread(() => this.RandomProducerUint(this.channelRandomUint.Writer, this.producerToken.Token)) {IsBackground = true};
this.producerRandomUint[1] = new Thread(() => this.RandomProducerUint(this.channelRandomUint.Writer, this.producerToken.Token)) {IsBackground = true};
this.producerRandomUint[0].Start();
this.producerRandomUint[1].Start();
2020-09-23 19:10:55 +00:00
2020-09-25 22:05:41 +00:00
this.producerRandomUniformDistributedDouble[0] = new Thread(() => this.RandomProducerUniformDistributedDouble(this.channelRandomUint.Reader, channelRandomUniformDistributedDouble.Writer, this.producerToken.Token)) {IsBackground = true};
this.producerRandomUniformDistributedDouble[1] = new Thread(() => this.RandomProducerUniformDistributedDouble(this.channelRandomUint.Reader, channelRandomUniformDistributedDouble.Writer, this.producerToken.Token)) {IsBackground = true};
this.producerRandomUniformDistributedDouble[0].Start();
this.producerRandomUniformDistributedDouble[1].Start();
2020-09-23 19:10:55 +00:00
}
#endregion
2020-09-25 22:05:41 +00:00
#region Producers
2020-09-23 19:10:55 +00:00
[ExcludeFromCodeCoverage]
2020-09-25 22:05:41 +00:00
private async void RandomProducerUint(ChannelWriter<uint> channelWriter, CancellationToken cancellationToken)
2020-09-23 19:10:55 +00:00
{
2020-09-25 22:05:41 +00:00
var buffer = new uint[CAPACITY_RANDOM_NUMBERS_4_SOURCE];
2020-09-23 19:10:55 +00:00
while (!cancellationToken.IsCancellationRequested)
{
2020-09-25 22:05:41 +00:00
lock (syncUintGenerators)
2020-09-23 19:10:55 +00:00
{
2020-09-24 18:22:05 +00:00
for (var n = 0; n < buffer.Length && !cancellationToken.IsCancellationRequested; n++)
{
2020-09-25 22:05:41 +00:00
this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16);
this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16);
buffer[n] = (this.mZ << 16) + this.mW;
2020-09-24 18:22:05 +00:00
}
2020-09-23 19:10:55 +00:00
}
for (var n = 0; n < buffer.Length && !cancellationToken.IsCancellationRequested; n++)
await channelWriter.WriteAsync(buffer[n], cancellationToken);
}
}
2020-09-25 22:05:41 +00:00
[ExcludeFromCodeCoverage]
private async void RandomProducerUniformDistributedDouble(ChannelReader<uint> channelReaderUint, ChannelWriter<double> channelWriter, CancellationToken cancellationToken)
{
var buffer = new double[CAPACITY_RANDOM_NUMBERS_4_SOURCE];
var randomUint = new uint[CAPACITY_RANDOM_NUMBERS_4_SOURCE];
while (!cancellationToken.IsCancellationRequested)
{
for (var n = 0; n < randomUint.Length; n++)
randomUint[n] = await channelReaderUint.ReadAsync(cancellationToken);
lock (syncUniformDistributedDoubleGenerators)
for (var n = 0; n < buffer.Length && !cancellationToken.IsCancellationRequested; n++)
buffer[n] = (randomUint[n] + 1.0) * 2.328306435454494e-10; // 2.328 => 1/(2^32 + 2)
for (var n = 0; n < buffer.Length && !cancellationToken.IsCancellationRequested; n++)
await channelWriter.WriteAsync(buffer[n], cancellationToken);
}
}
#endregion
2020-09-23 19:10:55 +00:00
#region Implementing interface
2020-09-26 09:46:54 +00:00
public async ValueTask<double> GetUniform(CancellationToken cancel = default) => await this.channelRandomUniformDistributedDouble.Reader.ReadAsync(cancel);
2020-09-25 22:05:41 +00:00
2020-09-26 09:46:54 +00:00
public async ValueTask<uint> NextNumber(uint rangeStart, uint rangeEnd, IDistribution distribution, CancellationToken cancel = default)
2020-09-23 19:10:55 +00:00
{
2020-09-25 22:05:41 +00:00
if (rangeStart > rangeEnd)
{
var tmp = rangeStart;
rangeStart = rangeEnd;
rangeEnd = tmp;
}
2020-09-23 19:10:55 +00:00
var range = rangeEnd - rangeStart;
2020-09-25 22:05:41 +00:00
distribution.Random = this;
var distributedValue = await distribution.GetDistributedValue(cancel);
return (uint) ((distributedValue * range) + rangeStart);
2020-09-23 19:10:55 +00:00
}
2020-09-26 09:46:54 +00:00
public async ValueTask<ulong> NextNumber(ulong rangeStart, ulong rangeEnd, IDistribution distribution, CancellationToken cancel = default(CancellationToken))
2020-09-23 19:10:55 +00:00
{
2020-09-25 22:05:41 +00:00
if (rangeStart > rangeEnd)
{
var tmp = rangeStart;
rangeStart = rangeEnd;
rangeEnd = tmp;
}
2020-09-23 19:10:55 +00:00
var range = rangeEnd - rangeStart;
2020-09-25 22:05:41 +00:00
distribution.Random = this;
var distributedValue = await distribution.GetDistributedValue(cancel);
return (ulong) ((distributedValue * range) + rangeStart);
2020-09-23 19:10:55 +00:00
}
2020-09-26 10:00:46 +00:00
public async ValueTask<double> NextNumber(double rangeStart, double rangeEnd, IDistribution distribution, CancellationToken cancel = default(CancellationToken))
2020-09-23 19:10:55 +00:00
{
2020-09-25 22:05:41 +00:00
if (rangeStart > rangeEnd)
{
var tmp = rangeStart;
rangeStart = rangeEnd;
rangeEnd = tmp;
}
2020-09-23 19:10:55 +00:00
var range = rangeEnd - rangeStart;
2020-09-25 22:05:41 +00:00
distribution.Random = this;
var distributedValue = await distribution.GetDistributedValue(cancel);
2020-09-26 10:00:46 +00:00
return (distributedValue * range) + rangeStart;
2020-09-23 19:10:55 +00:00
}
2020-09-26 10:00:46 +00:00
public async ValueTask<double> NextNumber(IDistribution distribution, CancellationToken cancel = default) => await this.NextNumber(0.0, 1.0, distribution, cancel);
2020-09-23 19:10:55 +00:00
public void StopProducer() => this.producerToken.Cancel();
#endregion
}
}