我依赖于我的程序中的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 逻辑的实施方式回答。我希望得到标准的支持。
答案 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以获得更详细的解释。