Как правильно выбрать RNG seed для параллельных процессов

В настоящее время я работаю над проектом C/C++, где я использую генератор случайных чисел (gsl или boost). Вся идея может быть упрощена до нетривиального случайный процесс который получает семя и возвращает результаты. Я вычисляю средние значения по различным реализациям процесса.

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

до сих пор, я использую time(NULL) дать семя. Однако, если два процесса начинаются в одну и ту же секунду, семя остается тем же. Это происходит потому ,что я использую parallelisation (используя в OpenMP).

Итак, мой вопрос: как реализовать "сеятель" на C / C++, который дает независимые семена?

например, я, хотя в использовании номера потока (thread_num),seed = time(NULL)*thread_num. Однако это означает, что семена коррелируют: они кратны друг другу. Это создает какие-либо проблемы для "псевдослучайный" или он так же хорош, как последовательные семена?

требования заключаются в том, что он должен работать как на Mac OS (my pc), так и на дистрибутиве Linux, аналогичном OS Cent (кластер) (и, естественно, давать независимые реализации).

9 ответов


мы столкнулись с аналогичной проблемой на вычислительной сетке Beowulf, решение, которое мы использовали, состояло в том, чтобы включить pid процесса в семя RNG, например:

time(NULL)*thread_num*getpid()

конечно, вы можете просто прочитать из /dev /urandom или/dev / random в целое число.


обычно используемая схема для этого должна иметь "мастер" RNG, используемый для генерации семян для каждого конкретного процесса RNG.

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


столкнувшись с этой проблемой, я часто использую seed_rng от Boost.идентификатор UUID. Он использует time, clock и случайные данные из /dev/urandom для расчета семени. Вы можете использовать его как

#include <boost/uuid/seed_rng.hpp>
#include <iostream>

int main() {
  int seed = boost::uuids::detail::seed_rng()();
  std::cout << seed << std::endl;
}

отметим, что seed_rng из detail пространство имен, поэтому оно может уйти без дальнейшего уведомления. В этом случае напишите свою собственную реализацию на основе seed_rng не должно быть слишком трудно.


Mac OS тоже Unix, поэтому, вероятно, имеет /dev/random. Если это так, то лучшее решение для получения семян. В противном случае, Если генератор хорошо, с time( NULL ) один раз, а затем увеличивая его для затравки каждый генератор должен давать достаточно хорошие результаты.


Если вы находитесь на x86 и не возражаете сделать код непереносимым, вы можете прочитать счетчик отметок времени (TSC), который является 64-битным счетчиком, который увеличивается на тактовой частоте CPU (max) (около 3 ГГц) и использовать его как семя.

#include <stdint.h>
static inline uint64_t rdtsc()
{
    uint64_t tsc;
    asm volatile
    ( 
        "rdtsc\n\t"
        "shl\t,%%rdx\n\t"       // rdx = TSC[ 63 : 32 ] : 0x00000000
        "add\t%%rdx,%%rax\n\t"     // rax = TSC[ 63 :  0 ]
        : "=a" (tsc) : : "%rdx"
    );
    return tsc;
}

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

если случайный процесс находится в высоком мерном фазовом пространстве, я думаю, что одно хорошее предложение может быть:

seed = MAXIMUM_INTEGER/NUMBER_OF_PARALLEL_RW*thread_num + time(NULL)

обратите внимание, что с помощью scheme вы не гарантируя, что время тау большое !!

Если у вас есть некоторые знания о системной шкале времени, вы можете вызвать генератор случайных чисел некоторое число o раз, чтобы генерировать семена, которые равноудалены на некоторый интервал времени.


возможно, вы могли бы попробовать std:: Chrono часы с высоким разрешением от C++11:

класс std:: chrono:: high_resolution_clock представляет часы с наименьший период тика, доступный в системе. Это может быть псевдоним СТД::хроно::system_clock или std::хроно::steady_clock, или третье, независимые часы.

http://en.cppreference.com/w/cpp/chrono/high_resolution_clock

но tbh я не уверен, что есть ничего плохого с srand(0); srand(1), srand(2).... но мои знания о ранде очень, очень глубоки. :/

для сумасшедшей безопасности рассмотрим это:

обратите внимание, что все генераторы псевдослучайных чисел, описанные ниже CopyConstructible и переуступке. Копирование или назначение генератора скопирует все свое внутреннее состояние, поэтому оригинал и копия будут генерировать одинаковые последовательности случайных числа.

http://www.boost.org/doc/libs/1_51_0/doc/html/boost_random/reference.html#boost_random.reference.generators

поскольку большинство генераторов имеют сумасшедшие длинные циклы, вы можете создать один, скопировать его как первый генератор, сгенерировать X чисел с оригиналом, скопировать его как второй, сгенерировать X чисел с оригиналом, скопировать его как третий... Если ваши пользователи вызывают свой собственный генератор меньше X раз, они не будут перекрываться.


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

в этом случае вы правы, подозревая, что предоставление разных (коррелированных) семян не гарантирует вам ничего, если алгоритм rng так не говорит. У вас в основном есть два решения:

простая версия

использовать a один источник случайных чисел, с одним семенем. Затем подавайте случайные числа по кругу для каждого процесса.

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

вы можете сделать то же самое, но генерировать все случайные числа, которые вам нужны сразу, а затем разделить этот набор на столько срезов, сколько у вас есть процессов.

используйте RNG, предназначенный для этого

вы можете найти в газетах и на в сети несколько алгоритмов, специально разработанных для обеспечения независимых потоков случайных чисел из одного начального состояния. Они сложны, но большинство из них предоставляют исходный код. Идея состоит в том, чтобы "разбить" пространство RNG (значения, которые вы можете получить из начального состояния) на различные куски, как указано выше. Они просто быстрее, потому что используемый алгоритм позволяет легко вычислить, каким будет состояние ГСЧ если вы пропустили определенное количество значений.

эти генераторы обычно называются "параллельными генераторами случайных чисел". Самые популярные из них, вероятно, эти два:

проверяем их руководства, чтобы полностью понять, что они делают, как они это делают, и если это действительно то, что вам нужно.


вы можете посмотреть на std::random_device для получения недетерминированных случайных чисел.