为什么随机数生成器在并行执行时会出现问题



我有一个任务,提到了以下内容:

并行性增加了更多的复杂性,因为随机数当两个或多个线程调用时,生成器函数必须正确运行同时,也就是说,它必须是线程安全的。

然而,我不明白为什么这是一个问题。随机生成器通常以种子作为参数进行调用,并通过对其进行多次操作来输出数字。我知道我们需要每个线程使用不同的种子,但除此之外,问题从哪里来?我已经意识到,在并行区域而不是串行区域中生成和调用随机生成器也确实会降低性能,但我不明白为什么会发生这种情况,因为从外观上看,随机数生成器应该同时运行,没有任何问题,因为它们没有依赖关系。

任何有助于理解这背后的理论的人都将不胜感激。

除了从竞赛条件中得到错误的值(由@MitchWheat指出(外,由于主流x86处理器上内核之间的缓存线共享,代码的效率将降低。

下面是一个(相当糟糕但很简单(32位随机生成器(用C编写(的例子:

uint32_t seed = 0xD1263AA2;
uint32_t customRandom() {
uint32_t old = seed;
seed = (uint32_t)(((uint64_t)seed * 0x2E094C40) >> 24);
return old;
}
void generateValues(uint32_t* arr, size_t size) {
for(size_t i=0 ; i<size ; ++i)
arr[i] = customRandom();
}

如果您按顺序运行此示例(请参阅此处的结果(,则状态seed可能会使用主流编译器(即GCC和Clang(存储在内存层次结构中。这个32位的内存块将被读取/写入L1缓存中,该缓存非常靠近执行代码的核心。

当您天真地并行化循环时,例如在OpenMP中使用#pragma omp parallel for,状态由多个线程同时读取/写入。有一个竞争条件:状态值seed可以由多个线程并行读取,也可以并行写入。因此,多个线程可以生成相同的值,而结果应该是随机的。赛道状况不好,必须修复。您可以在此处使用线程本地状态来修复此问题。

假设您没有修复代码,因为您想了解竞争条件对该代码最终性能的影响,那么您应该会看到性能同时下降。这个问题来自x86主流处理器使用的缓存一致性协议。事实上,seed在每个内核上执行的所有线程之间共享,因此处理器将尝试同步内核的缓存,以便它们保持一致。这个过程非常昂贵(比一级缓存中的读/写慢得多(。更具体地,当给定核上的线程写入seed时,处理器使存储在位于其他核的高速缓存中的所有其他线程中的seed无效。然后,当读取seed时,每个线程都必须获取更新的seed(通常来自速度慢得多的L3缓存(。你可以在这里找到更多信息。

最新更新