C++ супер быстрая потокобезопасная функция rand

void NetClass::Modulate(vector <synapse> & synapses )
{
    int size = synapses.size();
    int split = 200 * 0.5;

    for(int w=0; w < size; w++)
        if(synapses[w].active)
            synapses[w].rmod = ((rand_r(seedp) % 200 - split ) / 1000.0);
}

функции rand_r(seedp) серьезно бутылк-necking моя программа. В частности, его замедление меня 3X при запуске serialy и 4.4 X при запуске на 16 ядрах. rand() не вариант, потому что его еще хуже. Я могу что-нибудь сделать, чтобы это упростить? Если это будет иметь значение, я думаю, что смогу выдержать потерю с точки зрения статистической случайности. Будет ли предварительная генерация (перед выполнением) списка случайных чисел, а затем загрузка в стеки потоков?

7 ответов


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


Это зависит от того, насколько хорошей должна быть статистическая случайность. Для высокого качества,Мерсенн твистер, или свой вариант SIMD, хороший выбор. Вы можете генерировать и буферизировать большой блок псевдослучайных чисел одновременно, и каждый поток может иметь свой собственный вектор состояния. The Парк-Миллер-Карта PRNG чрезвычайно прост -эти ребята даже реализовал его как ядро CUDA.


Марсалья по генератор xor-shift вероятно самый быстрый генератор "разумного качества" который вы можете использовать. Он не совсем имеет такое же "качество", как MT19937 или хорошо, но, честно говоря, эти различия являются академическими софизмами.
Для всех реальных, практических применений нет никакой заметной разницы, за исключением 1-2 порядков разницы в скорости выполнения и 3 порядков разницы в потреблении памяти.

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

единственными тремя заметными проблемами с генератором XOR-shift являются:

  • это не K-распределено до 623 измерений, но, честно говоря, кого это волнует. Я не могу думать более чем в 4 измерениях (и даже это ложь!), и не могу представить себе много приложений, где более 10 или 20 измерений могут иметь значение. Это должно быть какая-то эзотерическая симуляция.
  • он проходит большинство, но не всегда педантичный статистический тест. Опять же, какая разница. Большинство людей используют случайный генератор, который даже не проходит ни одного теста и никогда не замечает.
  • нулевое семя произведет нулевую последовательность. Это тривиально фиксируется добавлением ненулевой постоянной к одному из временных (интересно, почему Марсалья никогда не думала об этом?). Сказав это, MT19937 также ведет себя очень плохо, учитывая нулевое семя, и не восстанавливается почти так же хорошо.

посмотреть Boost: http://www.boost.org/doc/libs/1_47_0/doc/html/boost_random.html Он имеет несколько вариантов, которые различаются по сложности (= скорость) и случайности (длина цикла).

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


вам абсолютно необходимо иметь 1 общий случайный?

некоторое время назад у меня была аналогичная проблема с конфликтом, решение, которое лучше всего работало для меня, состояло в создании нового случайного класса (я работал в C#) для каждого потока. все равно они чертовски дешевы.

Если вы правильно их засеиваете, чтобы убедиться, что вы не создаете дубликаты семян, вы должны быть в порядке. Тогда у вас не будет общего состояния, поэтому вам не нужно использовать функцию threadsafe.

с уважением GJ


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


Я думаю, вы можете использовать OpenMP для распараллеливания следующим образом:

#pragma omp parallel
for(int w=0; w < size; w++)