Range-v3 视图组合和视图计算并行化

joh*_*lis 5 c++ std range-v3 c++20 std-ranges

以下示例取自 range-v3 文档,演示了一个简单的views流水线组合以生成一个range

std::vector<int> const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
using namespace ranges;
auto rng = vi | views::remove_if([](int i){return i % 2 == 1;})
              | views::transform([](int i){return std::to_string(i);});
Run Code Online (Sandbox Code Playgroud)

我知道这views::foo相当于类似的东西foo_view(),因此上面的例子最终是这样的:

transform_view(remove_if_view(vi, <lambda>), <lambda>)
Run Code Online (Sandbox Code Playgroud)

现在的问题是:

remove_iftransform操作的顺序如何?(我知道他们是懒惰的,他们实际上并没有在这一步计算,而不是在实现时计算rng,但这不是重点)。

我可以在这里看到两个选项:

  1. 这些操作由 range-v3融合,当rng通过某个迭代器访问给定元素时,这两个操作因此应用于该元素。

  2. 当请求给定的元素时,整个remove_if操作将贯穿始终vi,然后该操作的输出向量被送入transform。因此,我们最终得到了一个完整的“ trasformed + removed_if ”向量,它使我们能够访问所需的元素。

我很确定选项 (1) 是实际发生的事情。如果是这种情况,range-v3 是如何实现的?它是否具有某种通用组合代码以组合无限数量的组合视图操作?

附带问题:range-v3 视图公开什么样的迭代器?我认为random-access迭代器下面的任何内容都会使并行化变得不可能。

元问题:如果选项(1)是事实,那么range-algorithms考虑到它们将一个简单的范围(由多个视图组成,通过操作融合按需计算)作为输入,并行化不是非常简单吗?

bra*_*ing 3

如果我必须猜测的话,我会说|运算符构建了操作的编译时 AST(抽象语法树)。如果你把这个 AST 存储到一个名为的变量中range,然后你调用auto it = std::begin(range)你具体化一个迭代器,其类型将是不平凡的(除非使用类型擦除)。当您调用时,*it它会评估从取消引用原始迭代器的当前状态开始的所有转换操作。当你调用时,++it情况会稍微复杂一些。它需要做几件事。它需要推进基本迭代器,但还需要评估所有中间变换,以便remove_if可以评估谓词 in 。如果谓词返回 true,那么当我们跳过一个项目时,基迭代器需要再次前进。

transform如果将 a 放在a 之前,可能会导致管道效率低下remove_if。每个项目的转换都会被评估两次。一次用于推进迭代器,第二次用于读取当前值。如果转换不简单,那么速度就会变慢。如果转换有副作用,那么可能会发生不好的事情。看

为什么 C++ 范围“变换 -> 过滤器”为与过滤器谓词匹配的值调用两次变换?

更多细节

关于使操作并行,可能会类似于 std 库实现,通过向每个 AST 节点添加一个标记参数来说明是否要并行执行。有关详细信息,请参阅https://www.modernnescpp.com/index.php/parallel-algorithm-of-the-standard-template-library

例如

vector<int> v = ...

// standard sequential sort
std::sort(v.begin(), v.end());

// sequential execution
std::sort(std::parallel::seq, v.begin(), v.end());

// permitting parallel execution
std::sort(std::parallel::par, v.begin(), v.end());

// permitting parallel and vectorized execution
std::sort(std::parallel::par_unseq, v.begin(), v.end());
Run Code Online (Sandbox Code Playgroud)

这种模式可以扩展到 range-v3,但本质上重载将是新的实现,并且实现起来并不简单。

rangev3 github 页面上有一个关于此主题的开放问题,其中包含一些您可能感兴趣的进一步链接。

https://github.com/ericniebler/range-v3/issues/921

(我不是 range-v3 的贡献者,但我已经实现了自己的 range 类库,该库与 range-v3 的功能有重叠。)