c++ how to elegantly use c++17 parallel execution with for loop that counts an integer?

泄露秘密 提交于 2021-02-16 10:48:49

问题


I can do

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

but is there a more elegant way of creating a parallelized version of for(int i=0; i<n; i++) that does not require me to first fill a vector with ascending ints?


回答1:


You could use std::generate to create a vector {0, 1, ..., 999}

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

There is an overload that accepts an ExecutionPolicy so you could modify the above to

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



回答2:


Although I can't suggest a way to avoid filling a vector, I can recommend using the std::iota() function as (perhaps) the most efficient/elegant way to fill it with incrementing integers:

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

The complexity of std::iota is exactly last - first increments and assignments, whereas the std::generate function has a complexity of last - first invocations of g() and assignments. Even if a decent compiler were to inline a simple increment lambda function for g, the iota syntax is considerably simpler, IMHO.




回答3:


Here are two ways to do it without pre-populating a vector just to store a sequence of integers.

  1. You can do it with Boost.counting_range (or directly using Boost.counting_iterator as you prefer) ... although good luck finding out how from reading the documentation.

     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. If you don't want to include Boost, we can write a simple version directly.

    With no apology for munging iota and iterator together instead of coming up with a decent name, the below will let you write something similar to the Boost version above:

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

    You can see how much boilerplate you save by using Boost for this:

     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. For posterity, and in case you can use C++20 in the future, std::ranges::iota_view will preferable where available.




回答4:


VisualC++ provides a rich parallel programming enviromnent, concurrency runtime ConCRT.
You can use OpenMP, which is open standard but also available in ConCRT. As described on wikipedia it is embarrassingly parallel, following code is supposed to create 1000 threads:

#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 ...
}

The #pragma omp directives are ignored if compiler option /openmp is not specified. In fact I don't understand the role of your vector, so I omitted it. Also I don't understand the reasoning behind the replacing of the standard for with any for_each and work with saved indexes, since for loop does it pretty well.
Or you can use Microsoft specific library PPL. Following code also creates 1000 threads, generating indexes from 0 to 999 inclusive and passing to parallel routine as s variable:

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

For heavy parallel computations there is also AMP available in concurrency runtime. AMP does the parallel routines on GPU instead of CPU.



来源:https://stackoverflow.com/questions/63340193/c-how-to-elegantly-use-c17-parallel-execution-with-for-loop-that-counts-an-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!