Перш за все у презентації, яку ви зв’язали, лише з метою безпеки йдеться про випадкові числа. Тому він не стверджує, що Random
це погано для цілей, що не стосуються безпеки.
Але я справді стверджую, що це так. Реалізація .net 4 Random
недолікована кількома способами. Я рекомендую використовувати його лише в тому випадку, якщо ви не дбаєте про якість своїх випадкових чисел. Я рекомендую використовувати кращі сторонні реалізації.
Помилка 1: посів
Конструктор за замовчуванням насіння з поточним часом. Таким чином, всі екземпляри, Random
створені за допомогою конструктора за замовчуванням за короткий проміжок часу (приблизно 10 мс), повертають однакову послідовність. Це задокументоване та «побічний проект». Це особливо дратує, якщо ви хочете багатопотоковий код, оскільки ви не можете просто створити екземплярRandom
на початку виконання кожного потоку.
Обхідний шлях полягає в тому, щоб бути надто обережним при використанні конструктора за замовчуванням та заводити вручну, коли це необхідно.
Інша проблема тут полягає в тому, що насіннєвий простір досить малий (31 біт). Отже, якщо ви генеруєте 50 тисяч екземплярів Random
із абсолютно випадковими насінинами, ви, ймовірно, отримаєте одну послідовність випадкових чисел двічі (через парадокс дня народження ). Тож висіти вручну теж непросто.
Помилка 2: Розподіл випадкових чисел, повернутих Next(int maxValue)
є упередженим
Є параметри, для яких Next(int maxValue)
явно неоднорідність. Наприклад, якщо підрахувати, r.Next(1431655765) % 2
ви отримаєте 0
приблизно 2/3 зразків. (Зразок коду в кінці відповіді.)
Помилка 3: NextBytes()
метод неефективний.
Вартість за байт NextBytes()
приблизно така велика, як вартість формування цілочисельної вибірки Next()
. З цього я підозрюю, що вони справді створюють один зразок на байт.
Краща реалізація, що використовує 3 байти з кожної вибірки, прискориться NextBytes()
майже в 3 рази.
Завдяки цьому недолік Random.NextBytes()
лише на 25% швидший, ніж System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes
на моїй машині (Win7, Core i3 2600MHz).
Я впевнений, що якщо хтось перевірить вихідний / декомпільований байт-код, він знайде ще більше недоліків, ніж я виявив за допомогою аналізу чорної скриньки.
Зразки коду
r.Next(0x55555555) % 2
є сильно упередженим:
Random r = new Random();
const int mod = 2;
int[] hist = new int[mod];
for(int i = 0; i < 10000000; i++)
{
int num = r.Next(0x55555555);
int num2 = num % 2;
hist[num2]++;
}
for(int i=0;i<mod;i++)
Console.WriteLine(hist[i]);
Продуктивність:
byte[] bytes=new byte[8*1024];
var cr=new System.Security.Cryptography.RNGCryptoServiceProvider();
Random r=new Random();
for(int i=0;i<100000;i++)
{
r.NextBytes(bytes);
}
for(int i=0;i<100000;i++)
{
for(int j=0;j<bytes.Length;j++)
bytes[j]=(byte)r.Next();
}
for(int i=0;i<100000;i++)
{
for(int j=0;j+2<bytes.Length;j+=3)
{
int num=r.Next();
bytes[j+2]=(byte)(num>>16);
bytes[j+1]=(byte)(num>>8);
bytes[j]=(byte)num;
}
}
for(int i=0;i<100000;i++)
{
cr.GetBytes(bytes);
}