【问题标题】:Can I use std::transform in place with a parallel execution policy?我可以在并行执行策略中使用 std::transform 吗?
【发布时间】:2020-02-04 04:55:16
【问题描述】:

如果我没记错的话,我可以让std::transform 执行就地,方法是使用与输入和输出迭代器相同的范围。假设我有一些std::vector 对象vec,那么我会写

std::transform(vec.cbegin(),vec.cend(),vec.begin(),unary_op)

使用合适的一元运算unary_op

使用 C++17 标准,我想通过在其中粘贴 std::execution::par 作为第一个参数来并行执行转换。这将使函数在cppreference article on std::transform 中从重载 (1) 变为 (2)。然而,这个重载的 cmets 说:

unary_op [...] 不得使任何迭代器无效,包括结束迭代器,或修改所涉及范围的任何元素。 (C++11 起)

“修改任何元素”是否真的意味着我不能在适当的位置使用算法,或者这是在谈论我误解的不同细节?

【问题讨论】:

    标签: c++ parallel-processing stl c++17


    【解决方案1】:

    我相信它在谈论不同的细节。 unary_op 接受序列的一个元素并返回一个值。该值(由transform)存储到目标序列中。

    所以这个unary_op 就可以了:

    int times2(int v) { return 2*v; }
    

    但这个不会:

    int times2(int &v) { return v*=2; }
    

    但这并不是你真正要问的。 您想知道是否可以将transformunary_op 版本用作具有相同源和目标范围的并行算法。我不明白为什么不。 transform 将源序列的单个元素映射到目标序列的单个元素。但是,如果您的 unary_op 不是真正的一元,(即,它引用序列中的其他元素 - 即使它只读取它们,那么您将遇到数据竞争)。

    【讨论】:

      【解决方案2】:

      这里引用标准

      [alg.transform.1]

      op [...] 不应使迭代器或子范围无效,或修改 范围

      这将禁止您的 unary_op 修改作为参数给出的值或容器本身。

      auto unary_op = [](auto& value) 
      { 
          value = 10;    // this is bad
          return value;
      }
      
      auto unary_op = [&vec](auto const& value) 
      { 
          vec[0] = value;   // also bad
          return value;
      }
      
      auto unary_op = [&vec](auto& value) 
      { 
          vec.erase(vec.begin());   // nope 
          return value;
      }
      

      但是,以下是可以的。

      auto unary_op = [](auto& value)  // const/ref not strictly needed
      {         
          return value + 10;   // totally fine
      }
      
      auto unary_op = [&vec](auto& value)
      {         
          return value + vec[0];   // ok in sequential but not in parallel execution
      }
      

      独立于我们拥有的UnaryOperation

      [alg.transform.5]

      在一元变换的情况下,结果可能等于第一个 [...]。

      意味着明确允许就地操作。

      现在

      [算法.parallel.overloads.2]

      除非另有说明,否则 ExecutionPolicy 算法重载的语义与没有它们的重载相同。

      表示执行策略在算法上没有用户可见的差异。您可以期望该算法产生完全相同的结果,就像您不指定执行策略一样。

      【讨论】:

        【解决方案3】:

        正如您在引用的链接示例中所见,修改任何元素确实意味着对元素进行所有类型的修改:

        函数的签名应该等同于:

        Ret fun(const Type &a);
        

        这包括对元素的修改。在最坏的情况下,如果您对目标使用相同的迭代器,则修改不应导致迭代器失效,例如push_back 到向量或 erasing 从 vector 可能会导致迭代器失效。

        查看一个你不应该做的失败示例Live

        【讨论】:

          猜你喜欢
          • 2018-06-17
          • 2022-07-06
          • 2021-03-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-01-23
          相关资源
          最近更新 更多