【问题标题】:Sampling conditional distribution OpenMP采样条件分布 OpenMP
【发布时间】:2020-08-28 12:03:18
【问题描述】:

我有一个抽取随机样本的函数:

Sample sample();

我有一个检查天气样本是否有效的功能:

bool is_valid(Sample s);

这模拟了条件分布。现在我想要很多有效的样本(大多数样本都无效)。

所以我想用 openMP 并行化这段代码

vector<Sample> valid_samples;
while(valid_samples.size() < n) {
    Sample s = sample();
    if(is_valid(s)) {
        valid_samples.push_back(s);
    }
}

我该怎么做?我发现的大部分 OpenMP 代码都是简单的 for 循环,其中迭代次数在一开始就确定了。

sample() 函数有一个

thread_local std::mt19937_64 gen([](){
    std::random_device d;
    std::uniform_int_distribution<int> dist(0,10000);
    return dist(d);
}());

作为随机数生成器。如果我假设我的设备有随机源,是否有效并且线程保存?有没有更好的解决方案?

【问题讨论】:

    标签: c++ c++17 openmp


    【解决方案1】:

    您可以使用 OpenMP 任务并行。最简单的解决方案是将任务定义为单个样本插入:

    vector<Sample> valid_samples(n); // need to be resized to allow access in parallel
    
    void insert_ith(size_t i)
    {
      do {
        valid_samples[i] = sample();
      } while (!is_valid(valid_samples[i]));
    }
    
    #pragma omp parallel
    {
      #pragma omp single
      {
        for (size_t i = 0; i < n; i++) 
        {
          #pragma omp task
          insert_ith(i);
        }
      }
    }
    

    请注意,这种单任务单插入映射可能存在性能问题。首先,会涉及虚假共享,但更糟糕的是,任务管理有一些开销,这对于非常小的任务可能很重要。在这种情况下,补救措施很简单——不是每个任务一次插入,而是一次插入多个项目,例如 100。通常,一个合适的数字是一种权衡:越低创建更多任务 = 更多开销,越高可能会导致负载平衡变差。

    【讨论】:

    • 在我的情况下,任务很大(不太可能有效),但错误共享似乎是一个问题(示例是 std::vector,所以 24 字节)。我将其更改为在任务中使用局部变量。基准测试有点困难,因为运行时间取决于随机数生成器的种子。
    • 您能否对问题的第二部分发表评论。您将如何播种随机数生成器?
    • 为什么 omp parallelomp single 没有取消有效?
    • @Unlikus 阅读有关 OpenMP 任务的内容。是的,它们正在取消,但仅用于生成任务。然后,所有线程并行解决任务。
    • @Unlikus 评论什么?如何播种?有办法,只要搜索 Google 或 StackOverflow。例如,您可以将线程号合并到种子中,或者为不同的线程创建一个种子向量并通过std::random_device 对其进行初始化。
    【解决方案2】:

    您需要注意代码中的关键部分,即插入到答案向量中

    这样的东西应该可以工作(没有编译,因为没有给出函数和类型)

    // create vector before parallel section because it shall be shared
    vector<Sample> valid_samples(n); // set initial size to avoid reallocation
    int reached_count = 0;
    #pragma omp parallel shared(valid_samples, n, reached_count)
    {
        while(reached_count < n) { // changed this, see comments for discussion
            Sample s = sample(); // I assume this to be thread indepent
            if(is_valid(s)) {
                #pragma omp critical
                {
                    // check condition again, another thread might have already
                    // reached maximum number
                    if(reached_count < n) {
                        valid_samples.push_back(s);
                        reached_count = valid_samples.size();
                    }
                }
            }
        }
    }
    

    请注意,sample()isvalid(s) 都不在临界区之内,因为我认为这些函数比向量插入要昂贵得多,或者接受率非常低

    如果不是这种情况,您可以使用独立的局部向量并最终合并,但只有以某种方式减少同步次数(例如提供固定次数的迭代)才能获得显着的好处(至少在很大程度上)

    【讨论】:

    • valid_samples.size() 没有同步的读取大小是数据竞争
    • 我知道这一点,但我接受了它,因为在最坏的情况下,它过于频繁地运行一次迭代(每个线程),这没有影响,因此它在重要部分中被检查结束。对于许多迭代,循环迭代次数的差异可以忽略不计,这应该比同步请求节省更多时间
    • 您接受数据竞赛吗?即使它们在您的程序中导致未定义的行为?
    • @schetefan24 std::vector.size() 在许多实现中减去两个指针,因此在重定位和数据竞争的情况下它可能不会增加。 UB 意味着 UB 并不是它会在一组有限的可能结果之间进行选择。
    • @schetefan24 我们需要生成n 任务。您可以通过单个线程(如我的情况)或并行执行此操作,但每个线程只需要生成任务的专有子集。
    猜你喜欢
    • 2020-11-20
    • 2020-10-07
    • 1970-01-01
    • 2014-02-01
    • 2018-10-04
    • 1970-01-01
    • 2020-12-18
    • 2021-03-04
    • 2020-04-28
    相关资源
    最近更新 更多