使用 C++17 算法并行化简单循环

问题描述 投票:0回答:1

我有一个并行代码,可以基本上简化为:

#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);
}

我想通过使用 并行算法来现代化该代码。 不幸的是,我在重写这么简单的代码时遇到了麻烦。

一个选项是使用

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 一样简洁地编写上述代码,同时满足 标准对并行算法的约束?

c++ iterator c++17 stl-algorithm
1个回答
0
投票

如果我的算法正确,我们必须

  • 迭代结构向量中所有可能的值对,前提是
    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 中的任何相对位置访问数组。

此代码经过测试并确认可以按照答案开头所述工作。

© www.soinside.com 2019 - 2024. All rights reserved.