VLT
VLT

Reputation: 195

Determininistic random numbers in parallel code

I have a question regarding thread ordering for the TPL. Indeed, it is very important for me that my Parallel.For loop to be executed in the order of the loop. What I mean is that given 4 threads, i would like the first thread to execute every 4k loop, 2nd thread every 4k+1 etc with (k between 0 and, NbSim/4).

1st thread -> 1st loop, 2nd thread -> 2nd loop , 3rd thread -> 3rd loop 4th thread -> 4th loop , 1th thread -> 5th loop etc ...

I have seen the OrderedPartition directive but I am not quite sure of the way I should apply it to a FOR loop and not to a Parallel.FOREACH loop.

Many Thanks for your help.

Follwing the previous remkarks, I am completing the description :

Actually, after some consideration, I believe that my problem is not about ordering. Indeed, I am working on a Monte-Carlo engine, in which for each iteration I am generating a set of random numbers (always the same (seed =0)) and then apply some business logic to them. Thus everything should be deterministic and when running the algorithm twice I should get the exact same results. But unfortunately this is not the case, and I am strugeling to understand why. Any idea, on how to solve that kind of problems (without printing out every variable I have)?

Edit Number 2:

Thank you all for your suggestions First, here is the way my code is ordered :

ParallelOptions options  = new ParallelOptions();
options.MaxDegreeOfParallelism = 4; //or 1

ParallelLoopResult res = Parallel.For<LocalDataStruct>(1,NbSim, options,
                    () => new LocalDataStruct(//params of the constructor of LocalData),
(iSim, loopState, localDataStruct) => {
    //logic
    return localDataStruct;
}, localDataStruct => {
   lock(syncObject) {
  //critical section for outputting the parameters
});

When setting the degreeofParallelism to 1,everything works fine, however when setting the degree of Parallelism to 4 I am getting results that are false and non deterministic (when running the code twice I get different results). It is probably due to mutable objects that is what I am checking now, but the source code is quite extensive, so it takes time. Do you think that there is a good strategy to check the code other than review it (priniting out all variables is impossible in this case (> 1000)? Also when setting the Nb of Simulation to 4 for 4 threads everything is working fine as well, mostly due to luck I believe ( that s why I metionned my first idea regarding ordering).

Upvotes: 1

Views: 989

Answers (3)

Ade Miller
Ade Miller

Reputation: 13753

You can enforce ordering in PLINQ but it comes at a cost. It gives ordered results but does not enforce ordering of execution.

You really cannot do this with TPL without essentially serializing your algorithm. The TPL works on a Task model. It allows you to schedule tasks which are executed by the scheduler with no guarantee as to the order in which the Tasks are executed. Typically parallel implementations take the PLINQ approach and guarantee ordering of results not ordering of execution.

Why is ordered execution important?

So. For a Monte-Carlo engine you would need to make sure that each index in your array received the same random numbers. This does not mean that you need to order your threads, just make the random numbers are ordered across the work done by each thread. So if each loop of your ParallelForEach was passed not only the array of elements to do work on but also it's own instance of a random number generator (with a different fixed seed per thread) then you will still get deterministic results.

I'm assuming that you are familiar with the challenges related to parallelizing Monte-Carlo and generating good random number sequences. If not here's something to get you started;Pseudo-random Number Generation for Parallel Monte Carlo—A Splitting Approach, Fast, High-Quality, Parallel Random-Number Generators: Comparing Implementations.

Some suggestions

I would start off by ensuring that you can get deterministic results in the sequential case by replacing the ParallelForEach with a ForEach and see if this runs correctly. You could also try comparing the output of a sequential and a parallel run, add some diagnostic output and pipe it to a text file. Then use a diff tool to compare the results.

If this is OK then it is something to do with your parallel implementation, which as is pointed out below is usually related to mutable state. Some things to consider:

  • Is your random number generator threadsafe? Random is a poor random number generator at best and as far as I know is not designed for parallel execution. It is certainly not suitable for M-C calculations, parallel or otherwise.

  • Does your code have other state shared between threads, if so what is it? This state will be mutated in a non-deterministic manner and effect your results.

  • Are you merging results from different threads in parallel. The non-associativity of parallel floating point operations will also cause you issues here, see How can floating point calculations be made deterministic?. Even if the thread results are deterministic if you are combining them in a non deterministic way you will still have issues.

Upvotes: 1

Dax Fohl
Dax Fohl

Reputation: 10781

If, rather than using a random number generator, you set up an array [0...N-1] of pre-determined values, say [0, 1/N, 2/N, ...], and do a Parallel.ForEach on that, does it still give nondeterministic results? If so, the RNG isn't the issue.

Upvotes: 0

Asik
Asik

Reputation: 22133

Assuming all threads share the same random number generator, then although you are generating the same sequence every time, which thread gets which elements of this sequence is non-deterministic. Hence you could arrive at different results.

That's if the random number generator is thread-safe; if it isn't, then it's not even guaranteed to generate the same sequence when called from multiple threads.

Apart from that it is difficult to theorize what could be causing non-determinism to arise; basically any global mutable state is suspicious. Each Task should be working with its own data.

Upvotes: 0

Related Questions