并行代码中的确定性随机数

本文关键字:确定性 随机数 代码 并行 | 更新日期: 2023-09-27 17:49:32

我有一个关于TPL线程排序的问题。的确,这对我来说非常重要,我的平行。For循环按照循环的顺序执行。我的意思是给定4个线程,我希望第一个线程执行每4k循环,第二个线程执行每4k+1等(k在0和NbSim/4之间)。

第一个线程->第一个循环,第二个线程->第二个循环,第三个线程->第三个循环第4个线程->第4个循环,第1个线程->第5个循环等等…

我已经看到了OrderedPartition指令,但我不太确定我应该将其应用于FOR循环而不是并行的方式。FOREACH循环。

谢谢你的帮助。

根据前面的注释,我正在完成描述:

实际上,经过一番考虑,我认为我的问题不是订购。实际上,我正在使用蒙特卡罗引擎,在每次迭代中,我生成一组随机数(总是相同的(seed =0)),然后对它们应用一些业务逻辑。因此,一切都应该是确定性的,当运行算法两次时,我应该得到完全相同的结果。但不幸的是,事实并非如此,我正在努力理解其中的原因。任何想法,如何解决这种问题(不打印出每一个变量我有)?

编辑号2:

谢谢大家的建议首先,我的代码是这样排列的:

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
});

当设置并行度为1时,一切都很好,但是当设置并行度为4时,我得到的结果是假的和不确定的(当运行代码两次时,我得到不同的结果)。这可能是由于可变对象,这是我现在正在检查,但源代码是相当广泛的,所以需要时间。你是否认为有一个好的策略来检查代码,而不是审查它(打印出所有的变量是不可能的,在这种情况下(> 1000)?同样,当将模拟的Nb设置为4时,4个线程的一切都工作得很好,我相信这主要是由于运气(这就是为什么我提到了我关于排序的第一个想法)。

并行代码中的确定性随机数

您可以在PLINQ中强制排序,但这是有代价的。它给出顺序的结果,但不强制执行顺序。

如果不序列化你的算法,你真的不能在TPL中做到这一点。TPL在任务模型上工作。它允许您调度由调度器执行的任务,而不保证任务执行的顺序。典型的并行实现采用PLINQ方法,保证结果的顺序而不是执行的顺序。

为什么有序执行很重要?

。对于蒙特卡罗引擎,您需要确保数组中的每个索引接收到相同的随机数。这并不意味着您需要对线程进行排序,只需使随机数在每个线程完成的工作中排序即可。因此,如果您的ParallelForEach的每个循环不仅传递了要做工作的元素数组,而且还传递了它自己的随机数生成器实例(每个线程具有不同的固定种子),那么您仍然会得到确定性结果。

我假设您熟悉与并行蒙特卡罗和生成好的随机数序列相关的挑战。如果没有,这里有一些东西可以让你开始;伪随机数生成并行蒙特卡罗-一种分裂方法,快速,高质量,并行随机数生成器:比较实现。

我将首先确保您可以通过用ForEach替换ParallelForEach来获得顺序情况下的确定性结果,并查看这是否正确运行。您还可以尝试比较顺序运行和并行运行的输出,添加一些诊断输出并将其管道传输到文本文件中。然后使用diff工具比较结果。

如果这是OK的,那么它与你的并行实现有关,正如下面指出的,它通常与可变状态有关。一些需要考虑的事情:

  • 你的随机数生成器线程安全吗?Random充其量是一个可怜的随机数生成器,据我所知,它不是为并行执行而设计的。它当然不适合M-C计算,并行或其他。

  • 你的代码有线程之间共享的其他状态,如果有,是什么?此状态将以不确定的方式发生突变,并影响您的结果。

  • 是否在并行合并来自不同线程的结果?并行浮点运算的非结合性也会给您带来问题,请参阅如何使浮点计算具有确定性?即使线程结果是确定的,如果你以非确定的方式组合它们,你仍然会有问题。

假设所有线程共享相同的随机数生成器,那么尽管每次都生成相同的序列,但哪个线程获取该序列的哪些元素是不确定的。因此,你可以得到不同的结果。

如果随机数生成器是线程安全的,那么

就是;如果不是,那么当从多个线程调用时,它甚至不能保证生成相同的序列。

除此之外,很难从理论上解释是什么导致了非决定论的产生;基本上,任何全局可变状态都是可疑的。每个Task都应该使用自己的数据。

如果不使用随机数生成器,而是设置一个数组[0…N-1]的预设值,例如[0,1/N, 2/N,…]],然后做一个Parallel.ForEach,它是否仍然给出不确定的结果?如果是这样,RNG就不是问题了。