在C++对象数组上使用OpenMP for循环

3

我正在运行一个模拟程序,其中需要生成许多随机数。随机数发生器是作为C++对象实现的,该对象具有返回随机数的公共方法。为了与OpenMP并行化一起使用,我只需创建一个包含多个RNG对象的数组,每个线程都有一个RNG对象。然后每个线程通过调用其中一个RNG对象来生成自己的随机数。例如:

  for (int i = 0; i < iTotThreads; i++) {
    aRNG[i] = new RNG();
  }
  // ... stuff here
#pragma omp parallel 
  {
    iT = omp_get_thread_num();
#pragma omp for
    for ( /* big loop */) {
      // more stuff
      aRNG[iT]->getRandomNumber();
      // more stuff
    }
  }  

尽管每个随机数生成器(RNG)都在其自己的成员变量上工作,并且两个这样的RNG不适合单个缓存行(我还尝试在创建时明确对齐它们的每个变量),但似乎存在一些错误共享,因为代码根本不具有可扩展性。
如果我在omp并行区域内实例化对象:
#pragma omp parallel
  { 
    i = omp_get_thread_num();
    aRNG[i] = new RNG();
  }

代码可以完美地扩展。你有任何想法吗?

编辑:顺便说一下,在第二种情况(可以很好地扩展),我创建RNG的并行区域与我使用它们的并行区域不同。我指望当我进入第二个并行区域时,aRNG[]中的每个指针仍然指向我的对象之一,但我想这是不好的实践...


你在你的随机数生成器中使用任何全局变量(或静态变量)吗? - jcxz
结果在返回之前被规范化为“static const unsigned long MY_MAX_RAND”,但除此之外,每个随机数生成器只写入其自己的私有成员变量和数组。 - AstralCar
现今大多数内存分配器都是线程感知的,并使用单独的每个线程内存区域。尝试向您的PRNG状态添加一个虚拟填充变量,其大小为缓存行的大小,并确保编译器不会将其优化掉。 - Hristo Iliev
您正在运行 NUMA 系统。当您在主线程中分配 PRNG 时,所有状态向量都会结束在执行主线程的 NUMA 节点上,并且对于一半的线程,这些向量位于远程内存中(因此访问速度较慢)。当每个线程分配自己的 PRNG 时,内存分配在执行线程的同一节点上,因此访问是本地的。这在处理大型数据集时变得非常重要,因为您的 CPU 只有 8 MB 的最后一级缓存。另外,您是否绑定了线程? - Hristo Iliev
哇,感谢您的解释。这很有道理。目前我还没有绑定线程,但这对我来说是一个不错的选择。我更喜欢并行分配+线程绑定,而不是将PRNG作为线程专用(我需要将其声明为静态变量才能将其用作线程专用 - 它是另一个类的成员 - 我刚刚注意到这个选项会稍微减慢速度)。 - AstralCar
显示剩余6条评论
1个回答

4
尽管从你的描述来看,我对虚假共享是造成问题的原因存疑,但为什么不以这种方式简化代码呢:
  // ... stuff here
#pragma omp parallel 
  {
    RNG rng;
#pragma omp for
    for ( /* big loop */) {
      // more stuff
      rng.getRandomNumber();
      // more stuff
    }
  }

声明在parallel区域内的rng将成为一个具有自动存储期限的私有变量,因此:

  • 每个线程将拥有自己的私有随机数生成器(这里不会出现误共享)
  • 您无需管理资源的分配/释放

如果这种方法不可行,并且按照@HristoIliev的建议,您始终可以声明一个threadprivate变量来保存指向随机数生成器的指针:

static std::shared_pointer<RNG> rng;
#pragma omp threadprivate(rng);

将其分配给第一个并行区域:

rng.reset( new RNG );

在这种情况下,需要注意一些要点以确保rng的值在并行区域之间得以保留(引用OpenMP 4.0标准):
  • 只有满足以下所有条件,非初始线程中线程专有变量中数据的值才能在两个连续活动的并行区域之间保持不变:
    • 没有嵌套在另一个显式并行区域内。
    • 用于执行两个并行区域的线程数相同。
    • 用于执行两个并行区域的线程亲和策略相同。
    • 封闭任务区域中dyn-var内部控制变量在进入两个并行区域时的值为false。
  • 如果满足以上全部条件,并且线程专有变量在两个区域中都被引用,则具有相同线程号的线程将引用该变量的同一副本。

@AstralCar,声明static RNG* rng; <new line> #pragma omp threadprivate(rng);在第一个并行区域中分配它;在最后一个并行区域中删除它。 - Hristo Iliev
@HristoIliev 只是一个问题,在这种情况下,threadprivate 是否产生与 firstprivate 相同的效果? - lorniper
1
@lorniper,firstprivate 变量在并行区域之间不会保留。而 threadprivate 变量则会。此外,threadprivate 变量不像 firstprivate 那样被初始化,因为它们没有“父”变量可以获取值。 - Hristo Iliev
@HristoIliev 感谢您的回答,也许我应该在一个独立的问题中问,但是您能否更具体地说明“firstprivate变量在并行区域之间不会持久存在”? - lorniper
@HristoIliev,如果您能回答这个问题,我将不胜感激:http://stackoverflow.com/questions/32347008/confused-about-firstprivate-and-threadprivate-in-openmp-context - lorniper
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接