【问题标题】:c++ how to elegantly use c++17 parallel execution with for loop that counts an integer?c++ 如何优雅地使用 c++17 并行执行和计算整数的 for 循环?
【发布时间】:2020-08-10 12:26:43
【问题描述】:

我可以的

std::vector<int> a;
a.reserve(1000);
for(int i=0; i<1000; i++)
    a.push_back(i);
std::for_each(std::execution::par_unseq, std::begin(a), std::end(a), [&](int i) {
  ... do something based on i ...
});

但是有没有更优雅的方法来创建不需要我首先用升序整数填充向量的 for(int i=0; i

【问题讨论】:

    标签: c++ parallel-processing c++17


    【解决方案1】:

    您可以使用std::generate 创建矢量{0, 1, ..., 999}

    std::vector<int> v(1000);
    std::generate(v.begin(), v.end(), [n = 0] () mutable { return n++; });
    

    有一个接受ExecutionPolicy 的重载,因此您可以将上面的内容修改为

    std::vector<int> v(1000);
    std::generate(std::execution::par, v.begin(), v.end(), [n = 0] () mutable { return n++; });
    

    【讨论】:

    • 这已经更优雅了,谢谢(尽管我怀疑编译器可以并行化它吗?)!但我想知道是否有可能不必首先填充向量?是否有一些迭代器可以在程序上生成整数,并且可以与 std::for_each 一起使用?
    • 我认为一旦 C++20 出来,你可以使用 ranges 来完成类似的事情。
    • boost::counting_range 已存在多年
    • Thrust 一直拥有counting iterators。不幸的是,花哨的迭代器概念没有成为标准
    【解决方案2】:

    虽然我不能建议避免填充向量的方法,但我可以推荐使用 std::iota() 函数作为(也许)最有效/最优雅的方式来填充递增整数:

    std::vector<int> a(1000);
    std::iota(std::begin(a), std::end(a), 0);
    std::for_each(std::execution::par_unseq, std::begin(a), std::end(a), [&](int i) {
      // ... do something based on i ...
    });
    

    std::iota 的复杂度正好是last - first 增量 和赋值,而std::generate 函数的复杂度是last - first g() 的调用和任务。即使一个体面的编译器要为 g 内联一个简单的增量 lambda 函数,iota 的语法也要简单得多,恕我直言。

    【讨论】:

      【解决方案3】:

      VisualC++ 提供丰富的并行编程环境,并发运行时ConCRT
      您可以使用 OpenMP,它是开放标准,但在 ConCRT 中也可用。如wikipedia 所述,它是embarrassingly parallel,下面的代码应该创建 1000 个线程:

      #include <omp.h>
      ...
      #pragma omp parallel for
      for(int s = 0; s < 1000; s++)
      {
          for(int i = 0; i < s; i++)
              ... do something parallel based on i ...
      }
      

      如果未指定编译器选项 /openmp,则忽略 #pragma omp 指令。 其实我不明白你的向量的作用,所以我省略了它。另外,我不明白用任何 for_each 替换标准 for 并使用保存的索引的原因,因为 for 循环可以做到这一点挺好的。
      或者,您可以使用 Microsoft 特定库 PPL。以下代码还创建 1000 个线程,生成从 0 到 999 的索引,并作为 s 变量传递给并行例程:

      #include <ppl.h>
      ...
      using namespace concurrency;
      parallel_for(0, 1000, [&](int s)
      {
         for(int i = 0; i < s; i++)
            ... do something parallel based on i ...
      });
      

      对于繁重的并行计算,并发运行时还可以使用 AMP。 AMP 在 GPU 而不是 CPU 上执行并行例程。

      【讨论】:

        【解决方案4】:

        这里有两种方法可以做到这一点预先填充一个向量只是为了存储一个整数序列。

        1. 可以使用Boost.counting_range(或根据您的喜好直接使用Boost.counting_iterator)来实现...虽然通过阅读文档了解如何操作,但祝您好运。

           auto range = boost::counting_range<int>(0,1000);
           std::for_each(std::execution::par_unseq,
                         range.begin(),
                         range.end(),
                         [&](int i) {
                             //  ... do something based on i ...
                         });
          
        2. 如果你不想包含Boost,我们可以直接写一个简单的版本。

          没有为将iotaiterator 混在一起而不是想出一个像样的名字而道歉,下面将让您编写类似于上面的 Boost 版本的内容:

           std::for_each(std::execution::par_unseq,
                         ioterable<int>(0),
                         ioterable<int>(1000),
                         [&](int i) {
                           //  ... do something based on i ...
                         }
           );
          

          您可以看到使用 Boost 节省了多少样板代码:

           template <typename NumericType>
           struct ioterable
           {
               using iterator_category = std::input_iterator_tag;
               using value_type = NumericType;
               using difference_type = NumericType;
               using pointer = std::add_pointer_t<NumericType>;
               using reference = NumericType;
          
               explicit ioterable(NumericType n) : val_(n) {}
          
               ioterable() = default;
               ioterable(ioterable&&) = default;
               ioterable(ioterable const&) = default;
               ioterable& operator=(ioterable&&) = default;
               ioterable& operator=(ioterable const&) = default;
          
               ioterable& operator++() { ++val_; return *this; }
               ioterable operator++(int) { ioterable tmp(*this); ++val_; return tmp; }
               bool operator==(ioterable const& other) const { return val_ == other.val_; }
               bool operator!=(ioterable const& other) const { return val_ != other.val_; }
          
               value_type operator*() const { return val_; }
          
           private:
               NumericType val_{ std::numeric_limits<NumericType>::max() };
           };
          
        3. 为了后代,如果您将来可以使用 C++20,std::ranges::iota_view 将在可用的情况下更可取。

        【讨论】:

        • 至少在顺序情况下 Boostscounting_iterator 肯定是正确的工具。如果它不适用于并行执行,我建议使用带有 OMP 或 TBB 后端的 Thrust 库。它提供 STL 算法的并行实现以及诸如counting_iterator 之类的智能迭代器。
        • 其实,或许就足够了。该文档没有显示您需要将其用于主要 [begin,end) 范围的 operator==,但也许它就在那里,只是没有被提及。
        • 据我所知,这些文档并没有深入探讨(并且不包含快速参考?),因此最好只查看源代码。但是在没有 ```operator==`` 的情况下使用该迭代器将是一件非常小众的事情,对吧?
        • 你说的很对,但是我自己没用过,没有明确的文档记录,老实说,我看代码也不能很快弄明白。哦,我认为它是由朋友类中的宏生成的……呸
        猜你喜欢
        • 2020-10-30
        • 1970-01-01
        • 2019-06-28
        • 2021-08-18
        • 2011-06-13
        • 2021-12-22
        • 1970-01-01
        • 2021-03-04
        • 2018-08-12
        相关资源
        最近更新 更多