【问题标题】:Range-v3 view composition and views calculation parallelizationRange-v3 视图组合和视图计算并行化
【发布时间】:2021-07-13 23:21:55
【问题描述】:

取自 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);});

我知道views::foo 等同于foo_view(),因此上面的示例最终如下:

transform_view(remove_if_view(vi, <lambda>), <lambda>)

现在的问题是:

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 是不是非常简单,因为它们将一个简单的范围(由多个视图组成,通过操作融合按需计算)作为输入)?

【问题讨论】:

  • 不,并行化范围算法并不容易。忘掉实现吧,你如何选择一个范围内的线程分布,这个范围可以由filter、take_first_n、reverse等任意顺序的操作组成?
  • 我猜想在take_first_n | reverse 上进行一个简单的fork-join 会完成这项工作吗?另外,还有更多富有成效的例子,如果并行化会带来性能提升(就像我所指的那样,在我的问题中)
  • 好吧,range-v3 是open source 如果你觉得它很琐碎,你可以去尝试并行化它,或者你可以用幼稚的方法来做。
  • 这个问题有很多。你基本上是问views::remove_ifviews::transform是怎么实现的?
  • @Barry 我在问它们是如何作为参考示例实现的。主要问题是从流水线视图派生的范围一般是如何实现的

标签: c++ std c++20 range-v3 std-ranges


【解决方案1】:

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

如果您将transform 放在remove_if 之前,这可能会导致管道效率低下。转换将对每个项目进行两次评估。一次用于推进迭代器,第二次用于读取当前值。如果转换不是微不足道的,那么你会减速。如果转换有副作用,那么可能会发生不好的事情。见

Why C++ ranges "transform -> filter" calls transform twice for values that match the filter's predicate?

更多详情

关于使操作并行化,它可能类似于 std 库实现,通过向每个 AST 节点添加一个标记参数来说明您是否要并行执行它。详情请见https://www.modernescpp.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());

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

rangev3 github 页面上有一个关于此主题的未决问题,其中包含您可能感兴趣的更多链接。

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

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

【讨论】:

  • 我的问题是故意开放的,但你还是给出了很好的答案!感谢您的解释和参考!
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 2019-10-04
  • 1970-01-01
  • 1970-01-01
  • 2013-04-19
  • 1970-01-01
  • 1970-01-01
  • 2011-06-22
相关资源
最近更新 更多