订购的OpenMP是否总是按顺序将部分循环分配给线程?

时间:2016-11-16 13:59:58

标签: c++11 openmp

背景

我依赖于我的程序中的OpenMP并行化和伪随机数生成,但同时我想使结果在需要时完全可复制(提供相同数量的线程)。

我正在分别为每个线程播种thread_local PRNG,

{
  std::minstd_rand master{};
  #pragma omp parallel for ordered
  for(int j = 0; j < omp_get_num_threads(); j++)
    #pragma omp ordered
    global::tl_rng.seed(master());
}

我已经提出了以下生成count某些元素的方法,并将它们全部放在一个数组中,最后以 deterministic 顺序排列(线程0的结果首先,线程1下一个等。)

std::vector<Element> all{};
...
#pragma omp parallel if(parallel)
{
  std::vector<Element> tmp{};
  tmp.reserve(count/omp_get_num_threads() + 1);

  // generation loop
  #pragma omp for
  for(size_t j = 0; j < count; j++)
    tmp.push_back(generateElement(global::tl_rng));

  // collection loop
  #pragma omp for ordered
  for(int j = 0; j < omp_get_num_threads(); j++)
    #pragma omp ordered
    all.insert(all.end(),
        std::make_move_iterator(tmp.begin()),
        std::make_move_iterator(tmp.end()));
}

问题

这似乎有效,但我不确定它是否可靠(阅读:便携式)。具体来说,例如,如果第二个线程早期完成了主循环的共享,因为它的generateElement()调用恰好快速返回,那么技术上是不是允许选择收集循环的第一次迭代?在我的编译器中没有发生,它始终是线程0执行j = 0,线程1按预期执行j = 1等。这是遵循标准还是允许编译器特定的行为?

我找不到ordered指令中的for子句,除了it is required,如果循环内部包含ordered directive。是否始终保证线程会在增加thread_num时从开始分割循环?它在推荐来源中的位置如何?或者我是否也必须使我的“生成”循环ordered - 当它中没有ordered指令时,它是否实际上有所不同(性能或逻辑方式)?

请不要根据经验或OpenMP 逻辑的实施方式回答。我希望得到标准的支持。

1 个答案:

答案 0 :(得分:2)

不,当前状态的代码不可移植。仅当默认循环调度为static时才会起作用,即迭代空间被划分为count / #threads个连续的块,然后按照其线程ID的顺序分配给线程,并保证映射之间的映射块和线程ID。但是OpenMP规范没有规定任何默认的时间表,而是留给实现选择一个。许多实现都使用static,但并不总是保证这种情况。

如果将schedule(static)添加到所有循环结构中,那么每个循环体内的ordered子句和ordered构造的组合将确保线程0将接收第一个迭代块,也将是第一个执行ordered构造的迭代。对于在线程数上运行的循环,块大小将为1,即每个线程将执行恰好一次迭代,并行循环的迭代次序将与顺序循环的次序匹配。迭代次数到static计划完成的线程ID的1:1映射将确保您的目标行为。

请注意,如果初始化线程本地PRNG的第一个循环位于不同的并行区域,则必须确保两个并行区域使用相同数量的线程执行,例如,通过禁用动态团队大小调整({ {1}})或明确应用omp_set_dynamic(0);子句。

关于num_threads子句+构造的重要性,它不会影响迭代到线程的分配,但它会同步线程并确保物理执行顺序与逻辑顺序相匹配。没有ordered子句的静态调度循环仍将为线程0分配迭代0,但不能保证某个其他线程不会在线程0之前执行其循环体。此外,任何代码都在仍然允许ordered构造之外的循环体同时执行并且不按顺序执行 - 请参阅here以获得更详细的解释。