随机存取容器
最简单的解决方案是将所有内容放入一个随机访问容器(如std::vector
)并使用 OpenMP 青睐的基于索引的循环:
// Copy elements
std::vector<size_t> neListVector(mesh->NEList[vid].begin(), mesh->NEList[vid].end());
// Process in a standard OpenMP index-based for loop
#pragma omp parallel for reduction(min : worst_q)
for (int i = 0; i < neListVector.size(); i++) {
worst_q = std::min(worst_q, complexCalc(neListVector[i]));
}
除了非常简单之外,在您的情况下(size_t
可以轻松复制的微小类型元素),这也是具有最佳性能和可扩展性的解决方案。
避免复制
但是,在与您的情况不同的情况下,您可能拥有不容易复制的元素(较大的元素)或根本无法复制。在这种情况下,您可以将相应的指针放入随机访问容器中:
// Collect pointers
std::vector<const nonCopiableObjectType *> neListVector;
for (const auto &entry : mesh->NEList[vid]) {
neListVector.push_back(&entry);
}
// Process in a standard OpenMP index-based for loop
#pragma omp parallel for reduction(min : worst_q)
for (int i = 0; i < neListVector.size(); i++) {
worst_q = std::min(worst_q, mesh->element_quality(*neListVector[i]));
}
这比第一个解决方案稍微复杂一些,在小元素上仍然具有相同的良好性能,并且在较大元素上具有更高的性能。
任务和动态调度
由于其他人在他的回答中提出了 OpenMP 任务,我想对此发表评论。任务是一个非常强大的结构,但它们有巨大的开销(甚至随着线程数量的增加而增加),在这种情况下只会让事情变得更加复杂。
为了min
减少使用 Tasks 是不合理的,因为在主线程中创建一个 Task 的成本远远超过了std::min
它本身!
对于更复杂的操作mesh->element_quality
,您可能认为 Tasks 的动态特性可以帮助您解决负载平衡问题,以防mesh->element_quality
迭代之间的执行时间差异很大并且您没有足够的迭代来平衡它。但即使在这种情况下,也有一个更简单的解决方案:只需在我以前的解决方案之一中将schedule(dynamic)
指令添加到您的行中,即可使用动态调度。parallel for
它实现了相同的行为,但开销要少得多。