【问题标题】:Boost Random and OpenMP提升随机和 OpenMP
【发布时间】:2013-03-19 15:53:57
【问题描述】:

我从 OpenMP 并行代码部分收到“总线错误”。我在下面重新创建了我的问题的简单版本。该代码本质上对函数 uniform_distribution 进行了多次调用,该函数使用 Boost 的 uniform_int_distribution 绘制 0 到 20000 之间的整数。

post 警告两个线程访问同一个对象。我猜在我的情况下是eng。 (不幸的是,我不知道如何编写“适当的互斥体包装器”,正如那篇帖子所暗示的那样)。

我想到的一个可能的肮脏解决方案是在#pragma for 循环内创建一个本地eng 并将其作为参数传递给uniform_distribution。我不喜欢这个想法,因为在我的真实代码中,我调用了许多函数,并且传递本地 eng 会很麻烦。另外,我担心的是,如果我在uniform_distribution 中声明eng,不同的线程会生成相同 随机数序列。所以我有两个要求:如何以某种方式并行化

  1. 每个线程都在从其他线程生成概率独立的绘图?
  2. RNG 上没有出现竞争条件?

谢谢;非常感谢任何帮助。

#include <omp.h>
#include <boost/random/uniform_int_distribution.hpp>

boost::random::mt19937  eng;

int uniform_distribution(int rangeLow, int rangeHigh) {
    boost::random::uniform_int_distribution<int> unirv(rangeLow, rangeHigh);
    return unirv(eng);
}
int main()
{  
    # pragma omp parallel for private(eng)
    for (int bb=0; bb<10000; bb++)
        for (int i=0; i<20000; i++)
             int a = uniform_distribution(0,20000);

    return 0;
}

【问题讨论】:

    标签: c++ boost random openmp


    【解决方案1】:

    我认为最方便的解决方案是使用thread_local RNG 和将线程 ID 作为每个线程的唯一编号的种子,例如,您可以在系统时间和线程 ID 之间进行 XOR 以播种RNG。类似于(使用 C++11):

    #include <omp.h>
    #include <boost/random/uniform_int_distribution.hpp>
    
    #include <thread>
    #include <ctime>
    
    boost::random::mt19937& get_rng_engine() {
      thread_local boost::random::mt19937 eng(
        reinterpret_cast<unsigned int>(std::time(NULL)) ^ std::this_thread::get_id());
      return eng;
    };
    

    (注意:如果要使用 C++11,也可以使用 &lt;random&gt;

    如果您不能使用 C++11,那么您可以使用 boost::thread 来获得类似的行为,请参阅 thread-local storage 上的 Boost 页面。

    【讨论】:

      【解决方案2】:

      当您并行化某些代码时,您必须考虑共享资源,这可能会导致数据竞争,进而最终可能会破坏您的程序。 (注意:并非所有数据竞争都会破坏您的程序。)

      在您的情况下,正如您正确预期的那样,eng 是由两个或多个线程共享的,为了正确执行必须避免这种情况。

      您的情况的解决方案是私有化:为共享资源制作每个线程的副本。您需要创建一个单独的 eng 副本。

      eng 有多种私有化方法:

      (1) 尝试使用threadprivate 指令(link):例如#pragma omp threadprivate(eng)。但是,某些编译器可能不支持该指令的非 POD 结构。

      (2) 在threadprivate不可用的情况下,使用eng的数组并使用线程id访问:声明如eng[MAX_THREAD]。然后,使用线程 id 访问:eng[omp_get_thread()]

      但是,第二种解决方案需要考虑虚假共享,这会严重影响性能。最好保证eng[MAX_THREAD] 中的每个项目都分配在单独的缓存行边界上,在现代台式机CPU 中通常为64 字节。还有几种方法可以避免虚假共享。最简单的解决方案是使用填充:例如,char padding[x] 在包含 engstruct 中。

      【讨论】:

      • 私有子句实际上会创建一个变量的线程本地版本。与应用了 threadprivate 指令的变量不同,当线程加入时,它将被取消分配。但是,这在 OP 的示例中不是问题。 private 子句在这里不起作用的原因是,uniform_distribution 中引用的 eng 是全局的,而不是线程特定的。
      • 是的,您关于priavte 的观点是正确的。我删了一句话。但是,OP所面临的问题本质上是私有化问题。使线程本地化将是一个解决方案。您的第二个解决方案也是私有化的一种方式。
      • @minjang:感谢您的帮助。我尝试了 (1),但 threadprivate 抱怨 eng 参数。您能否详细说明(2),可能有一个例子?关于虚假分享的评论我恐怕不太明白。
      • 是的,对于某些 OpenMP 实现,threadprivate 仅获取 POD 类型。对于第二种解决方案,首先只需声明eng[MAX_THREAD],然后看看效果是否良好。然后,您可以稍后解决虚假共享问题。
      • @minjang:你的建议,再加上 Mikael Persson 的播种版本,可以解决问题。我现在对你的虚假分享评论很感兴趣。我的缓存行大小是 64,sizeof(eng[0]) 是 2504。这是否意味着虚假共享不会成为问题,还是我应该担心?谢谢!
      【解决方案3】:

      你有两个选择:

      • 每个线程都有单独的随机数生成器,并以不同的方式播种
      • 使用互斥

      先举个互斥的例子:

      # pragma omp parallel for
      for (int bb=0; bb<10000; bb++)
      {
          for (int i=0; i<20000; i++)
          {
              // enter critical region, disallowing simulatneous access to eng
              #pragma omp critical
              {
                  int a = uniform_distribution(0,20000);
              }
              // presumably some more code...
          }
          // presumably some more code...
      }
      

      接下来,一个带有种子的线程本地存储示例:

      # pragma omp parallel
      {
          // declare and seed thread-specific generator
          boost::random::mt19937 eng(omp_get_thread_num());
          #pragma omp for
          for (int bb=0; bb<10000; bb++)
          {
              for (int i=0; i<20000; i++)
              {
                  int a = uniform_distribution(0,20000, eng);
                  // presumably some more code...
              }
              // presumably some more code...
          }
      }
      

      这两个 sn-ps 都只是说明性的,根据您的要求(例如安全相关、游戏与建模),您可能希望选择其中一个。您可能还想更改确切的实现以适合您的使用。例如,如果您希望生成器可重复或更接近真正随机(是否可能是系统特定的),那么如何播种生成器很重要。这同样适用于两种解决方案(尽管在互斥情况下要获得可重复性更难)。

      线程本地生成器可能运行得更快,而互斥情况应该使用更少的内存。

      编辑:需要明确的是,互斥解决方案只有在随机数的生成不是线程工作的大部分时才有意义(即示例中的 // presumably some more code... 存在并且不'不要花费微不足道的时间来完成)。关键部分只需要包含对共享变量的访问,稍微改变你的架构可以让你更好地控制它(在线程本地存储的情况下,也可以让你避免传递eng 引用)

      【讨论】:

      • 虽然使用临界区提供了正确性,但它会有效地序列化代码,而不会提高性能。所以,对于这种简单的并行化情况,我不认为使用临界区是一个好的解决方案。
      • @jerry:谢谢你的回答。我都试过了;第一个解决了竞态条件,但没有带来性能提升(但可能是因为我的测试代码太简单了),这正是 minjang 预测的。您的第二个答案与 minjang 的相似,但它的行为似乎很奇怪。我将继续尝试您的第二种方法。
      • @covstat,关于 jerry 的第二个解决方案,请尝试将 private(eng) 放在 omp for 上。此代码仍将共享eng
      • @minjang 在这个示例代码中,这绝对是正确的。但是,OP 声明该示例是一个简化版本,我假设原始代码不仅仅计算循环中的随机数(这就是我所说的// presumably some more code...)。根据数量的多少,互斥的影响可能从无法测量到有效序列化。
      • @covstat 你看到了什么奇怪的结果?我相信eng 应该是私有的,因为它是在并行结构中声明的,但听起来您的实现可能存在非 POD 线程本地数据的问题。老实说,这不是我以前遇到过的事情,但显然 minjang 遇到过。您使用的是什么编译器/版本?它实现了哪个版本的 OpenMP 标准?另外,出于好奇,您究竟是如何修改 uniform_distribution 以获取生成器的?
      猜你喜欢
      • 1970-01-01
      • 2018-03-26
      • 2019-10-14
      • 2011-06-14
      • 2018-02-21
      • 1970-01-01
      • 1970-01-01
      • 2021-01-26
      • 2017-03-01
      相关资源
      最近更新 更多