0

I need to quickly generate random floating-point numbers across multiple running threads. I've tried using System.Random, but it's too slow for my needs and it returns the same number across multiple threads. (It works fine when I run my application in a single thread.) Also, I need to make sure the generated numbers are between 0 and 100.

Here's what I'm trying now:

number = random.NextDouble() * 100;

What should I try instead?

reuben
  • 3,360
  • 23
  • 28
MartinS
  • 751
  • 3
  • 12
  • 27
  • 5
    If random gives you the same numbers all the time, you're likely not using it right. Also note that `Random` is not thread-safe. – Sergey Kalinichenko Feb 16 '12 at 11:05
  • Also note that generating truely random numbers is a big deal : http://www.random.org/randomness/ – Myles McDonnell Feb 16 '12 at 11:13
  • 6
    Very fast and thread-safe: `return 4;` – H H Feb 16 '12 at 11:15
  • Googling on the topic of 'parallel random number generator' hits O(10^6) references. I suggest OP does some research. – High Performance Mark Feb 16 '12 at 11:20
  • [The MSDN page](http://msdn.microsoft.com/en-us/library/system.random.aspx) says "The current implementation of the Random class is based on a modified version of Donald E. Knuth's subtractive random number generator algorithm." so I can't imagine it's overly complex and that you'd get much quicker? If you're simply worried about getting different numbers per thread you could seed separate instances of it with a value based on the current thread ID. – Rup Feb 16 '12 at 11:24
  • 1
    possible duplicate of [Random.Next returns always the same values](http://stackoverflow.com/questions/1654887/random-next-returns-always-the-same-values) – H H Feb 16 '12 at 20:00

3 Answers3

5

Here is my take on it (requires .net 4.0):

public static class RandomGenerator
{
    private static object locker = new object();
    private static Random seedGenerator = new Random(Environment.TickCount);

    public static double GetRandomNumber()
    {
        int seed;

        lock (locker)
        {
            seed = seedGenerator.Next(int.MinValue, int.MaxValue);
        }

        var random = new Random(seed);

        return random.NextDouble();
    }
}

and a test to check that for 1000 iterations each value is unique:

[TestFixture]
public class RandomGeneratorTests
{
    [Test]
    public void GetRandomNumber()
    {
        var collection = new BlockingCollection<double>();

        Parallel.ForEach(Enumerable.Range(0, 1000), i =>
        {
            var random = RandomGenerator.GetRandomNumber();
            collection.Add(random);
        });

        CollectionAssert.AllItemsAreUnique(collection);
    }
}

I don't guarantee that it will never return a duplicate value, but I've run the test with 10000 iterations and it passed the test.

Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
  • 4
    This isn't thread-safe because it's sharing a single `Random` instance -- `seedGenerator` -- across all threads. Sooner or later this will break in a multithreaded environment. – LukeH Feb 16 '12 at 12:12
  • Good point, I missed the lock around the seedGenerator.Next(). I've updated the example. – Trevor Pilley Feb 16 '12 at 12:36
  • When generating numbers in the range `1..n`, the odds of generating value `x` twice in a row should be `1/n`, not zero. – Bevan Jan 05 '17 at 03:37
4

If Random is giving you the same numbers then you're probably using it incorrectly, either by creating many instances in close succession (meaning that they'll all use the same seed and so generate the same sequence), or by using a single instance across several threads (thereby "breaking" that instance since it's not safe for multithreaded use).

If the speed and randomness of Random are good enough for you when running in a single thread then you could try wrapping it in a ThreadLocal<T> to ensure a separate instance for each thread in your multithreaded scenario:

var number = _rng.Value.NextDouble() * 100;

// ...

private static int _staticSeed = Environment.TickCount;
private static readonly ThreadLocal<Random> _rng = new ThreadLocal<Random>(() =>
    {
        int seed = Interlocked.Increment(ref _staticSeed) & 0x7FFFFFFF;
        return new Random(seed);
    });
LukeH
  • 263,068
  • 57
  • 365
  • 409
2

I use the windows cryptoAPI for good random numbers. For performance I do a single call for a block of 8KB of random data and distribute numbers from that instead of call the cryptoAPI for each number. Not sure what the performance is in the end compared to the normal random. But the randomization is far better (check the internet for details on the Windows CryptoAPI)

This is the code;

// UNIT RandomNumberGeneratorBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FastLibrary
{
    public abstract class RandomNumberGeneratorBase
{    
        private int _byteBufSize;
        private byte[] _buf;
        private int _idx;
        private int _lastsize;

        public RandomNumberGeneratorBase(int bufSize = 8092)
    {    
            _byteBufSize = bufSize;
            _buf = new byte[_byteBufSize];
            _idx = _byteBufSize;
        }

        protected abstract void GetNewBuf(byte[] buf);

        private void CheckBuf(int bytesFreeNeeded = 1)
        {    
            _idx += _lastsize;
            _lastsize = bytesFreeNeeded;
            if (_idx + bytesFreeNeeded < _byteBufSize) { return; }
            GetNewBuf(_buf);
            _idx      = 0;
        }

        public byte GetRandomByteStartAtZero(int belowValue)
       {    
         return (byte)(Math.Round(((double)GetRandomByte() * (belowValue - 1)) / 255));
       }    

        public int GetRandomIntStartAtZero(int belowValue)
       {    
            return (int)(Math.Round(((double)GetRandomUInt32() * (double)(belowValue - 1)) / (double)uint.MaxValue));
       }    

        public byte GetRandomByte()
    {    
            CheckBuf();
        return _buf[_idx];
    }    

        public bool GetRandomBool()
    {    
            CheckBuf();
        return _buf[_idx] > 127;
    }    

        public ulong GetRandomULong()
    {    
            CheckBuf(sizeof(ulong));
        return BitConverter.ToUInt64(_buf, _idx);
    }    

        public int GetRandomInt()
    {    
            CheckBuf(sizeof(int));
        return BitConverter.ToInt32(_buf, _idx);
    }    

        /// <summary>
        ///     Double from 0 to 1 (might be zero, will never be 1)
        /// </summary>
        public double GetRandomDouble()
    {    
            return GetRandomUInt32() / (1d + UInt32.MaxValue);
    }    

        /// <summary>
        ///     Float from 0 to 1 (might be zero, will never be 1)
        /// </summary>
        public float GetRandomFloat()
    {    
            return GetRandomUInt32() / (1f + UInt32.MaxValue);
    }    

        public uint GetRandomUInt32()
    {    
            CheckBuf(sizeof(UInt32));
            return BitConverter.ToUInt32(_buf, _idx);
    }    
    }    
}    

// UNIT StrongRandomNumberGenerator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace FastLibrary
{
    public sealed class StrongRandomNumberGenerator : RandomNumberGeneratorBase
{    
        private RNGCryptoServiceProvider _rnd;

        public StrongRandomNumberGenerator()
    {    
            _rnd = new RNGCryptoServiceProvider();
    }    

        protected override void GetNewBuf(byte[] buf)
    {    
            _rnd.GetBytes(buf);
    }    

    }
}    
Community
  • 1
  • 1
IvoTops
  • 3,463
  • 17
  • 18
  • 1
    I wonder - how did you test it? I created a simple test method, according to which when you generate a milion random numbers your code works far worse than default Random class - https://pastebin.com/BGc66gZx. Not only it's slower, but produces twice as many duplicates... – Bartosz Jul 27 '19 at 20:41
  • The dupes were due to a small bug. Code is updated. Randomness of cryptoaapi should be a lot better as random.next according to what I read on the net 7 years ago. No links for that, sorry. Performance was not the main concern I needed the better randomness. – IvoTops Jul 28 '19 at 07:03
  • Your testcode will spend a lot of time adding to the list and expanding its memory. For performance only testing do not add the numbers into a list and compare those timings. – IvoTops Jul 28 '19 at 07:08
  • See this video around minute 13. It is one source telling us to not use random.next but the cryptoapi instead. https://youtu.be/ySQl0NhW1J0?t=788 – IvoTops Jul 28 '19 at 07:13
  • For testing I made x buckets and generated millions of numbers that should be distributed equally across the buckets and checked the distribution across buckets at the end. Say a bucket for 1 to 6 and generate the numbers of a dice repeatedly. The counts in all buckets should be really close. The bug survives this test unforrunately. I found it later when adding some feature to the code I think. (And forgot to update stackoverflow at the time. Sorry :) – IvoTops Jul 28 '19 at 07:34
  • @Bartosz See previous comments – IvoTops Jul 28 '19 at 11:12
  • thans for reply. Regarding my tests - yeah, there is a matter of adding to the list, but it is the same overhead for both approaches so I don't think it matters. – Bartosz Jul 28 '19 at 16:34
  • What striked me later though, is... Randomness is not uniqueness. Perhaps crytpo- approach to generating makes the numbers more close to true randomness, but not necessarily more unique... – Bartosz Jul 28 '19 at 16:36
  • @bartosz if list overhead is 90% of cpu time the difference between the two methods is understated a lot. I have no idea how much the overhead is so removing the list will make the relative timings more comparable. – IvoTops Jul 28 '19 at 20:07