Генерация одинаковых случайных чисел с потоками в OMP - PullRequest
0 голосов
/ 19 октября 2018

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

    long linkCnt;
    long j;
    long t;
    srand(randInit);

    linkCnt=0; //Keep track of # of outgoing edges
 #pragma omp parallel for schedule(runtime) private(t)
    for(j = 0; j < G->N; j++)
    {

        if(i == j){
            t = NO_CONN;
        } else {
            t = (rand() % ((MAX_EDGE_WEIGTH-1) * 2)+1); //50% of having no connection
            if(t > MAX_EDGE_WEIGTH){
                //t = INF; //Like no connection
                t = NO_CONN; //Like no connection
            } else {
                linkCnt++;
                G->visited[j] = VISITED; //Do this to find isolated nods that have no incomming edge
            }
        }

        G->node[i][j] = t;
    }

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

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

Во-первых, не-поточно-безопасный характер функции rand() означает, что одновременный вызов rand() из разных потоков может привести к разным результатам.значения, чем если бы он был вызван последовательно.Вероятно, проще всего объяснить это на простом примере, поэтому давайте рассмотрим 32-битную версию PCG, поскольку она приятна и проста.Он имеет 32-битное состояние и генерирует 32-битные числа, например:

static uint32_t state = ...;

static uint32_t generate(void) {
  uint32_t s = state;
  uint32_t v = ((s >> ((s >> 28) + 4)) ^ s) * (277803737U);
  v ^= v >> 22;
  state = state * 747796405U + 1729U;
  return v;
}

Теперь подумайте, что произойдет, если два потока вызовут generate() примерно в одно и то же время.Возможно, они оба получают одинаковое значение для state, и поэтому генерируют одно и то же случайное число дважды.Возможно, один обновляет state до того, как другой прочитает его, поэтому они получают разные значения.

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

Вторая часть проблемы заключается в том, что происходит, когда порядок вызывающих абонентов смешивается в вашем коде.Допустим, у вас есть два потока (называемые A и B), и каждый из них должен выполнить две итерации вашего цикла.Даже если вы получаете ваши случайные числа из источника, ориентированного на многопотоковое исполнение, порядок вызовов может быть AABB, ABAB, ABBA, BBAA, BABA или BAAB, каждый из которых приведет к тому, что ваш код сгенерирует другой результат.

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

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

int i;
int nums[LEN];
for (i = 0 ; i < LEN ; i++)
  nums[i] = generate();
#pragma omp parallel for ...
for (i = 0 ; i < LEN ; i++) {
  do_stuff(nums[i]);
}

Лучшим решением, однако, может быть просто отказаться от идеи генерации случайных чисел и вместо этого использовать хеширование.https://burtleburtle.net/bob/hash/integer.html имеет несколько вариантов.Примером этого может быть что-то вроде

for (int i = 0 ; i < LEN ; i++) {
  do_stuff(hash(i));
}

Конечно, вы можете добавить немного соли, возможно, даже использовать rand() для получения соли.

0 голосов
/ 19 октября 2018

Вот подход, основанный на блоках, который делит проблемное пространство на блоки N / BLOCK_SIZE и повторно устанавливает ГСЧ с вашим номером блока randInit + для каждого блока.Это дает воспроизводимый вывод независимо от того, сколько у вас потоков.Он также генерирует те же начальные N чисел для последовательности N + x.Это до тех пор, пока вы сохраняете тот же самый BLOCK_SIZE.

Хороший размер блока, вероятно, похож на ваш типичный N / (max_num_procs * 2).Но здесь есть место для экспериментов.

#include <omp.h>
#include <stdio.h>
#include <stdlib.h>

#define N_DEFAULT  48   //Default number of nodes 
#define BLOCK_SIZE 12   //BLOCK SIZE number of nodes per block.
                        //Changes this changes reseed frequencey, 
                        //.. and hence the generated sequence
#define randInit   42   //Had to be something.

int main(int argc , char* argv[])
{
    int N=N_DEFAULT;
    if (argc >1)
        N=atoi(argv[1]); 

    int rands[N];// keep our random numbers for sequential debug output
    int n=BLOCK_SIZE;
    int num_blocks=(N+BLOCK_SIZE-1)/ BLOCK_SIZE;  // ceil(N/BLOCK_SIZE)
    int nt=omp_get_max_threads();   


    printf("          N: %d\n",N);
    printf("     Blocks: %d, (size: %d)\n",num_blocks,n);
    printf("    threads: %d\n",nt);

    //Parallel random generation
    #pragma omp parallel for schedule(runtime) 
    for (int J=0;J<num_blocks;J++)
    {
        int block_seed=randInit+J;      // unique block seed
        int start = J * n;
        int end= start+n > N?N:start+n;

        int tid = omp_get_thread_num(); // Just for debug
        printf("thread %d: works on block %d (%d - %d )\n",tid,J,start,end);

        for (int j=start; j < end;j++)
        {           
            int t=rand_r(&block_seed); //rand_r provides thread safe (re-entrant rand)
            rands[j]=t;
        }
    }

    //Output for debug single thread
    for (int j=0; j < N;j++)
    {
        printf("%d : %d \n",j,rands[j]);
    }   
    return 0;
}

Вывод с другим N и числом потоков показано ниже.

N: 24                                   N: 27
Blocks: 3, (size: 8)                    Blocks: 4, (size: 8)
threads: 4                              threads: 1
-------------------------------------|-------------------------------
thread 1: works on block 1 (8 - 16 )    thread 0: works on block 0 (0 - 8 )
thread 2: works on block 2 (16 - 24 )   thread 0: works on block 1 (8 - 16 )
thread 0: works on block 0 (0 - 8 )     thread 0: works on block 2 (16 - 24 )
                                        thread 0: works on block 3 (24 - 27 )
-------------------------------------|-------------------------------
0 : 681191333                           0 : 681191333
1 : 928546885                           1 : 928546885
2 : 1457394273                          2 : 1457394273
3 : 941445650                           3 : 941445650
4 : 2129613237                          4 : 2129613237
5 : 1661015563                          5 : 1661015563
6 : 2071432601                          6 : 2071432601
7 : 222443696                           7 : 222443696
8 : 1156886562                          8 : 1156886562
9 : 398918689                           9 : 398918689
10 : 170756699                         10 : 170756699
11 : 703115845                         11 : 703115845
12 : 1424182583                        12 : 1424182583
13 : 1516198481                        13 : 1516198481
14 : 1740837599                        14 : 1740837599
15 : 1148851528                        15 : 1148851528
16 : 1633630368                        16 : 1633630368
17 : 2015727614                        17 : 2015727614
18 : 1031602773                        18 : 1031602773
19 : 463737465                         19 : 463737465
20 : 720848057                         20 : 720848057
21 : 1369285272                        21 : 1369285272
22 : 1411290150                        22 : 1411290150
23 : 2074210785                        23 : 2074210785
-------------------------------------|-------------------------------
                                       24 : 2109326622
                                       25 : 1486099418
                                       26 : 1892448847
...