I need a high performance random number generator that is thread-safe. I need only random bytes in the value type (which is ulong for now), not within ranges. I've used the C# built-in Random class, but it was kind of slow and not thread-safe.
Later I moved to XORShift functions that actually works very fine, but to achieve thread-safeness I need to put the calculation in lock, and that degrades the performance drastically.
What I'm using to generate a random ulong is the following:
public class Rand
{
ulong seed = 0;
object lockObj = new object();
public Rand()
{
unchecked
{
seed = (ulong)DateTime.Now.Ticks;
}
}
public Rand(ulong seed)
{
this.seed = seed;
}
public ulong GetULong()
{
unchecked
{
lock (lockObj)
{
ulong t = 0;
t = seed;
t ^= t >> 12;
t ^= t << 25;
t ^= t >> 27;
seed = t;
return t * 0x2545F4914F6CDD1D;
}
}
}
}
This works fine and fast, but locking makes it take about 1-2us if it is called from 200 concurrent threads, otherwise calculation finishes under 100ns.
If I remove locking there is a chance two threads take the same seed and will calculate the same random which is not good for my purposes. If I'm removing the ulong t declaration and work directly on the seed then there will be a very little chance to generate the same random for two concurrent calls, but there is also a chance the value will be shifted out from the value range, like t << 25 will be called many times in a row by different threads without carrying the rotation it will become just simply 0.
I think the proper way would be if there is a shared value that may be changed by any concurrent call and work with that value in the calculation methods, since these values are atomic (at least withing CPU cores) it is not a problem if many calculations are using it in the same time, but that is a problem if this value shifts out from the bitrange.
Is there any good solution to solve this problem? I'd be thankful for any help.
Edit: Ok, I've forgot to mention I have no control over the threads, because async tasks are calling this function, so threads are coming randomly from the threadpool, using thread ID is also a no solution, since there is a chance one specific thread never will call this method again at all, and keeping an instance for that ID is not a good thing.
Simply create one instance of Rand on each thread. Thread-safe, no locking, thus very performant. This can be achieved using the ThreadStaticAttribute.
public static class Rand
{
[ThreadStatic] private static Rand defaultRand;
public static Rand Default => defaultRand ??= new Rand();
// Add extra methods for seeding the static instance...
}
// Then in any thread:
var randomNumber = Rand.Default.GetULong();
You can do it without locking and still be threadsafe. Assuming the calculation is very fast (it is) and there is slower code executing around it, it's likely faster to simply recalculate if another thread changes it in between starting the calculation and finishing it. You can do that with an Interlocked.CompareExchange spin loop. The only difficulty is that there is no ulong version of that so we have to use an unsafe method to get the equivalent.
private static unsafe ulong InterlockedCompareExchange(ref ulong location,
ulong value, ulong comparand)
{
fixed (ulong* ptr = &location)
{
return (ulong)Interlocked.CompareExchange(ref *(long*)ptr, (long)value, (long)comparand);
}
}
public ulong GetULong()
{
unchecked
{
ulong prev = seed;
ulong t = prev;
t ^= t >> 12;
t ^= t << 25;
t ^= t >> 27;
while (InterlockedCompareExchange(ref seed, t, prev) != prev)
{
prev = seed;
t = prev;
t ^= t >> 12;
t ^= t << 25;
t ^= t >> 27;
}
return t * 0x2545F4914F6CDD1D;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With