Практически любая реализация "старого" rand()
использует LCG ; в то время как они, как правило, не самые лучшие генераторы, обычно вы не увидите, как они провалится в таком базовом тесте - среднее значение и стандартное отклонение, как правило, исправляются даже худшими PRNG.
Типичные недостатки "плохих" - но достаточно распространенных - rand()
реализаций:
- низкая случайность младших битов;
- короткий период;
- низкий
RAND_MAX
;
- некоторая корреляция между последовательными экстракциями (в общем, LCG производят числа, которые находятся на ограниченном числе гиперплоскостей, хотя это может быть как-то смягчено).
Тем не менее, ни один из них не относится к API rand()
. Конкретная реализация может поместить генератор семейства xorshift позади srand
/ rand
и, строго говоря, получить современный PRNG без изменений интерфейса, поэтому ни один тест, подобный тому, который вы выполняли, не показал бы слабости в выход.
Редактировать: @R. правильно отмечает, что интерфейс rand
/ srand
ограничен тем фактом, что srand
занимает unsigned int
, поэтому любой генератор, который реализация может поставить за ними, изначально ограничен UINT_MAX
возможными начальными начальными числами (и, следовательно, сгенерированными последовательностями). Это действительно так, хотя API можно тривиально расширить, чтобы srand
принять unsigned long long
или добавить отдельную srand(unsigned char *, size_t)
перегрузку.
Действительно, настоящая проблема с rand()
заключается не столько в реализации в принципе , но:
Наконец, rand
состояние дел:
- не указывает фактическую реализацию (стандарт C предоставляет только пример реализации), поэтому любая программа, которая предназначена для получения воспроизводимого результата (или ожидает PRNG некоторого известного качества) для разных компиляторов, должна выполнить свой собственный генератор;
- не предоставляет какого-либо кроссплатформенного метода для получения достойного начального числа (
time(NULL)
нет, поскольку он недостаточно детализирован, и часто - думаю, встроенные устройства без RTC - даже не достаточно случайный).
Отсюда новый заголовок <random>
, который пытается исправить этот беспорядок, предоставляя следующие алгоритмы:
- полностью определено (так что вы можете иметь воспроизводимый кросс-компилятором вывод и гарантированные характеристики - скажем, диапазон генератора);
- как правило, самого современного качества ( с момента создания библиотеки ; см. Ниже);
- инкапсулировано в классах (поэтому вам не нужно навязывать глобальное состояние, что позволяет избежать проблем с многопоточностью и нелокальностью);
... и по умолчанию random_device
, а также для их заполнения.
Теперь, если вы спросите меня, мне бы понравился также простой API, построенный поверх этого для "простых", "угадать число" случаев (аналогично тому, как Python предоставляет "сложный" «API, а также тривиальное random.randint
& Co., использующее глобальный предварительно отобранный PRNG для нас, несложных людей, которые хотели бы не утонуть в случайных устройствах / движках / адаптерах / чем угодно, каждый раз, когда мы хотим извлечь число для бинго-карты), но это правда, что вы можете легко создать его самостоятельно на основе текущих возможностей (в то время как создание «полного» API поверх упрощенного было бы невозможным).
Наконец, вернемся к вашему сравнению производительности: как указали другие, вы сравниваете быструю LCG с более медленным (но обычно считающимся лучшим качеством) Mersenne Twister; если вы согласны с качеством LCG, вы можете использовать std::minstd_rand
вместо std::mt19937
.
Действительно, после настройки вашей функции использовать std::minstd_rand
и избегать бесполезных статических переменных для инициализации
int getRandNum_New() {
static std::minstd_rand eng{std::random_device{}()};
static std::uniform_int_distribution<int> dist{0, 5};
return dist(eng);
}
Я получаю 9 мс (старый) против 21 мс (новый); наконец, если я избавлюсь от dist
(который по сравнению с классическим оператором по модулю обрабатывает перекос распределения для выходного диапазона, не кратного входному диапазону) и вернусь к тому, что вы делаете в getRandNum_Old()
int getRandNum_New() {
static std::minstd_rand eng{std::random_device{}()};
return eng() % 6;
}
Я уменьшил его до 6 мс (то есть на 30% быстрее), вероятно потому, что в отличие от вызова rand()
, std::minstd_rand
проще встроить.
Между прочим, я провел тот же тест, используя свернутый вручную (но в значительной степени соответствующий стандартному интерфейсу библиотеки) XorShift64*
, и это в 2,3 раза быстрее, чем rand()
(3,68 мс против 8,61 мс); учитывая, что, в отличие от Mersenne Twister и различных предоставленных LCG, он проходит текущий набор тестов на случайность с летающими цветами и , он невероятно быстрый, заставляет задуматься, почему он не включен в стандартной библиотеке пока нет.