Подтвердить что ты не робот

Как создать безопасные однородные случайные числа?

Моя программа должна генерировать множество случайных целых чисел в некотором диапазоне (int min, int max). Каждый вызов будет иметь другой диапазон. Что такое хороший (предпочтительно поточно-безопасный) способ сделать это? Следующее не является потокобезопасным (и использует rand(), который люди, похоже, препятствуют):

int intRand(const int & min, const int & max)
{
    return (rand() % (max+1-min)) + min;
}

Это намного медленнее, но использует <random>:

int intRand(const int & min, const int & max) {
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(min,max);
    return distribution(generator);
}

Что-то вроде этого - то, что я собираюсь (функция changeParameters не существует):

int intRand(const int & min, const int & max) {
    static std::default_random_engine generator;
    static std::uniform_int_distribution<int> distribution(0, 10);
    distribution.changeParameters(min, max);
    return distribution(generator);
}

Другой вариант - сделать широкий диапазон на uniform_int_distribution, а затем использовать mod, как в первом примере. Тем не менее, я занимаюсь статистической работой, поэтому я хочу, чтобы числа исходили из непредсказуемого распределения (например, если диапазон используемого распределения не кратен (max-min), распределение будет немного предвзято). Это вариант, но опять же, я бы хотел его избежать.

РЕШЕНИЕ. Это решение исходит из ответов @konrad-rudolph @mark-ransom и @mathk. Посев генератора случайных чисел выполняется в соответствии с моими конкретными потребностями. Более общий подход заключается в использовании времени (NULL). Если вы сделаете много потоков за одну секунду, они тогда получат одно и то же семя. Даже с clock() это проблема, поэтому мы включаем идентификатор потока. Недостаток - это утечка памяти - один генератор на поток.

#if defined (_MSC_VER)  // Visual studio
    #define thread_local __declspec( thread )
#elif defined (__GCC__) // GCC
    #define thread_local __thread
#endif

#include <random>
#include <time.h>
#include <thread>

using namespace std;

/* Thread-safe function that returns a random number between min and max (inclusive).
This function takes ~142% the time that calling rand() would take. For this extra
cost you get a better uniform distribution and thread-safety. */
int intRand(const int & min, const int & max) {
    static thread_local mt19937* generator = nullptr;
    if (!generator) generator = new mt19937(clock() + this_thread::get_id().hash());
    uniform_int_distribution<int> distribution(min, max);
    return distribution(*generator);
}
4b9b3361

Ответ 1

Вы пробовали это?

int intRand(const int & min, const int & max) {
    static thread_local std::mt19937 generator;
    std::uniform_int_distribution<int> distribution(min,max);
    return distribution(generator);
}

Распределения чрезвычайно дешевы (они будут полностью встроены оптимизатором, так что единственными оставшимися служебными данными являются фактическое масштабирование случайного числа). Не бойтесь регенерировать их так часто, как вам нужно - на самом деле их сброс будет концептуально не дешевле (поэтому эта операция не существует).

Фактический генератор случайных чисел, с другой стороны, представляет собой тяжеловесный объект, несущий много состояний и требующий достаточно времени для его построения, поэтому его следует инициализировать только один раз на поток (или даже по потокам, но то вам необходимо синхронизировать доступ, который является более дорогостоящим в долгосрочной перспективе).

Ответ 2

Сделайте генератор static, поэтому он создается только один раз. Это более эффективно, поскольку хорошие генераторы обычно имеют большое внутреннее состояние; что более важно, это означает, что вы фактически получаете псевдослучайную последовательность, которую он генерирует, а не (гораздо менее случайные) начальные значения отдельных последовательностей.

Создавайте новый дистрибутив каждый раз; это, как правило, легкие объекты с небольшим состоянием, особенно такие же простые, как uniform_int_distribution.

Для обеспечения безопасности потоков, необходимо сделать генератор thread_local, с другим семенем для каждого потока или защитить его мьютексом. Первый, скорее всего, будет быстрее, особенно если есть много споров, но будет потреблять больше памяти.

Ответ 3

Вы можете использовать один default_random_engine для каждого потока, используя локальное хранилище потоков.

Я не могу сказать, как правильно использовать TLS, поскольку он зависит от ОС. Лучший источник, который вы можете использовать, - это поиск через Интернет.

Ответ 4

Я человек из будущего с той же проблемой. Принятый ответ не будет компилироваться на MSVC 2013, потому что он не реализует thread_local (а использование __declspec(thread) не работает, потому что ему не нравятся конструкторы).

Утечка памяти в вашем решении может быть удалена из кучи, изменив все, чтобы использовать новое размещение.

Здесь мое решение (объединенное из заголовка и исходного файла):

#ifndef BUILD_COMPILER_MSVC
thread_local std::mt19937 _generator;
#else
__declspec(thread) char _generator_backing[sizeof(std::mt19937)];
__declspec(thread) std::mt19937* _generator;
#endif
template <typename type_float> inline type_float get_uniform(void) {
    std::uniform_real_distribution<type_float> distribution;
    #ifdef BUILD_COMPILER_MSVC
        static __declspec(thread) bool inited = false;
        if (!inited) {
            _generator = new(_generator_backing) std::mt19937();
            inited = true;
        }
        return distribution(*_generator);
    #else
        return distribution(_generator);
    #endif
}

Ответ 5

Напишите простой LCG (или любой другой) PRNG для себя, который будет производить номера максимально возможного. Используйте одну статическую копию встроенного RNG, чтобы засеять новую локальную копию собственного PRNG для каждого нового потока, который вы создаете. Каждый поток-локальный PRNG будет иметь свое собственное локальное хранилище и никогда больше не должен ссылаться на центральный RNG.

Это предполагает, что статистически хороший RNG подходит для вас, и эта криптографическая безопасность не является проблемой.