我有一个并行代码,可以基本上简化为:
#include <algorithm>
#include <vector>
struct TKeyObjPtr;
class TObj
{
public:
virtual void Calculate(TKeyObjPtr const &) = 0;
};
struct TKeyObjPtr
{
int Key;
TObj *ObjPtr;
};
void Calculate(std::vector<TKeyObjPtr> const &KeyObjPtrVec)
{
#pragma omp parallel for
for (auto It1= KeyObjPtrVec.begin(); It1!=KeyObjPtrVec.end(); ++It1)
for (auto It2= It1+1; It2!=KeyObjPtrVec.end() && It2->Key==It1->Key; ++It2)
It1->ObjPtr->Calculate(*It2);
}
我想通过使用 c++17 并行算法来现代化该代码。 不幸的是,我在重写这么简单的代码时遇到了麻烦。
boost::counting_iterator
:
void Calculate(std::vector<TKeyObjPtr> const &KeyObjPtrVec)
{
std::for_each(std::execution::par_unseq,
boost::counting_iterator<std::size_t>(0u),
boost::counting_iterator<std::size_t>(KeyObjPtrVec.size()),
[&KeyObjPtrVec](auto i)
{
for (auto j= i+1; j<KeyObjPtrVec.size() && KeyObjPtrVec[j].Key==KeyObjPtrVec[i].Key; ++j)
KeyObjPtrVec[i].ObjPtr->Calculate(KeyObjPtrVec[j]);
});
}
这可行,但相当冗长,更糟糕的是,我认为它不符合 标准,因为
boost::counting_iterator
是一个隐藏迭代器,因此不
满足Cpp17ForwardIterator要求。
是否可以像使用 OpenMP 一样简洁地编写上述代码,同时满足 标准对并行算法的约束?
如果我的算法正确,我们必须
KeyObjPtrVec
矢量,Key
字段匹配,我们必须在结构体的 Calculate
字段上调用 Obj
。C++17 具有出色的内置并行化支持,这可以通过标准库轻松完成:
#include <algorithm>
#include <execution>
void Calculate(std::vector<TKeyObjPtr> const &KeyObjPtrVec) {
std::vector<size_t> indices(KeyObjPtrVec.size());
for (size_t k = 0; k < indices.size(); k++) {
indices[k] = k;
}
std::for_each(std::execution::par, indices.begin(), indices.end(),
[KeyObjPtrVec, indices](size_t k) {
std::for_each(std::execution::par, indices.begin() + k + 1, indices.end(),
[k, KeyObjPtrVec](size_t l) {
if (KeyObjPtrVec[k].Key == KeyObjPtrVec[l].Key) {
KeyObjPtrVec[k].ObjPtr->Calculate(KeyObjPtrVec[l]);
}
});
});
}
如果指定
std::execution::par
作为 std::for_each
的第一个参数,则并行版本运行。无需关心执行程序、线程同步或任何清理 - 所有这些都可以开箱即用。只需链接库 tbb
(用于 cmake 的 target_link_libraries(myexecutable tbb)
)。
使用 std::for_each 你可以访问你应该处理的元素,而不是迭代器。为了获得设置第二个
std::for_each
所需的更多自由访问权限,我们创建索引数组并对其进行迭代。有了元素的索引,我们就可以在两个 lambda 中的任何相对位置访问数组。
此代码经过测试并确认可以按照答案开头所述工作。