在并行算法中使用range :: view :: iota

时间:2018-07-05 07:51:19

标签: c++ c++17 stl-algorithm range-v3 c++20

由于parallel for algorithm中没有基于索引的,我想知道ranges::view::iota是否可以与std::for_each结合使用来模拟。那就是:

using namespace std;

constexpr int N= 10'000'000;
ranges::iota_view indices(0,N);
vector<int> v(N);

for_each(execution::par_unseq,indices.begin(),indices.end(),[&](int i) { v[i]= i; });

iota_view似乎提供了对适当类型([range.iota.iterator])的随机访问:

  

iota_view<I, Bound>::iterator::iterator_category的定义如下:

     

(1.1)—如果IAdvanceable建模,则iterator_categoryrandom_access_iterator_tag

     

(1.2)—否则,如果IDecrementable建模,则iterator_categorybidirectional_iterator_tag

     

(1.3)—否则,如果IIncrementable建模,则iterator_categoryforward_iterator_tag

     

(1.4)-否则,iterator_categoryinput_iterator_tag

以上代码正确吗?以这种方式使用iota_view有性能上的损失吗?


编辑:我已经用range-v3cmcstl2和Intel的PSTL做过一些测试。

使用range-v3,以上示例无法在GCC 8上编译。编译器抱怨beginend具有不同的类型:

deduced conflicting types for parameter ‘_ForwardIterator’ (‘ranges::v3::basic_iterator<ranges::v3::iota_view<int, int> >’ and ‘ranges::v3::default_sentinel’)

使用cmcstl2,代码可以干净地编译,但不能并行运行。在我看来,它回退到了顺序版本,可能是因为某种程度上无法满足前向迭代器的要求(https://godbolt.org/z/yvr-M2)。

存在与PSTL有关的问题(https://github.com/intel/parallelstl/issues/22)。

2 个答案:

答案 0 :(得分:0)

深入研究标准草案后,恐怕答案是否定的:它并非严格符合标准 ranges::iota_view的并行版本中的for_each

for_each的并行重载被声明为[alg.foreach]

template<class ExecutionPolicy, class ForwardIterator, class Function>
  void for_each(ExecutionPolicy&& exec,
                ForwardIterator first, ForwardIterator last,
                Function f);

另一方面,在[algorithms.requirements]中我们找到了约束:

  

如果算法的模板参数名为ForwardIteratorForwardIterator1ForwardIterator2,则模板参数应满足 Cpp17ForwardIterator 的要求。

正如Billy O'Neal在我在问题中发布的链接之一中指出的那样,明智地实施ranges::iota_view::iterator不太可能满足“相等迭代器引用同一对象”的正向迭代器要求{{3 }}。因此,据我了解,ranges::iota_view::iterator无法满足 Cpp17ForwardIterator 的要求,例如 boost::counting_iterator

但是,实际上,我希望实现会使用std::iterator_traits::iterator_category来调度 适当的算法重载,如PSTL所做的那样。因此,我相信OP中的示例代码将按预期工作。 cmcstl2不起作用的原因可能是所使用的iterator_category属于[iterator.cpp17],而不是std

答案 1 :(得分:0)

在C ++ 20中,有一个std::views::common,其范围适用于标准的迭代器对接受算法。将输入范围转换为std::ranges::common_range后,请使用std::ranges::beginstd::ranges::end函数为std::transform或您使用的任何算法获取一对迭代器。

这是一个示例程序,假定使用C ++ 20编译器(这不是基于ranges-v3的实现,不是)。我测试过的唯一一个(截至2020年10月)是G ++版本10。

#include <algorithm>
#include <numeric>
#include <execution>
#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    // A "large" number of elements (limited to ten for a reasonably small std::cout output)
    constexpr int N = 10;

    // Some range with a finite number of values (views::take at the end)
    auto very_long_input_range = std::views::iota(0) | std::views::take(N);

    // Source range converted to common_range (which supports std::begin & std::end)
    auto input_range = std::ranges::common_view(very_long_input_range);

    // Element processing function. E.g., if 'i' is a file name and this lambda parses it, it might be a big time-saver
    auto some_complex_function = [](auto i){ return i * i; };

    // Declare and allocate an output array (maybe range_value_t is an overkill here, but still)
    // Using std::ranges::size(input_range) instead of N can also help generalize this code,
    // but input_range must satisfy the std::ranges::sized_range concept
    std::vector< std::ranges::range_value_t<decltype(input_range)> > output_array( N );

    // Use C++17 std::execution::par with a pair of C++20 iterators from std::ranges
    std::transform(std::execution::par,
            std::ranges::begin(input_range),
            std::ranges::end(input_range),
            output_array.begin(),
            some_complex_function);

    // Test the output
    for (auto p: output_array)
            std::cout << p << std::endl;
}

G ++ 10(Ubuntu 20.20)的命令行为

g++-10 -std=c++2a -o ptest ptest.cpp -ltbb -lstdc++