9

由于在中没有基于索引的并行算法,我想知道是否可以结合使用来模拟它。那是:ranges::view::iotastd::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) — 如果I模型Advanceable,那么iterator_categoryrandom_access_iterator_tag

(1.2) — 否则,如果I模型Decrementableiterator_category则为bidirectional_iterator_tag

(1.3) — 否则,如果I模型Incrementableiterator_category则为forward_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)。

4

3 回答 3

4

深挖标准草案后,恐怕答案是否定的: ranges::iota_viewfor_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要求。

正如比利奥尼尔在我在问题中发布的一个链接中所指出的那样,合理的实现ranges::iota_view::iterator不太可能满足“相等的迭代器引用相同的对象”前向迭代器要求[iterator.cpp17]。因此,据我了解,ranges::iota_view::iterator不会满足Cpp17ForwardIterator要求,例如 boost::counting_iterator.

但是,在实践中,我希望实现将用于std::iterator_traits::iterator_category调度算法的适当重载,就像 PSTL 似乎所做的那样。因此,我相信 OP 中的示例代码会按预期工作。cmcstl2 不起作用的原因可能是 usediterator_category属于__stl2命名空间而不是std那些。

于 2018-12-10T09:13:26.877 回答
1

在 C++20 中,有一个std::views::common使范围适应标准迭代器接受算法的范围。在将输入范围转换为std::ranges::common_range使用std::ranges::beginandstd::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++
于 2020-10-27T11:20:43.600 回答
0

我自己也遇到过这个;我相信本质上的根本问题是 std::ranges 和 range-v3 现在都需要 snetinels 的概念才能正常工作 - 请参阅此处以获取有关此的帖子。我已经添加了一个单独的答案,因为这个概念是代码无法编译的理论原因。感谢其他答案指向正确的解决方法,然后将它们转换为通用范围。

正如您所提到的,旧的 std 算法具有相同类型的迭代器,因此可以执行比较i != end以控制循环结构。各种标准库函数的重载依赖于两个迭代器具有相同的推导类型。这现在被称为一个公共范围,因为迭代器共享一个公共类型。但是,对于范围,这很有效,我们数据的概念更通用,直到到达终点才能知道。因此,和以前一样,“结束”标记表示没有更多数据可供使用,但这种行为现在被编码为单独的类型(哨兵),而不是依赖于比较,这对于例如无限序列是无法执行的。因此,范围算法现在需要重载两个单独的模板参数 - 迭代器和哨兵,以便正确运行。如果没有适配器,范围就不能用于传统的迭代器算法,但这正是views::common我们要做的。

于 2022-02-14T19:34:14.683 回答