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

Случайные числа для нескольких потоков

Проблема

Я намерен написать приложение С++ 11 для Linux, которое выполняет некоторое численное моделирование (а не криптографию) на основе примерно одного миллиона псевдослучайных 32-битных чисел. Чтобы ускорить работу, я хотел бы выполнить симуляцию в параллельных потоках, используя все ядра настольного процессора. Я бы хотел использовать Mersenne Twister mt19937, предоставленный boost, как PRNG, и я предполагаю, что по соображениям производительности у меня должен быть такой PRNG на поток. Теперь я не уверен, как их семени, чтобы не генерировать одну и ту же последовательность случайных чисел в нескольких потоках.

Альтернативы

Вот альтернативы, о которых я думал до сих пор:

  • Разделите PRNG для каждого потока независимо от /dev/urandom.

    Меня немного беспокоит случай, когда системный энтропийный пул исчерпан, так как я не знаю, как работает внутренний PRNG системы. Может случиться так, что я случайно получаю последовательные семена, которые точно определяют последовательные состояния Мерсэнн Твистер, из-за того, что /dev/urandom использует сам Мерсенн Твистер? Вероятно, сильно связан с моими проблемами для следующего пункта.

  • Поместите один PRNG из /dev/urandom, а остальные - из первого.

    В основном такая же проблема: хорошо или плохо использовать один PRNG для посева другого, который использует тот же алгоритм? Или, другими словами, читает ли 625 32-битных целых чисел от mt19937 непосредственно внутреннее состояние генератора mt19937 в любой точке этого поколения?

  • Выделите других людей сначала с информацией, не относящейся к Мерсенне.

    Поскольку использование одного и того же алгоритма для генерации случайных чисел и генерации начального семени так или иначе похоже на то, что это может быть плохой идеей, я подумал о введении некоторого элемента, который не зависит от алгоритма Мерсенна Твистера. Например, я мог бы XOR идентификатор потока в каждый элемент исходного вектора семени. Это улучшает ситуацию?

  • Поделитесь одним PRNG между потоками.

    Это гарантирует, что существует только одна последовательность, со всеми известными и желательными свойствами Mersenne Twister. Но накладные расходы на блокировку, необходимые для контроля доступа к этому генератору, меня несколько волнуют. Поскольку я не нашел никаких доказательств обратного, я предполагаю, что я как пользователь библиотеки будет нести ответственность за предотвращение одновременного доступа к PRNG.

  • Предварительно сгенерируйте все случайные числа.

    Это будет иметь один поток, который генерирует все необходимые 1M случайные числа спереди, которые будут использоваться разными потоками позже. Требование к памяти 4M было бы небольшим по сравнению с объемом всего приложения. Меня больше всего беспокоит такой подход: генерация самих случайных чисел не является параллельной. Весь этот подход также не слишком хорошо масштабируется.

Вопросы

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

Знаете ли вы, какие из моих проблем оправданы и которые просто объясняются моим отсутствием понимания того, как все работает?

4b9b3361

Ответ 1

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

  • Даже небольшие изменения в пространстве состояний приводят к довольно большим изменениям вниз по течению - если вы можете убедиться, что у них нет одинакового стартового пространства (и не существует идентичного префикса состояния), я бы не стал беспокоиться о том, чтобы производить одинаковые числа. Например, использование только значений 1,2,3 для семян трех потоков будет работать нормально - вам даже не нужно высевать все пространство. Еще одно преимущество: с помощью явно предсказуемых семян вы можете легко дискредитировать идею о том, что вы выбрали вишню в любых прогонах (при условии, что вы пытаетесь что-то продемонстрировать).
  • Это тривиально для семени таким образом, что результирующие "дети" сильно не коррелированы. Просто перебирайте в первую очередь; т.е. если вы хотите посеять значения N x 623 int, не выставляйте значения 623 последовательно, а выбирайте первый N и распределяйте, затем следующий N и т.д. Даже если есть некоторая корреляция между сеятелем и детьми, корреляция между различные дети должны быть практически несуществующими - и что вам все равно.
  • Я бы предпочел алгоритм, позволяющий детерминированное выполнение, когда это возможно, поэтому в зависимости от урандома не является привлекательным. Это облегчает отладку.
  • Наконец, и, очевидно, тест. Эти PRNG довольно надежны, но, во всяком случае, обеспечивают результаты и делают несколько корреляционных тестов, вдохновляющих то, что вы имитируете. Большинство проблем должно быть очевидным - либо вы посеялись плохо, и есть очевидные повторяющиеся подпоследовательности, вы хорошо посеялись, а затем качество диктуется ограничениями PRNG.
  • Для окончательных исполнений после того, как вы закончите тестирование, вы можете засеять первое из значений состояния 623, используя urandom для спокойствия и/или идентификатора потока.

Ответ 2

Я бы пошел с №1, запустил каждый prng из urandom. Это гарантирует, что состояния полностью независимы (поскольку данные семян независимы). Как правило, будет доступно множество энтропии, если у вас много потоков. Кроме того, в зависимости от алгоритма, используемого для /dev/urandom, вам почти наверняка не нужно беспокоиться об этом.

Для создания каждого prng вы можете использовать что-то вроде следующего:

#include <random>

std::mt19937 get_prng() {
    std::random_device r;
    std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
    return std::mt19937(seed);
}

Вы должны убедиться, что ваша реализация std::random_device тянется от /dev/urandom в вашей конфигурации. И если он по умолчанию использует /dev/urandom, то обычно вы можете сказать std::random_device("/dev/random"), если вы хотите использовать /dev/random вместо этого.

Ответ 3

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

Однако я бы выбрал №5. Если он работает, тогда это нормально. Если это не так, вы все равно можете его оптимизировать.

Точка создания хорошего PRNG намного сложнее, чем можно было бы ожидать. Хороший PRNG для резьбовых приложений - это, скорее всего, то, что все еще подвержено исследованиям.

Если количество процессоров достаточно низкое, вы можете ускользнуть от прыжков. Например. если у вас есть 4 ядра, инициализируйте все с одинаковыми значениями, но продвигайте ядро ​​1 PRNG на 1, # 2 и 3 на 3. Затем заранее выполняйте 4 шага, когда вам нужен новый номер.

Ответ 4

Семенная нить 1 с 1, семенная нить 2 с 2 и т.д.

Если вам нужен monte carlo, это даст вам воспроизводимые результаты, легко отслеживается и реализуется.

Ответ 6

Я бы сказал, что №3 - победитель. Семя каждого потока с чем-то вроде processID или threadID; в то время как технически возможно, что вы могли бы перекрыться, это маловероятно. Даже последовательные числа не должны быть связаны с семенами, когда вы выходите из одиночных цифр (я не знаю алгоритм Twister, но худший PRNG, который я видел, был выше 7). Один миллион PRNG не так много по сравнению с объемом большинства уравнений PRNG.

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

Ответ 7

Существует конкретная реализация (и опубликованная статья), касающаяся использования Mersenne Twister для параллельных вычислений. Это оригинальные авторы МТ. Они называют его "динамическим создателем", и его можно найти здесь:

http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/DC/dc.html

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