Быстрый потокобезопасный генератор случайных чисел для C # - PullRequest
0 голосов
/ 16 февраля 2012

Мне нужно быстро генерировать случайные числа с плавающей точкой в ​​нескольких запущенных потоках.Я попытался использовать System.Random, но он слишком медленный для моих нужд и возвращает одно и то же число в нескольких потоках.(Он отлично работает, когда я запускаю свое приложение в одном потоке.) Кроме того, мне нужно убедиться, что сгенерированные числа находятся в диапазоне от 0 до 100.

Вот что я сейчас пытаюсь:

number = random.NextDouble() * 100;

Что мне вместо этого попробовать?

Ответы [ 3 ]

6 голосов
/ 16 февраля 2012

Вот мое мнение (требуется .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();
    }
}

и тест для проверки того, что для 1000 итераций каждое значение уникально:

[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);
    }
}

Я не гарантирую, что он никогда не вернет дублирующее значение, но я выполнил тест с 10000 итерациями, и он прошел тест.

3 голосов
/ 29 июня 2012

Я использую Windows CryptoAPI для хороших случайных чисел.Для производительности я делаю один вызов для блока из 8 КБ случайных данных и распределяю числа из них вместо вызова cryptoAPI для каждого номера.Не уверен, что производительность в конце концов по сравнению с обычным случайным.Но рандомизация намного лучше (узнайте подробности о Windows CryptoAPI в Интернете)

Это код;

// 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;
            _lastsize = 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);
    }    

    }
}    
2 голосов
/ 16 февраля 2012

Если Random дает вам одни и те же числа, то вы, вероятно, используете его неправильно, либо создавая много экземпляров в последовательной последовательности (это означает, что все они будут использовать одно и то же начальное число и генерировать таким образом той же последовательности), или используя один экземпляр в нескольких потоках (тем самым «разбивая» этот экземпляр, поскольку он небезопасен для многопоточного использования).

Если скорость и случайность Random достаточны для вас при работе в одном потоке, вы можете попробовать обернуть его в ThreadLocal<T>, чтобы обеспечить отдельный экземпляр для каждого потока в вашем многопоточном сценарии:

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);
    });
...